feat: add minimumRelevantValue and minimumScores to classes (#1348)

* fix(bot): don't spam ongeki classes

* feat(bot): fix discord spam for good

* fix: missed a few values

* fix: update wacca

* fix: update ddr

* fix: remove bms, pms, iidx

* fix: add maimaidx
This commit is contained in:
nairobi
2025-08-22 12:55:56 +01:00
committed by GitHub
parent 5c3c6ca34e
commit 9aa4527668
13 changed files with 67 additions and 40 deletions
+40 -37
View File
@@ -5,7 +5,7 @@ import { CreateEmbed } from "../utils/embeds";
import { PrependTachiUrl } from "../utils/fetchTachi";
import logger from "../utils/logger";
import { FormatClass, GetGameChannel } from "../utils/misc";
import { FormatGame } from "tachi-common";
import { FormatGame, GetGamePTConfig } from "tachi-common";
import type {
Classes,
GPTString,
@@ -32,15 +32,14 @@ export async function HandleClassUpdateV1(
}
if (!ShouldRenderUpdate(game, playtype, event.set, event.new)) {
logger.info(
`Not rendering class update ${event.set}: ${event.old} -> ${event.new} (not relevant).`
);
return 204;
}
const userDoc = await GetUserInfo(event.userID);
// We don't want to render classes if the user is blitzing through them
// because they haven't played enough charts to "average out".
// For example, a new player playing their first 50 charts will blitz through
// atleast 10 of the volforce ranks, which will just result in channel spam.
const minimumNecessaryScores = GetMinimumScores(game, playtype, event.set);
if (minimumNecessaryScores !== null) {
@@ -48,7 +47,9 @@ export async function HandleClassUpdateV1(
// Do not render if the user hasn't hit the score cap.
if (totalScores < minimumNecessaryScores) {
logger.info(`Not rendering class update ${event.set}: ${event.old} -> ${event.new}.`);
logger.info(
`Not rendering class update ${event.set}: ${event.old} -> ${event.new} (not enough scores).`
);
return 204;
}
}
@@ -82,16 +83,34 @@ function ShouldRenderUpdate(
classSet: Classes[GPTString],
classValue: string
) {
if (game === "sdvx" && classSet === "vfClass") {
return ["IMPERIAL_I", "IMPERIAL_II", "IMPERIAL_III", "IMPERIAL_IV"].includes(classValue);
} else if (game === "popn" && classSet === "class") {
return [
// All of the other classes in pop'n can be trivially blitzed through.
"GOD",
].includes(classValue);
const config = GetGamePTConfig(game, playtype);
const classSpec = config.classes[classSet];
if (classSpec === undefined) {
logger.error(`Invalid class ${classSet} for ${game} ${playtype}`);
return false;
}
return true;
if (classSpec.minimumRelevantValue === undefined) {
return true;
}
const ids = classSpec.values.map((c) => c.id);
const currentId = ids.indexOf(classValue);
const minimumId = ids.indexOf(classSpec.minimumRelevantValue);
if (currentId < 0) {
logger.error(`Invalid classValue ${classValue} for ${game} ${playtype}`);
return false;
}
if (minimumId < 0) {
logger.error(`Invalid minimum classValue ${classValue} for ${game} ${playtype}`);
return false;
}
return currentId >= minimumId;
}
function GetMinimumScores(
@@ -99,29 +118,13 @@ function GetMinimumScores(
playtype: Playtype,
classSet: Classes[GPTString]
): integer | null {
if (game === "chunithm" && classSet === "colour") {
return 50;
} else if (game === "sdvx" && classSet === "vfClass") {
return 50;
} else if (game === "gitadora") {
return 50;
} else if (game === "jubeat") {
return 60;
} else if (game === "wacca") {
return 50;
} else if (game === "bms") {
return 20;
} else if (game === "iidx") {
return 20;
} else if (game === "pms") {
return 20;
} else if (game === "ddr") {
return 90;
} else if (game === "maimai" && classSet === "colour") {
return 30;
} else if (game === "maimaidx" && classSet === "colour") {
return 50;
const config = GetGamePTConfig(game, playtype);
const classSpec = config.classes[classSet];
if (classSpec === undefined) {
logger.error(`Invalid class ${classSet} for ${game} ${playtype}`);
return null;
}
return null;
return classSpec.minimumScores ?? null;
}
@@ -151,6 +151,7 @@ export const CHUNITHM_SINGLE_CONF = {
colour: {
type: "DERIVED",
values: CHUNITHMColours,
minimumScores: 50,
},
dan: {
+1
View File
@@ -186,6 +186,7 @@ export const DDR_SP_CONF = {
flare: {
type: "DERIVED",
values: DDRFlare,
minimumRelevantValue: "URANUS",
},
},
+1 -1
View File
@@ -106,7 +106,7 @@ export const GITADORA_GITA_CONF = {
},
classes: {
colour: { type: "DERIVED", values: GitadoraColours },
colour: { type: "DERIVED", values: GitadoraColours, minimumScores: 50 },
},
orderedJudgements: ["perfect", "great", "good", "ok", "miss"],
+1 -1
View File
@@ -107,7 +107,7 @@ export const JUBEAT_SINGLE_CONF = {
},
classes: {
colour: { type: "DERIVED", values: JubeatColours },
colour: { type: "DERIVED", values: JubeatColours, minimumScores: 60 },
},
orderedJudgements: ["perfect", "great", "good", "poor", "miss"],
@@ -204,6 +204,7 @@ export const MAIMAI_DX_SINGLE_CONF = {
colour: {
type: "DERIVED",
values: MaimaiDXColours,
minimumRelevantValue: "BRONZE",
},
dan: {
type: "PROVIDED",
+1
View File
@@ -143,6 +143,7 @@ export const MAIMAI_SINGLE_CONF = {
colour: {
type: "DERIVED",
values: MaimaiColours,
minimumScores: 30,
},
dan: {
type: "PROVIDED",
+1
View File
@@ -195,6 +195,7 @@ export const ONGEKI_SINGLE_CONF = {
colour: {
type: "DERIVED",
values: OngekiColours,
minimumRelevantValue: "RAINBOW",
},
},
+1
View File
@@ -129,6 +129,7 @@ export const POPN_9B_CONF = {
class: {
type: "DERIVED",
values: PopnClasses,
minimumRelevantValue: "GOD",
},
},
+1
View File
@@ -168,6 +168,7 @@ export const SDVX_SINGLE_CONF = {
vfClass: {
type: "DERIVED",
values: SDVXVFClasses,
minimumRelevantValue: "IMPERIAL_I",
},
},
+1
View File
@@ -93,6 +93,7 @@ export const USC_CONTROLLER_CONF = {
vfClass: {
type: "DERIVED",
values: SDVXVFClasses,
minimumRelevantValue: "IMPERIAL_I",
},
},
+1
View File
@@ -128,6 +128,7 @@ export const WACCA_SINGLE_CONF = {
colour: {
type: "DERIVED",
values: WaccaColours,
minimumRelevantValue: "RAINBOW",
},
},
+16 -1
View File
@@ -33,9 +33,24 @@ interface BaseClassConfig<V extends string> {
/**
* What are the possible values for this class field?
*
* @note This should be in ascending order.
* @note This must be in ascending order.
*/
values: Array<ClassInfo<V>>;
/**
* The ID of the lowest class value that should trigger a discord message.
* Lower values will be ignored to reduce channel spam.
*/
minimumRelevantValue?: V;
/**
* What number of scores is needed to "fill" this class?
* If the number of scores is lower, new class values will not
* trigger discord messages (useful if minimumRelevantValue is undesired)
* For example, a new SDVX player playing their first 50 charts will blitz through
* at least 10 of the volforce ranks, which would just result in channel spam.
*/
minimumScores?: number;
}
/**