style: migrate to cadence

This commit is contained in:
zkldi
2022-06-26 16:28:11 +01:00
parent 7e6b8dc898
commit cc82db447b
19 changed files with 1442 additions and 1455 deletions
+1 -2
View File
@@ -1,3 +1,2 @@
node_modules
js
scripts
js
+26 -51
View File
@@ -1,59 +1,34 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
"@typescript-eslint",
"import",
"cadence"
],
"env": {
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
"plugin:cadence/recommended"
],
"rules": {
"prettier/prettier": [
"error",
{
"printWidth": 100,
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"endOfLine": "auto",
"useTabs": true
}
],
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"no-unused-labels": "error",
"prefer-const": "error",
"eqeqeq": "error",
"no-eval": "error",
"no-var": "error",
"no-new-object": "error",
"quote-props": [
"error",
"as-needed"
],
"no-array-constructor": "error",
"prefer-template": "error",
"prefer-promise-reject-errors": "error",
"no-useless-escape": "error",
"no-loop-func": "error",
"prefer-rest-params": "error",
"default-param-last": "error",
"no-new-func": "error",
"arrow-body-style": "error",
"no-useless-constructor": "error",
"no-duplicate-imports": "error",
"dot-notation": "error",
"space-before-blocks": "error",
"no-prototype-builtins": "off",
"prefer-arrow-callback": "error"
}
"no-inline-comments": "off"
},
"overrides": [
{
"files": [
"**/*.test.ts"
],
"rules": {
// Our test files break these rules *all* the time, and there's
// no point refactoring. Our tests deliberately play with the
// dynamic nature of TS to more accurately test arbitrary input.
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-call": "off",
// we abuse this one for faking data, but we really shouldn't.
"@typescript-eslint/consistent-type-assertions": "warn"
}
}
]
}
+4 -7
View File
@@ -9,19 +9,16 @@
"scripts": {
"test": "tap",
"build": "tsc",
"prepublish": "tsc"
"prepublish": "tsc",
"lint": "eslint . --ext .ts --fix"
},
"author": "zkldi",
"license": "MIT",
"devDependencies": {
"@types/node": "17.0.8",
"@types/tap": "15.0.3",
"@typescript-eslint/eslint-plugin": "4.19.0",
"@typescript-eslint/parser": "4.19.0",
"eslint": "7.22.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-prettier": "3.3.1",
"prettier": "2.2.1",
"eslint": "8",
"eslint-plugin-cadence": "0.0.3",
"tap": "15.0.9",
"ts-node": "10.0.0",
"typescript": "4.6.3"
+811 -278
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,6 +1,6 @@
import t from "tap";
import { GetGameConfig, GetGamePTConfig } from "./config";
import { allSupportedGames } from "./static-config";
import t from "tap";
t.test("#GetGameConfig", (t) => {
t.plan(allSupportedGames.length);
@@ -18,6 +18,7 @@ t.test("#GetGamePTConfig", (t) => {
for (const playtype of gameConfig.validPlaytypes) {
const conf = GetGamePTConfig(game, playtype);
t.equal(
conf.idString,
`${game}:${playtype}`,
+34 -26
View File
@@ -1,17 +1,16 @@
import { FormatInt, GPTTierlists } from "..";
import { FormatInt } from "..";
import {
BMSGenocideDans,
BMSLNDans,
BMSScratchDans,
BMSStSlDans,
CHUNITHMColours,
ClassInfo,
DDRDans,
GameClassSets,
GitadoraColours,
IIDXDans,
JubeatColours,
PMSDans,
// PMSDans,
PopnClasses,
SDVXDans,
@@ -19,8 +18,11 @@ import {
WaccaColours,
WaccaStageUps,
} from "../game-classes";
import { ESDJudgementFormat } from "../lib/esd";
import {
import { FormatSieglindeBMS, FormatSieglindePMS } from "../utils/util";
import type { GPTTierlists } from "..";
import type { ClassInfo, GameClassSets } from "../game-classes";
import type { ESDJudgementFormat } from "../lib/esd";
import type {
Playtypes,
Game,
Difficulties,
@@ -33,13 +35,12 @@ import {
JudgementLookup,
GPTSupportedVersions,
} from "../types";
import { FormatSieglindeBMS, FormatSieglindePMS } from "../utils/util";
export interface GameConfig<G extends Game = Game> {
internalName: string;
name: string;
defaultPlaytype: Playtypes[G];
validPlaytypes: Playtypes[G][];
validPlaytypes: Array<Playtypes[G]>;
}
interface BaseGamePTConfig<I extends IDStrings> {
@@ -51,9 +52,9 @@ interface BaseGamePTConfig<I extends IDStrings> {
defaultSessionRatingAlg: SessionCalculatedDataLookup[I];
defaultProfileRatingAlg: UGSRatingsLookup[I];
scoreRatingAlgs: ScoreCalculatedDataLookup[I][];
sessionRatingAlgs: SessionCalculatedDataLookup[I][];
profileRatingAlgs: UGSRatingsLookup[I][];
scoreRatingAlgs: Array<ScoreCalculatedDataLookup[I]>;
sessionRatingAlgs: Array<SessionCalculatedDataLookup[I]>;
profileRatingAlgs: Array<UGSRatingsLookup[I]>;
scoreRatingAlgDescriptions: Record<ScoreCalculatedDataLookup[I], string>;
sessionRatingAlgDescriptions: Record<SessionCalculatedDataLookup[I], string>;
@@ -65,35 +66,35 @@ interface BaseGamePTConfig<I extends IDStrings> {
>;
profileRatingAlgFormatters: Partial<Record<UGSRatingsLookup[I], (v: number) => string>>;
difficulties: Difficulties[I][];
difficulties: Array<Difficulties[I]>;
shortDifficulties: Partial<Record<Difficulties[I], string>>;
defaultDifficulty: Difficulties[I];
difficultyColours: Record<Difficulties[I], string | null>;
grades: Grades[I][];
grades: Array<Grades[I]>;
gradeColours: Record<Grades[I], string>;
clearGrade: Grades[I];
gradeBoundaries: number[] | null;
gradeBoundaries: Array<number> | null;
lamps: Lamps[I][];
lamps: Array<Lamps[I]>;
lampColours: Record<Lamps[I], string>;
clearLamp: Lamps[I];
classHumanisedFormat: Record<GameClassSets[I], ClassInfo[]>;
classHumanisedFormat: Record<GameClassSets[I], Array<ClassInfo>>;
judgements: JudgementLookup[I][];
judgements: Array<JudgementLookup[I]>;
scoreBucket: "grade" | "lamp";
supportedVersions: GPTSupportedVersions[I][];
supportedVersions: Array<GPTSupportedVersions[I]>;
tierlists: GPTTierlists[I][];
tierlists: Array<GPTTierlists[I]>;
tierlistDescriptions: Record<GPTTierlists[I], string>;
}
interface GamePTConfigWithESD<I extends IDStrings> extends BaseGamePTConfig<I> {
supportsESD: true;
judgementWindows: ESDJudgementFormat[];
judgementWindows: Array<ESDJudgementFormat>;
}
interface GamePTConfigWithoutESD<I extends IDStrings> extends BaseGamePTConfig<I> {
@@ -340,6 +341,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// IIDX has some unhinged grade boundaries...
// You might think these two lines are identical, but they are not. The order of operations affects
// floating point math at this point.
@@ -537,6 +539,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// see iidx:SP for an explaination of why this is wrote like this.
gradeBoundaries: [
0,
@@ -1099,6 +1102,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// see iidx:SP for an explaination of why this is wrote like this.
gradeBoundaries: [
0,
@@ -1210,6 +1214,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// see iidx:SP for an explaination of why this is wrote like this.
gradeBoundaries: [
0,
@@ -1277,8 +1282,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
profileRatingAlgs: ["MFCP", "ktRating"],
scoreRatingAlgDescriptions: {
MFCP:
"Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.",
MFCP: "Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.",
ktRating: "TODO REMOVE ME",
},
profileRatingAlgDescriptions: {
@@ -1409,8 +1413,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
profileRatingAlgs: ["MFCP", "ktRating"],
scoreRatingAlgDescriptions: {
MFCP:
"Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.",
MFCP: "Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.",
ktRating: "TODO REMOVE ME",
},
profileRatingAlgDescriptions: {
@@ -1589,6 +1592,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
"SSS+": COLOUR_SET.white,
},
clearGrade: "A",
// @hack Maimai's top grade depends on the chart's maximum percent
// we just set it at percentMax, but it's not technically correct
gradeBoundaries: [0, 10, 20, 40, 60, 80, 90, 94, 97, 98, 99, 99.5, 100, Infinity],
@@ -1822,8 +1826,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
},
profileRatingAlgDescriptions: {
naiveRate: "A naive rating algorithm that just averages your 50 best scores.",
rate:
"Rating as it's implemented in game, taking 15 scores from the latest version and 35 from all old versions.",
rate: "Rating as it's implemented in game, taking 15 scores from the latest version and 35 from all old versions.",
},
sessionRatingAlgDescriptions: {
rate: "The average of your best 10 ratings this session.",
@@ -1865,6 +1868,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MASTER: COLOUR_SET.white,
},
clearGrade: "S",
// i'm pretty sure the first grade is below the limit of reliable FPA accuracy
gradeBoundaries: [0, 0.0001, 3.0001, 70, 80, 85, 90, 93, 95, 97, 98, 99, 100],
@@ -1948,6 +1952,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
S: COLOUR_SET.teal,
},
clearGrade: "A",
// TECHNICALLY THIS IS NOT CORRECT
// A Fail is capped at A rank (82%), no matter what!
gradeBoundaries: [0, 50, 62, 72, 82, 90, 95, 98],
@@ -2117,6 +2122,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// see iidx:SP for an explaination of why this is wrote like this.
gradeBoundaries: [
0,
@@ -2224,6 +2230,7 @@ const GAME_PT_CONFIGS: GamePTConfigs = {
MAX: COLOUR_SET.white,
},
clearGrade: "A",
// see iidx:SP for an explaination of why this is wrote like this.
gradeBoundaries: [
0,
@@ -2396,5 +2403,6 @@ export function GetGamePTConfig<I extends IDStrings = IDStrings>(
playtype: Playtypes[Game]
): GamePTConfig<I> {
const idString = `${game}:${playtype}` as IDStrings;
return (GAME_PT_CONFIGS[idString] as unknown) as GamePTConfig<I>;
return GAME_PT_CONFIGS[idString] as unknown as GamePTConfig<I>;
}
+7 -7
View File
@@ -1,4 +1,4 @@
import {
import type {
FileUploadImportTypes,
IRImportTypes,
APIImportTypes,
@@ -8,7 +8,7 @@ import {
GPTSupportedVersions,
} from "..";
export const fileImportTypes: FileUploadImportTypes[] = [
export const fileImportTypes: Array<FileUploadImportTypes> = [
"file/eamusement-iidx-csv",
"file/eamusement-sdvx-csv",
"file/batch-manual",
@@ -17,7 +17,7 @@ export const fileImportTypes: FileUploadImportTypes[] = [
"file/pli-iidx-csv",
];
export const irImportTypes: IRImportTypes[] = [
export const irImportTypes: Array<IRImportTypes> = [
"ir/direct-manual",
"ir/barbatos",
"ir/fervidex",
@@ -27,7 +27,7 @@ export const irImportTypes: IRImportTypes[] = [
"ir/kshook-sv6c",
];
export const apiImportTypes: APIImportTypes[] = [
export const apiImportTypes: Array<APIImportTypes> = [
"api/arc-iidx",
"api/arc-sdvx",
"api/eag-iidx",
@@ -37,13 +37,13 @@ export const apiImportTypes: APIImportTypes[] = [
"api/min-sdvx",
];
export const allImportTypes: ImportTypes[] = [
export const allImportTypes: Array<ImportTypes> = [
...fileImportTypes,
...irImportTypes,
...apiImportTypes,
];
export const allIDStrings: IDStrings[] = [
export const allIDStrings: Array<IDStrings> = [
"iidx:SP",
"bms:14K",
"bms:7K",
@@ -65,7 +65,7 @@ export const allIDStrings: IDStrings[] = [
"itg:Stamina",
];
export const allSupportedGames: Game[] = [
export const allSupportedGames: Array<Game> = [
"iidx",
"museca",
"maimai",
+122 -122
View File
@@ -1,112 +1,112 @@
export enum IIDX_LAMPS {
NO_PLAY,
FAILED,
ASSIST_CLEAR,
EASY_CLEAR,
CLEAR,
HARD_CLEAR,
EX_HARD_CLEAR,
FULL_COMBO,
NO_PLAY = 0,
FAILED = 1,
ASSIST_CLEAR = 2,
EASY_CLEAR = 3,
CLEAR = 4,
HARD_CLEAR = 5,
EX_HARD_CLEAR = 6,
FULL_COMBO = 7,
}
export enum CHUNITHM_LAMPS {
FAILED,
CLEAR,
FULL_COMBO,
ALL_JUSTICE,
ALL_JUSTICE_CRITICAL,
FAILED = 0,
CLEAR = 1,
FULL_COMBO = 2,
ALL_JUSTICE = 3,
ALL_JUSTICE_CRITICAL = 4,
}
export enum SDVX_LAMPS {
FAILED,
CLEAR,
EXCESSIVE_CLEAR,
ULTIMATE_CHAIN,
PERFECT_ULTIMATE_CHAIN,
FAILED = 0,
CLEAR = 1,
EXCESSIVE_CLEAR = 2,
ULTIMATE_CHAIN = 3,
PERFECT_ULTIMATE_CHAIN = 4,
}
export enum MUSECA_LAMPS {
FAILED,
CLEAR,
CONNECT_ALL,
PERFECT_CONNECT_ALL,
FAILED = 0,
CLEAR = 1,
CONNECT_ALL = 2,
PERFECT_CONNECT_ALL = 3,
}
export enum DDR_LAMPS {
FAILED,
CLEAR,
LIFE4,
FULL_COMBO,
GREAT_FULL_COMBO,
PERFECT_FULL_COMBO,
MARVELOUS_FULL_COMBO,
FAILED = 0,
CLEAR = 1,
LIFE4 = 2,
FULL_COMBO = 3,
GREAT_FULL_COMBO = 4,
PERFECT_FULL_COMBO = 5,
MARVELOUS_FULL_COMBO = 6,
}
export enum MAIMAI_LAMPS {
FAILED,
CLEAR,
FULL_COMBO,
ALL_PERFECT,
ALL_PERFECT_PLUS,
FAILED = 0,
CLEAR = 1,
FULL_COMBO = 2,
ALL_PERFECT = 3,
ALL_PERFECT_PLUS = 4,
}
export enum GITADORA_LAMPS {
FAILED,
CLEAR,
FULL_COMBO,
EXCELLENT,
FAILED = 0,
CLEAR = 1,
FULL_COMBO = 2,
EXCELLENT = 3,
}
export enum IIDX_GRADES {
F,
E,
D,
C,
B,
A,
AA,
AAA,
MAX_MINUS,
MAX,
F = 0,
E = 1,
D = 2,
C = 3,
B = 4,
A = 5,
AA = 6,
AAA = 7,
MAX_MINUS = 8,
MAX = 9,
}
export enum CHUNITHM_GRADES {
D,
C,
B,
BB,
BBB,
A,
AA,
AAA,
S,
SS,
SSS,
D = 0,
C = 1,
B = 2,
BB = 3,
BBB = 4,
A = 5,
AA = 6,
AAA = 7,
S = 8,
SS = 9,
SSS = 10,
}
export enum SDVX_GRADES {
D,
C,
B,
A,
A_PLUS,
AA,
AA_PLUS,
AAA,
AAA_PLUS,
S,
D = 0,
C = 1,
B = 2,
A = 3,
A_PLUS = 4,
AA = 5,
AA_PLUS = 6,
AAA = 7,
AAA_PLUS = 8,
S = 9,
}
export enum MUSECA_GRADES {
没,
拙,
凡,
佳,
良,
優,
秀,
傑,
傑G,
没 = 0,
拙 = 1,
凡 = 2,
佳 = 3,
良 = 4,
優 = 5,
秀 = 6,
傑 = 7,
傑G = 8,
}
/**
@@ -116,57 +116,57 @@ export enum MUSECA_GRADES {
* to 975K.
*/
export enum MUSECA_GRADES_ASCII {
ZERO,
K600,
K700,
K800,
K850,
K900,
K950,
K975,
K1000,
ZERO = 0,
K600 = 1,
K700 = 2,
K800 = 3,
K850 = 4,
K900 = 5,
K950 = 6,
K975 = 7,
K1000 = 8,
}
export enum DDR_GRADES {
D,
D_PLUS,
C_MINUS,
C,
C_PLUS,
B_MINUS,
B,
B_PLUS,
A_MINUS,
A,
A_PLUS,
AA_MINUS,
AA,
AA_PLUS,
AAA,
D = 0,
D_PLUS = 1,
C_MINUS = 2,
C = 3,
C_PLUS = 4,
B_MINUS = 5,
B = 6,
B_PLUS = 7,
A_MINUS = 8,
A = 9,
A_PLUS = 10,
AA_MINUS = 11,
AA = 12,
AA_PLUS = 13,
AAA = 14,
}
export enum MAIMAI_GRADES {
F,
E,
D,
C,
B,
A,
AA,
AAA,
S,
S_PLUS,
SS,
SS_PLUS,
SSS,
SSS_PLUS,
F = 0,
E = 1,
D = 2,
C = 3,
B = 4,
A = 5,
AA = 6,
AAA = 7,
S = 8,
S_PLUS = 9,
SS = 10,
SS_PLUS = 11,
SSS = 12,
SSS_PLUS = 13,
}
export enum GITADORA_GRADES {
C,
B,
A,
S,
SS,
MAX,
C = 0,
B = 1,
A = 2,
S = 3,
SS = 4,
MAX = 5,
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { APIPermissions } from "../types";
import type { APIPermissions } from "../types";
// Some parts of tachi-server and some parts of the schemas refer to this
// in order to assert that permissions are correctly assigned.
+15 -14
View File
@@ -1,7 +1,7 @@
// Classes refer to things like dans.
// Not the JS construct.
import { IDStrings, integer } from "./types";
import type { IDStrings, integer } from "./types";
export interface ClassInfo {
display: string;
@@ -11,7 +11,7 @@ export interface ClassInfo {
backgroundColor: string;
color: string;
};
variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "info";
variant?: "danger" | "info" | "primary" | "secondary" | "success" | "warning";
}
function mouseoverCSS(id: string, d: string, m: string, bg = "black", color = "white") {
@@ -22,7 +22,7 @@ function mouseoverVariant(
id: string,
d: string,
m: string,
variant: "primary" | "secondary" | "success" | "warning" | "danger" | "info"
variant: "danger" | "info" | "primary" | "secondary" | "success" | "warning"
) {
return { id, display: d, mouseover: m, variant };
}
@@ -30,7 +30,7 @@ function mouseoverVariant(
function noMouseoverVariant(
id: string,
d: string,
variant: "primary" | "secondary" | "success" | "warning" | "danger" | "info"
variant: "danger" | "info" | "primary" | "secondary" | "success" | "warning"
) {
return { id, display: d, variant };
}
@@ -39,7 +39,7 @@ function noMouseoverCSS(id: string, d: string, bg = "#131313", color = "white")
return { id, display: d, css: { backgroundColor: bg, color } };
}
export const IIDXDans: ClassInfo[] = [
export const IIDXDans: Array<ClassInfo> = [
mouseoverCSS("KYU_7", "七級", "7th Kyu", "green"),
mouseoverCSS("KYU_6", "六級", "6th Kyu", "green"),
mouseoverCSS("KYU_5", "五級", "5th Kyu", "green"),
@@ -61,7 +61,7 @@ export const IIDXDans: ClassInfo[] = [
mouseoverVariant("KAIDEN", "皆伝", "Kaiden", "warning"),
];
export const GitadoraColours: ClassInfo[] = [
export const GitadoraColours: Array<ClassInfo> = [
mouseoverCSS("WHITE", "白", "White", "white", "black"),
mouseoverCSS("ORANGE", "橙", "Orange", "orange"),
mouseoverCSS("ORANGE_GRD", "橙グラ", "Orange Gradient", "orange"),
@@ -78,11 +78,12 @@ export const GitadoraColours: ClassInfo[] = [
mouseoverCSS("BRONZE", "銅", "Bronze", "bronze"),
mouseoverCSS("SILVER", "銀", "Silver", "silver", "black"),
mouseoverCSS("GOLD", "金", "Gold", "gold"),
// @todo #11 Come up with CSS for Gitadora's rainbow icon.
mouseoverCSS("RAINBOW", "虹", "Rainbow", "todo"),
];
export const BMSGenocideDans: ClassInfo[] = [
export const BMSGenocideDans: Array<ClassInfo> = [
mouseoverCSS("NORMAL_1", "☆1", "Normal 1st Dan", "lightblue"),
mouseoverCSS("NORMAL_2", "☆2", "Normal 2nd Dan", "lightblue"),
mouseoverCSS("NORMAL_3", "☆3", "Normal 3rd Dan", "lightblue"),
@@ -107,7 +108,7 @@ export const BMSGenocideDans: ClassInfo[] = [
mouseoverCSS("OVERJOY", "(^^)", "Overjoy", "purple"),
];
export const BMSStSlDans: ClassInfo[] = [
export const BMSStSlDans: Array<ClassInfo> = [
mouseoverCSS("SL0", "sl0", "Satellite 0"),
mouseoverCSS("SL1", "sl1", "Satellite 1"),
mouseoverCSS("SL2", "sl2", "Satellite 2"),
@@ -135,7 +136,7 @@ export const BMSStSlDans: ClassInfo[] = [
mouseoverCSS("ST11", "st11", "Stella 11"),
];
export const BMSLNDans: ClassInfo[] = [
export const BMSLNDans: Array<ClassInfo> = [
mouseoverCSS("DAN_1", "◆1", "LN 1st Dan", "lightblue"),
mouseoverCSS("DAN_2", "◆2", "LN 2nd Dan", "lightblue"),
mouseoverCSS("DAN_3", "◆3", "LN 3rd Dan", "lightblue"),
@@ -151,7 +152,7 @@ export const BMSLNDans: ClassInfo[] = [
mouseoverCSS("UDON", "◆うどん", "LN Udon", "gold"),
];
export const BMSScratchDans: ClassInfo[] = [
export const BMSScratchDans: Array<ClassInfo> = [
mouseoverCSS("KYU_7", "七級", "Scratch 7th Kyu", "green"),
mouseoverCSS("KYU_6", "六級", "Scratch 6th Kyu", "green"),
mouseoverCSS("KYU_5", "五級", "Scratch 5th Kyu", "green"),
@@ -172,7 +173,7 @@ export const BMSScratchDans: ClassInfo[] = [
mouseoverVariant("KAIDEN", "皆伝", "Scratch Kaiden", "warning"),
];
export const SDVXDans: ClassInfo[] = [
export const SDVXDans: Array<ClassInfo> = [
mouseoverCSS("DAN_1", "LV.01", "1st Dan"),
mouseoverCSS("DAN_2", "LV.02", "2nd Dan"),
mouseoverCSS("DAN_3", "LV.03", "3rd Dan"),
@@ -187,7 +188,7 @@ export const SDVXDans: ClassInfo[] = [
mouseoverCSS("INF", "LV.INF", "Inf. Dan", "purple", "gold"),
];
export const SDVXVFClasses: ClassInfo[] = [
export const SDVXVFClasses: Array<ClassInfo> = [
noMouseoverCSS("SIENNA_I", "Sienna I"),
noMouseoverCSS("SIENNA_II", "Sienna II"),
noMouseoverCSS("SIENNA_III", "Sienna III"),
@@ -340,12 +341,12 @@ export interface GameClassSets {
"maimai:Single": never;
"jubeat:Single": "colour";
"museca:Single": never;
"bms:7K": "genocideDan" | "stslDan" | "lnDan" | "scratchDan";
"bms:7K": "genocideDan" | "lnDan" | "scratchDan" | "stslDan";
"bms:14K": "genocideDan";
"chunithm:Single": "colour";
"gitadora:Gita": "colour";
"gitadora:Dora": "colour";
"wacca:Single": "stageUp" | "colour";
"wacca:Single": "colour" | "stageUp";
"pms:Controller": "dan";
"pms:Keyboard": "dan";
"itg:Stamina": never;
+1 -2
View File
@@ -1,6 +1,5 @@
import t from "tap";
import * as tachiCommon from "./index";
import t from "tap";
// we just check that we're exporting stuff properly
t.test("Property Checks", (t) => {
-539
View File
@@ -1,539 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
// disable all type checking, this is a legacy file and is only kept around
// as a reference
import { Game, Playtypes, ChartDocument, ChartDocument } from "../types";
// human readable stuff for versions
const versionHuman: Record<Game, Record<string, string>> = {
iidx: {
0: "1st Style",
1: "substream",
2: "2nd Style",
3: "3rd Style",
4: "4th Style",
5: "5th Style",
6: "6th Style",
7: "7th Style",
8: "8th Style",
9: "9th Style",
10: "10th Style",
11: "IIDX RED",
12: "HAPPY SKY",
13: "DISTORTED",
14: "GOLD",
15: "DJ TROOPERS",
16: "EMPRESS",
17: "SIRIUS",
18: "Resort Anthem",
19: "Lincle",
20: "tricoro",
21: "SPADA",
22: "PENDUAL",
23: "copula",
24: "SINOBUZ",
25: "CANNON BALLERS",
26: "ROOTAGE",
27: "HEROIC VERSE",
28: "BISTROVER",
},
museca: {
1: "",
1.5: "1+1/2",
},
maimai: {
maimai: "",
maimaiplus: "PLUS",
green: "GReeN",
greenplus: "GReeN PLUS",
orange: "ORANGE",
orangeplus: "ORANGE PLUS",
pink: "PiNK",
pinkplus: "PiNK PLUS",
murasaki: "MURASAKi",
murasakiplus: "MURASAKi PLUS",
milk: "MiLK",
milkplus: "MiLK PLUS",
finale: "FiNALE",
},
// jubeat: {
// jubeat: "",
// ripples: "ripples",
// knit: "knit",
// copious: "copious",
// saucer: "saucer",
// saucerfulfill: "saucer fulfill",
// prop: "prop",
// qubell: "Qubell",
// clan: "clan",
// festo: "festo",
// },
// popn: {
// 1: "1",
// 2: "2",
// 3: "3",
// 4: "4",
// 5: "5",
// 6: "6",
// 7: "7",
// 8: "8",
// 9: "9",
// 10: "10",
// 11: "11",
// 12: "12 Iroha",
// 13: "13 CARNIVAL",
// 14: "14 FEVER!",
// 15: "15 ADVENTURE",
// 16: "16 PARTY♪",
// 17: "17 THE MOVIE",
// 18: "18 Sengoku",
// 19: "19 TUNE STREET",
// 20: "20 fantasia",
// park: "Sunny Park",
// lapis: "Lapistoria",
// eclale: "éclale",
// usaneko: "Usaneko",
// peace: "peace",
// },
sdvx: {
booth: "BOOTH",
inf: "II -infinite infection-",
gw: "III GRAVITY WARS",
heaven: "IV HEAVENLY HAVEN",
vivid: "V VIVID WAVE",
},
ddr: {
1: "1st Mix",
2: "2nd Mix",
3: "3rd Mix",
4: "4th Mix",
5: "5th Mix",
max: "MAX",
max2: "MAX2",
extreme: "EXTREME",
snova: "SuperNOVA",
snova2: "SuperNOVA 2",
x: "X",
x2: "X2",
x3: "X3 vs. 2nd Mix",
2013: "(2013)",
2014: "(2014)",
a: "Ace",
a20: "A20",
},
chunithm: {
chuni: "",
chuniplus: "PLUS",
air: "AIR",
airplus: "AIR PLUS",
star: "STAR",
starplus: "STAR PLUS",
amazon: "AMAZON",
amazonplus: "AMAZON PLUS",
crystal: "CRYSTAL",
crystalplus: "CRYSTAL PLUS",
},
gitadora: {
xg: "XG",
xg2: "XG2",
xg3: "XG3",
gitadora: "",
overdrive: "OverDrive",
triboost: "Tri-Boost",
triboostplus: "Tri-Boost (Re:EVOLVE)",
matixx: "Matixx",
exchain: "EXCHAIN",
nextage: "NEX+AGE",
},
bms: {
0: "BMS",
},
usc: {
0: "USC",
},
};
// release orders of the games.
const gameOrders = {
iidx: [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
],
museca: ["1", "1.5"],
maimai: [
"maimai",
"maimaiplus",
"green",
"greenplus",
"orange",
"orangeplus",
"pink",
"pinkplus",
"murasaki",
"murasakiplus",
"milk",
"milkplus",
"finale",
],
jubeat: [
"jubeat",
"ripples",
"knit",
"copious",
"saucer",
"saucerfulfill",
"prop",
"qubell",
"clan",
"festo",
],
popn: [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"park",
"lapis",
"eclale",
"usaneko",
"peace",
],
sdvx: ["booth", "inf", "gw", "heaven", "vivid"],
ddr: [
"1",
"2",
"3",
"4",
"5",
"max",
"max2",
"extreme",
"snova",
"snova2",
"x",
"x2",
"x3",
"2013",
"2014",
"a",
"a20",
],
bms: ["0"],
chunithm: [
"chuni",
"chuniplus",
"air",
"airplus",
"star",
"starplus",
"amazon",
"amazonplus",
"crystal",
"crystalplus",
],
gitadora: [
"gf1",
"gf2dm1",
"gf3dm2",
"gf4dm3",
"gf5dm4",
"gf6dm5",
"gf7dm6",
"gf8dm7",
"gf8dm7plus",
"gf9dm8",
"gf10dm9",
"gf11dm10",
"v",
"v2",
"v3",
"v4",
"v5",
"v6",
"v7",
"v8",
"xg",
"xg2",
"xg3",
"gitadora",
"overdrive",
"triboost",
"triboostplus",
"matixx",
"exchain",
"nextage",
],
usc: ["0"],
};
function humaniseGame<T extends Game>(game: T, pt?: Playtypes[T]): string {
if (!pt) {
return gameHuman[game];
}
if (validPlaytypes[game].length === 1) {
return gameHuman[game];
}
return `${gameHuman[game]} (${pt})`;
}
const ratingParameters = {
iidx: {
failHarshnessMultiplier: 0.3,
pivotPercent: 0.7777, // Grade: AA
clearExpMultiplier: 1,
},
bms: {
failHarshnessMultiplier: 0.5,
pivotPercent: 0.7777, // Grade: AA
clearExpMultiplier: 0.75,
},
museca: {
failHarshnessMultiplier: 1,
pivotPercent: 0.8, // grade: not fail
clearExpMultiplier: 1, // no real reason
},
popn: {
failHarshnessMultiplier: 1,
pivotPercent: 0.8, // grade: A
clearExpMultiplier: 0.4, // no real reason
},
maimai: {
failHarshnessMultiplier: 1,
pivotPercent: 0.8,
clearExpMultiplier: 1,
},
jubeat: {
failHarshnessMultiplier: 0.9,
pivotPercent: 0.7, // grade: A (clear)
clearExpMultiplier: 1,
},
sdvx: {
failHarshnessMultiplier: 1,
pivotPercent: 0.92,
clearExpMultiplier: 1.45, // testing
},
usc: {
failHarshnessMultiplier: 1,
pivotPercent: 0.92,
clearExpMultiplier: 1.45, // testing
},
ddr: {
failHarshnessMultiplier: 0.9,
pivotPercent: 0.9,
clearExpMultiplier: 1,
},
chunithm: {
failHarshnessMultiplier: 1,
pivotPercent: 0.975,
clearExpMultiplier: 1.1,
},
gitadora: {
// GENERIC PARAMS. THIS IS **NOT** THE USED CALC.
failHarshnessMultiplier: 1,
pivotPercent: 0.8,
clearExpMultiplier: 1.1,
},
};
function ChangeAlpha(string: string, alpha: string): string {
const spl = string.split(",");
spl[spl.length - 1] = `${alpha})`;
return spl.join(",");
}
function DirectScoreGradeDelta(
game: Game,
score: number,
percent: number,
chart: ChartDocument,
delta: number
): SGDReturn | null {
const grade = GetGrade(game, percent);
if (!grade) {
throw new Error(`Invalid grade created from ${game}, ${percent}`);
}
const scoreObj: PartialScore = {
scoreData: {
score,
grade,
},
};
return ScoreGradeDelta(game, scoreObj, chart, delta);
}
interface SGDReturn {
grade: string;
delta: number;
formattedString: string;
}
interface PartialScore {
scoreData: {
score: number;
grade: string;
};
}
function ScoreGradeDelta(
game: Game,
score: PartialScore,
chart: ChartDocument,
delta: number
): SGDReturn | null {
const nextGrade = grades[game][grades[game].indexOf(score.scoreData.grade) + delta];
if (nextGrade) {
const nextGradePercent = gradeBoundaries[game][grades[game].indexOf(nextGrade)];
const nGScore = CalculateScore(game, nextGradePercent, chart);
if (nGScore) {
const delta = score.scoreData.score - nGScore;
let formattedString = `(${nextGrade})`;
if (Number.isInteger(delta)) {
formattedString += delta >= 0 ? `+${delta}` : `${delta}`;
} else {
formattedString += delta >= 0 ? `+${delta.toFixed(2)}` : `${delta.toFixed(2)}`;
}
return {
grade: nextGrade,
delta: delta,
formattedString: formattedString,
};
} else {
return null;
}
} else {
return null;
}
}
function AbsoluteScoreGradeDelta(
game: Game,
score: number,
percent: number,
absDelta: number
): SGDReturn | null {
const grade = grades[game][absDelta];
if (grade) {
let chart = null;
if (game === "iidx" || game === "bms") {
const reversedNC = Math.floor((score / percent) * 100) / 2;
chart = {
data: {
notecount: reversedNC,
},
} as unknown; // heheh
}
const sc = CalculateScore(game, gradeBoundaries[game][absDelta], chart as ChartDocument);
if (sc) {
const delta = score - sc;
let formattedString = `(${grade})`;
formattedString += delta >= 0 ? `+${delta}` : `${delta}`;
return {
grade: grade,
delta: delta,
formattedString: formattedString,
};
} else {
return null;
}
} else {
return null;
}
}
function CalculateScore(game: Game, percent: number, chart: ChartDocument): number | null {
let score = percent;
if (game === "iidx" || game === "bms") {
score = (chart as ChartDocument<"iidx:SP">).data.notecount * 2 * (percent / 100);
} else if (game === "ddr" || game === "museca" || game === "jubeat" || game === "chunithm") {
score = 1_000_000 * (percent / 100);
} else if (game === "popn") {
score = 100_000 * (percent / 100);
} else if (game === "sdvx" || game === "usc") {
score = 10_000_000 * (percent / 100);
}
if (score) {
return score;
}
return null;
}
function PercentToScore(percent: number, game: Game, chartData: ChartDocument): number {
let eScore = 0;
if (game === "iidx" || game === "bms") {
eScore = percent * (chartData as ChartDocument<"iidx:SP">).data.notecount * 2;
} else if (game === "museca" || game === "jubeat") {
eScore = percent * 1000000;
} else if (game === "popn") {
eScore = percent * 100000;
} else if (game === "sdvx" || game === "usc") {
eScore = percent * 10000000;
} else if (game === "ddr") {
// todo
} else if (game === "chunithm") {
eScore = percent * 1000000;
} else if (game === "gitadora") {
eScore = percent;
}
return eScore;
}
+6 -4
View File
@@ -1,13 +1,13 @@
import t from "tap";
import { GetGamePTConfig } from "../config/config";
import { CalculateESD, ESDCompare, PercentCompare } from "./esd";
import { GetGamePTConfig } from "../config/config";
import t from "tap";
function approx(t: Tap.Test, given: number, recieved: number, acc = 0.01) {
if (Math.abs(given - recieved) < acc) {
return t.pass();
} else {
return t.fail(`Expected ${given} to be within ${acc} of ${recieved}.`);
}
return t.fail(`Expected ${given} to be within ${acc} of ${recieved}.`);
}
const gptConfig = GetGamePTConfig("iidx", "SP");
@@ -20,6 +20,7 @@ const judgementWindows = gptConfig.judgementWindows;
t.test("#CalculateESD", (t) => {
const jdg = judgementWindows;
approx(t, CalculateESD(jdg, 0.8), 17.58);
approx(t, CalculateESD(jdg, 0), 200);
t.throws(() => CalculateESD(jdg, 0, true));
@@ -37,6 +38,7 @@ t.test("#ESDCompare", (t) => {
t.test("#PercentCompare", (t) => {
const jdg = judgementWindows;
approx(t, PercentCompare(jdg, 0.99, 0.98), -5.53);
approx(t, PercentCompare(jdg, 0.99, 0.98), PercentCompare(jdg, 0.99, 0.98, 1));
+17 -12
View File
@@ -13,7 +13,8 @@ function cdf(x: number, mean: number, variance: number) {
function erf(x: number) {
// save the sign of x
const sign = x >= 0 ? 1 : -1;
x = Math.abs(x);
const absX = Math.abs(x);
// constants
const a1 = 0.254829592;
@@ -24,8 +25,9 @@ function erf(x: number) {
const p = 0.3275911;
// A&S formula 7.1.26
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
const t = 1.0 / (1.0 + p * absX);
const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-absX * absX);
return sign * y; // erf(-x) = -erf(x);
}
@@ -46,21 +48,21 @@ const MEAN = 0;
* @returns The percent this std. deviation would produce.
*/
function StdDeviationToPercent(
judgements: ESDJudgementFormat[],
judgements: Array<ESDJudgementFormat>,
stddev: number,
largestValue: number
) {
let lastJudgeMSBorder = 0;
let prbSum = 0;
for (let i = 0; i < judgements.length; i++) {
const judge = judgements[i];
for (const judge of judgements) {
const nVal = CDFBetween(lastJudgeMSBorder, judge.msBorder, MEAN, stddev ** 2) * judge.value;
lastJudgeMSBorder = judge.msBorder;
prbSum += nVal;
prbSum = prbSum + nVal;
}
prbSum /= largestValue;
prbSum = prbSum / largestValue;
return prbSum;
}
@@ -83,7 +85,7 @@ export interface ESDJudgementFormat {
* @returns
*/
export function CalculateESD(
judgements: ESDJudgementFormat[],
judgements: Array<ESDJudgementFormat>,
percent: number,
errOnInaccuracy = false
): number {
@@ -92,6 +94,7 @@ export function CalculateESD(
"(ESD) Invalid percent. Percent must be between 0 and 1, and also a number."
);
}
const largestValue = judgements.slice(0).sort((a, b) => b.value - a.value)[0].value;
// massive optimisation possible here by using better initial estimates with precalc'd table of values.
@@ -128,7 +131,7 @@ export function CalculateESD(
}
if (errOnInaccuracy) {
throw `(ESD-JS) Did not reach value within MAX_ITERATIONS (${MAX_ITERATIONS})`;
throw new Error(`(ESD-JS) Did not reach value within MAX_ITERATIONS (${MAX_ITERATIONS})`);
}
return estSD;
@@ -148,6 +151,7 @@ export function ESDCompare(baseESD: number, compareESD: number, cdeg = 1): numbe
let inv = false;
let variance;
let bound;
if (compareESD > baseESD) {
inv = true;
variance = compareESD ** 2;
@@ -162,7 +166,7 @@ export function ESDCompare(baseESD: number, compareESD: number, cdeg = 1): numbe
let besdc = BASE_CASE - esdc;
if (inv) {
besdc *= -1;
besdc = besdc * -1;
}
return besdc * 100;
@@ -172,12 +176,13 @@ export function ESDCompare(baseESD: number, compareESD: number, cdeg = 1): numbe
* Converts two percents to ESD, then runs ESDCompare.
*/
export function PercentCompare(
judgements: ESDJudgementFormat[],
judgements: Array<ESDJudgementFormat>,
baseP: number,
compareP: number,
cdeg = 1
): number {
const e1 = CalculateESD(judgements, baseP);
const e2 = CalculateESD(judgements, compareP);
return ESDCompare(e1, e2, cdeg);
}
+71 -70
View File
@@ -2,15 +2,18 @@
// their name in the database to the object they schemaify.
// The schemas themselves are wrapped in functions that throw on error.
import p, {
import { allIDStrings, allImportTypes, allSupportedGames } from "../config/static-config";
import { ALL_PERMISSIONS } from "../constants/permissions";
import { GetGameConfig, GetGamePTConfig } from "../index";
import { UserAuthLevels } from "../types";
import p from "prudence";
import type { GamePTConfig } from "../index";
import type { Game, IDStrings, NotificationBody, Playtypes } from "../types";
import type {
PrudenceSchema,
ValidationFunctionParentOptionsKeychain,
ValidSchemaValue,
} from "prudence";
import { allIDStrings, allImportTypes, allSupportedGames } from "../config/static-config";
import { ALL_PERMISSIONS } from "../constants/permissions";
import { GamePTConfig, GetGameConfig, GetGamePTConfig } from "../index";
import { Game, IDStrings, NotificationBody, Playtypes, UserAuthLevels } from "../types";
export const optNull = (v: ValidSchemaValue): ValidationFunctionParentOptionsKeychain =>
p.optional(p.nullable(v));
@@ -33,7 +36,7 @@ function prSchemaFnWrap(schema: PrudenceSchema) {
};
}
const PR_GameStats = (game: Game, playtype: Playtypes[Game], gptConfig: GamePTConfig) => ({
const PR_GAME_STATS = (game: Game, playtype: Playtypes[Game], gptConfig: GamePTConfig) => ({
userID: p.isPositiveNonZeroInteger,
game: p.is(game),
playtype: p.is(playtype),
@@ -43,7 +46,7 @@ const PR_GameStats = (game: Game, playtype: Playtypes[Game], gptConfig: GamePTCo
),
});
const PR_GoalInfo = {
const PR_GOAL_INFO = {
progress: "?number",
progressHuman: "string",
outOf: "number",
@@ -98,7 +101,8 @@ const extractGPTIDString = (self: unknown) => {
throw new Error(`Expected a string where self.idString is. Got ${s.idString}`);
}
const [game, playtype] = s.idString.split(":");
// if there's no ":" in the string, this returns only one element.
const [game, playtype] = s.idString.split(":") as [string, string | undefined];
if (!IsValidGame(game)) {
throw new Error(`Expected valid game -- got ${game} from idString ${s.idString}.`);
@@ -138,12 +142,12 @@ const extractGPT = (self: unknown) => {
return { game: s.game, playtype: s.playtype };
};
const PR_Random = p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR");
const PR_RANDOM = p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR");
const GetScoreMeta = (game: Game, playtype: Playtypes[Game]): PrudenceSchema => {
if (game === "iidx" && playtype === "SP") {
return {
random: optNull(PR_Random),
random: optNull(PR_RANDOM),
assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")),
range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")),
gauge: optNull(p.isIn("ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX-HARD")),
@@ -151,8 +155,8 @@ const GetScoreMeta = (game: Game, playtype: Playtypes[Game]): PrudenceSchema =>
} else if (game === "iidx" && playtype === "DP") {
return {
random: optNull({
left: PR_Random,
right: PR_Random,
left: PR_RANDOM,
right: PR_RANDOM,
}),
assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")),
range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")),
@@ -167,15 +171,15 @@ const GetScoreMeta = (game: Game, playtype: Playtypes[Game]): PrudenceSchema =>
};
} else if (game === "bms" && playtype === "7K") {
return {
random: optNull(PR_Random),
random: optNull(PR_RANDOM),
inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")),
client: optNull(p.isIn("LR2", "lr2oraja")),
};
} else if (game === "bms" && playtype === "14K") {
return {
random: optNull({
left: PR_Random,
right: PR_Random,
left: PR_RANDOM,
right: PR_RANDOM,
}),
inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")),
client: optNull(p.isIn("LR2", "lr2oraja")),
@@ -194,7 +198,7 @@ function IsValidGame(str: string): str is Game {
}
const getPlaytype = (game: Game, self: unknown): Playtypes[Game] => {
if (!self || typeof self !== "object") {
if (self === null || typeof self !== "object") {
throw new Error("Expected an object.");
}
@@ -203,6 +207,7 @@ const getPlaytype = (game: Game, self: unknown): Playtypes[Game] => {
const s = self as Record<string, unknown>;
const playtype = s.playtype as string;
if (!IsValidPlaytype(game, playtype)) {
throw new Error(`Expected any of ${gameConfig.validPlaytypes.join(", ")}`);
}
@@ -213,7 +218,7 @@ const getPlaytype = (game: Game, self: unknown): Playtypes[Game] => {
const games = allSupportedGames;
const isValidPlaytype = (self: unknown, parent: Record<string, unknown>) => {
if (!parent.game || typeof parent.game !== "string" || !IsValidGame(parent.game)) {
if (typeof parent.game !== "string" || !IsValidGame(parent.game)) {
throw new Error(`Invalid Schema, need game to base IsValidPlaytype off of.`);
}
@@ -416,7 +421,7 @@ function GetChartDataForGPT(idString: IDStrings): PrudenceSchema {
}
}
const PR_SongDocument = (game: Game): PrudenceSchema => {
const PR_SONG_DOCUMENT = (game: Game): PrudenceSchema => {
const data = GetSongDataForGame(game);
return {
@@ -429,7 +434,7 @@ const PR_SongDocument = (game: Game): PrudenceSchema => {
};
};
const PR_ChartDocument = (game: Game) => (self: unknown) => {
const PR_CHART_DOCUMENT = (game: Game) => (self: unknown) => {
const playtype = getPlaytype(game, self);
const gptConfig = GetGamePTConfig(game, playtype);
@@ -459,14 +464,14 @@ const PR_ChartDocument = (game: Game) => (self: unknown) => {
// Returns true on success, throws on failure.
export type SchemaValidatorFunction = (self: unknown) => true;
type GameSchemas = Record<`${"songs" | "charts"}-${Game}`, SchemaValidatorFunction>;
type GameSchemas = Record<`${"charts" | "songs"}-${Game}`, SchemaValidatorFunction>;
function CreateGameSchemas() {
const obj: Partial<GameSchemas> = {};
for (const game of games) {
obj[`songs-${game}`] = prSchemaFnWrap(PR_SongDocument(game));
obj[`charts-${game}`] = PR_ChartDocument(game);
obj[`songs-${game}`] = prSchemaFnWrap(PR_SONG_DOCUMENT(game));
obj[`charts-${game}`] = PR_CHART_DOCUMENT(game);
}
// guaranteed to no longer be partial
@@ -486,6 +491,7 @@ const GAME_SCHEMAS = CreateGameSchemas();
const PRE_SCHEMAS = {
"bms-course-lookup": prSchemaFnWrap({
title: "string",
// Must be comprised of 4 md5 hashes, which are 32 chars long.
md5sums: (self) => {
if (typeof self !== "string") {
@@ -496,7 +502,7 @@ const PRE_SCHEMAS = {
return "Expected 32 * 4 characters (4 md5 hashes long).";
}
if (!self.match(/^[a-z0-9]*$/u)) {
if (!/^[a-z0-9]*$/u.exec(self)) {
return "Expected all chars to be in the range of a-z0-9.";
}
@@ -516,9 +522,9 @@ const PRE_SCHEMAS = {
type: p.isIn("songs", "charts", "static"),
data: (self, parent) => {
if (parent.type === "songs") {
return true; //temp. should be a song.
return true; // temp. should be a song.
} else if (parent.type === "charts") {
return true; //temp. should be a chart.
return true; // temp. should be a chart.
}
return (
@@ -652,14 +658,12 @@ const PRE_SCHEMAS = {
const gptConfig = GetGamePTConfig(game, playtype);
const hitMeta = Object.assign(
{
fast: optNull(p.isPositiveInteger),
slow: optNull(p.isPositiveInteger),
maxCombo: optNull(p.isPositiveInteger),
},
GetHitMeta(game)
);
const hitMeta = {
fast: optNull(p.isPositiveInteger),
slow: optNull(p.isPositiveInteger),
maxCombo: optNull(p.isPositiveInteger),
...GetHitMeta(game),
};
const scoreMeta = GetScoreMeta(game, playtype);
@@ -701,14 +705,12 @@ const PRE_SCHEMAS = {
const gptConfig = GetGamePTConfig(game, playtype);
const hitMeta = Object.assign(
{
fast: optNull(p.isPositiveInteger),
slow: optNull(p.isPositiveInteger),
maxCombo: optNull(p.isPositiveInteger),
},
GetHitMeta(game)
);
const hitMeta = {
fast: optNull(p.isPositiveInteger),
slow: optNull(p.isPositiveInteger),
maxCombo: optNull(p.isPositiveInteger),
...GetHitMeta(game),
};
return prSchemaFnWrap({
composedFrom: {
@@ -803,6 +805,7 @@ const PRE_SCHEMAS = {
importID: "string",
scoreIDs: ["string"],
game: p.isIn(games),
// @ts-expect-error We've asserted this is definitely a game.
playtypes: [(self) => p.isIn(GetGameConfig(self.game).validPlaytypes)(self)],
errors: [
@@ -830,8 +833,8 @@ const PRE_SCHEMAS = {
goalInfo: [
{
goalID: "string",
old: PR_GoalInfo,
new: PR_GoalInfo,
old: PR_GOAL_INFO,
new: PR_GOAL_INFO,
},
],
milestoneInfo: [
@@ -857,7 +860,7 @@ const PRE_SCHEMAS = {
const gptConfig = GetGamePTConfig(game, playtype);
return prSchemaFnWrap(PR_GameStats(game, playtype, gptConfig))(self);
return prSchemaFnWrap(PR_GAME_STATS(game, playtype, gptConfig))(self);
},
"game-stats-snapshots": (self: unknown) => {
const { game, playtype } = extractGPT(self);
@@ -865,7 +868,7 @@ const PRE_SCHEMAS = {
const gptConfig = GetGamePTConfig(game, playtype);
return prSchemaFnWrap(
Object.assign(PR_GameStats(game, playtype, gptConfig), {
Object.assign(PR_GAME_STATS(game, playtype, gptConfig), {
rankings: Object.fromEntries(
gptConfig.profileRatingAlgs.map((e) => [
e,
@@ -890,6 +893,7 @@ const PRE_SCHEMAS = {
preferredScoreAlg: p.nullable(p.isIn(gptConfig.scoreRatingAlgs)),
preferredSessionAlg: p.nullable(p.isIn(gptConfig.sessionRatingAlgs)),
preferredProfileAlg: p.nullable(p.isIn(gptConfig.profileRatingAlgs)),
// ouch
stats: p.and(
[
@@ -991,7 +995,7 @@ const PRE_SCHEMAS = {
// Typescript moment...
const s = self as Record<string, unknown>;
for (const key in ALL_PERMISSIONS) {
for (const key of Object.keys(ALL_PERMISSIONS)) {
if (s[key] !== undefined && typeof s[key] !== "boolean") {
return `Invalid permission value of ${s[key]} at ${key}.`;
}
@@ -1016,8 +1020,8 @@ const PRE_SCHEMAS = {
return prSchemaFnWrap({
idString: p.isIn(allIDStrings),
chartDoc: PR_ChartDocument(game),
songDoc: PR_SongDocument(game),
chartDoc: PR_CHART_DOCUMENT(game),
songDoc: PR_SONG_DOCUMENT(game),
userIDs: [p.isPositiveNonZeroInteger],
})(self);
},
@@ -1050,16 +1054,9 @@ const PRE_SCHEMAS = {
["import", "importParse", "session", "pb"].map((k) => [k, p.isPositive])
),
abs: Object.fromEntries(
[
"parse",
"import",
"importParse",
"session",
"pb",
"ugs",
"goal",
"milestone",
].map((k) => [k, p.isPositive])
["parse", "import", "importParse", "session", "pb", "ugs", "goal", "milestone"].map(
(k) => [k, p.isPositive]
)
),
}),
"arc-saved-profiles": prSchemaFnWrap({
@@ -1073,14 +1070,14 @@ const PRE_SCHEMAS = {
createdAt: p.isPositive,
consumed: "boolean",
consumedBy: (self, parent) => {
if (parent.consumed) {
if (parent.consumed === true) {
return self === null;
}
return p.isPositiveInteger(self);
},
consumedAt: (self, parent) => {
if (parent.consumed) {
if (parent.consumed === true) {
return self === null;
}
@@ -1100,16 +1097,20 @@ const PRE_SCHEMAS = {
let subSchema = {};
if (type === "RIVALED_BY") {
subSchema = {
userID: p.isPositiveNonZeroInteger,
game: p.isIn(games),
playtype: isValidPlaytype,
};
} else if (type === "MILESTONE_CHANGED") {
subSchema = {
milestoneID: "string",
};
switch (type) {
case "RIVALED_BY": {
subSchema = {
userID: p.isPositiveNonZeroInteger,
game: p.isIn(games),
playtype: isValidPlaytype,
};
break;
}
case "MILESTONE_CHANGED":
subSchema = {
milestoneID: "string",
};
}
const err = p(self, subSchema);
+303 -295
View File
File diff suppressed because it is too large Load Diff
+14 -17
View File
@@ -1,6 +1,6 @@
import { ChartDocument, SongDocument, Game, IDStrings, Playtypes } from "..";
import { GetGameConfig, GetGamePTConfig } from "../config/config";
import { Grades, integer } from "../types";
import type { ChartDocument, SongDocument, Game, IDStrings, Playtypes } from "..";
import type { Grades, integer } from "../types";
export function FormatInt(v: number): string {
return v.toString();
@@ -9,6 +9,7 @@ export function FormatInt(v: number): string {
export function FormatDifficulty(chart: ChartDocument, game: Game): string {
if (game === "bms" || game === "pms") {
const bmsChart = chart as ChartDocument<"bms:7K" | "bms:14K">;
return (
bmsChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ") || "Unrated"
);
@@ -70,11 +71,7 @@ export function FormatGame(game: Game, playtype: Playtypes[Game]): string {
return `${gameConfig.name} (${playtype})`;
}
export function FormatChart(
game: Game,
song: SongDocument,
chart: ChartDocument<IDStrings>
): string {
export function FormatChart(game: Game, song: SongDocument, chart: ChartDocument): string {
if (game === "bms") {
const tables = (chart as ChartDocument<"bms:7K" | "bms:14K">).data.tableFolders;
@@ -83,11 +80,11 @@ export function FormatChart(
let realTitle = bmsSong.title;
if (bmsSong.data.subtitle) {
realTitle += ` - ${bmsSong.data.subtitle}`;
realTitle = `${realTitle} - ${bmsSong.data.subtitle}`;
}
if (bmsSong.data.genre) {
realTitle += ` [${bmsSong.data.genre}]`;
realTitle = `${realTitle} [${bmsSong.data.genre}]`;
}
if (tables.length === 0) {
@@ -96,24 +93,22 @@ export function FormatChart(
return `${realTitle} (${tables.map((e) => `${e.table}${e.level}`).join(", ")})`;
} else if (game === "usc") {
const uscChart = chart as ChartDocument<"usc:Keyboard" | "usc:Controller">;
const tables = Object.entries(uscChart.data.tableFolders);
const uscChart = chart as ChartDocument<"usc:Controller" | "usc:Keyboard">;
// If this chart isn't an official, render it differently
if (!uscChart.data.isOfficial) {
// Same as BMS. turn this into SongTitle (Keyboard MXM normal1, insane2)
return `${song.title} (${chart.playtype} ${chart.difficulty} ${tables
.map((e) => `${e[0]}${e[1]}`)
.join(", ")})`;
} else if (uscChart.data.isOfficial && tables.length !== 0) {
return `${song.title} (${chart.playtype} ${
chart.difficulty
} ${uscChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ")})`;
} else if (uscChart.data.tableFolders.length !== 0) {
// if this chart is an official **AND** is on tables (unlikely), render
// it as so:
// SongTitle (Keyboard MXM 17, normal1, insane2)
return `${song.title} (${chart.playtype} ${chart.difficulty} ${
chart.level
}, ${tables.map((e) => `${e[0]}${e[1]}`).join(", ")})`;
}, ${uscChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ")})`;
}
// otherwise, it's just an official and should be rendered like any other game.
@@ -231,6 +226,7 @@ export function GenericFormatGradeDelta<I extends IDStrings = IDStrings>(
export function FormatSieglindeBMS(sgl: number): string {
const fixedSgl = sgl.toFixed(2);
if (sgl < 13) {
return `${fixedSgl} (☆${fixedSgl})`;
}
@@ -240,6 +236,7 @@ export function FormatSieglindeBMS(sgl: number): string {
export function FormatSieglindePMS(sgl: number): string {
const fixedSgl = sgl.toFixed(2);
if (sgl < 13) {
return `${fixedSgl} (○${fixedSgl})`;
}
+5 -5
View File
@@ -1,6 +1,6 @@
import { Game, Playtypes } from ".";
import { GameClassSets } from "./game-classes";
import { GoalImportStat, IDStrings, integer, MilestoneImportStat } from "./types";
import type { Game, Playtypes } from ".";
import type { GameClassSets } from "./game-classes";
import type { GoalImportStat, IDStrings, integer, MilestoneImportStat } from "./types";
/**
* An event fired when a users class improves.
@@ -25,12 +25,12 @@ export interface WebhookEventGoalAchievedV1 {
content: {
userID: integer;
game: Game;
goals: {
goals: Array<{
goalID: string;
old: GoalImportStat;
new: GoalImportStat;
playtype: Playtypes[Game];
}[];
}>;
};
}
+2 -2
View File
@@ -16,10 +16,10 @@
"sourceMap": true
},
"include": [
"src/**/*.ts"
"src/**/*.ts",
"src/**/*.test.ts"
],
"exclude": [
"src/**/*.test.ts",
"src/legacy/**/*.ts"
]
}