diff --git a/bot/package.json b/bot/package.json index 3af455c1b..57c2de22e 100644 --- a/bot/package.json +++ b/bot/package.json @@ -44,7 +44,7 @@ "node-fetch": "2.6.7", "prudence": "0.9.6", "sync-fetch": "^0.3.1", - "tachi-common": "0.7.1", + "tachi-common": "0.7.2", "ts-node": "10.2.0" } } \ No newline at end of file diff --git a/bot/pnpm-lock.yaml b/bot/pnpm-lock.yaml index 3098b6e59..27dba56b4 100644 --- a/bot/pnpm-lock.yaml +++ b/bot/pnpm-lock.yaml @@ -33,7 +33,7 @@ specifiers: prettier: 2.6.0 prudence: 0.9.6 sync-fetch: ^0.3.1 - tachi-common: 0.7.1 + tachi-common: 0.7.2 ts-node: 10.2.0 typescript: 4.6.2 @@ -56,7 +56,7 @@ dependencies: node-fetch: 2.6.7 prudence: 0.9.6 sync-fetch: 0.3.1 - tachi-common: 0.7.1 + tachi-common: 0.7.2 ts-node: 10.2.0_11c6e3e3478c740255e54b205a394e0f devDependencies: @@ -2347,8 +2347,8 @@ packages: - encoding dev: false - /tachi-common/0.7.1: - resolution: {integrity: sha512-M3J5SCNDfodqDYJoPi61nSF34AFlUfIoSMec1F0C2jJXpZeIxssf6yA6h2ZW8m2KJ6wWEKBrLaEtVUoiSgA/fw==} + /tachi-common/0.7.2: + resolution: {integrity: sha512-VBCrWWtQRa2FcVEcc7JNKBS7g9ZX0+wYbcCJKGsuSsPGWrv9wRVLwxc5Oo4xKEs12Lm93t7e2ywEvLV7mmEfCQ==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/bot/src/utils/embeds.ts b/bot/src/utils/embeds.ts index 9bb60ae97..6dd47f77c 100644 --- a/bot/src/utils/embeds.ts +++ b/bot/src/utils/embeds.ts @@ -16,7 +16,9 @@ import { Entries, FormatDate, FormatProfileRating, + FormatScoreData, FormatScoreRating, + GetChartPertinentInfo, MillisToSince, Pluralise, UppercaseFirst, @@ -124,17 +126,15 @@ export async function CreateChartScoresEmbed( .setURL(CreateChartLink(chart, game)); if (pb === null) { - embed.setDescription("You have not played this chart."); + embed.setDescription(`${userDoc.username} has not played this chart.`); } else { + const { scoreStr, lampStr } = FormatScoreData(pb); + + const pertinentInfo = GetChartPertinentInfo(game, chart); + embed - .addField( - "Score", - `${pb.scoreData.score} (${pb.scoreData.grade}, ${pb.scoreData.percent.toFixed( - 2 - )}%)`, - true - ) - .addField("Lamp", pb.scoreData.lamp, true) + .addField("Score", scoreStr, true) + .addField("Lamp", lampStr, true) .addField( "Ratings", Entries(pb.calculatedData) @@ -157,6 +157,10 @@ export async function CreateChartScoresEmbed( true ) .addField("Ranking", `#**${pb.rankingData.rank}**/${pb.rankingData.outOf}`); + + if (pertinentInfo) { + embed.addField("Related", pertinentInfo); + } } return embed; diff --git a/bot/src/utils/misc.ts b/bot/src/utils/misc.ts index 696a376e9..98c4fba1e 100644 --- a/bot/src/utils/misc.ts +++ b/bot/src/utils/misc.ts @@ -2,15 +2,20 @@ import { Client } from "discord.js"; import humaniseDuration from "humanize-duration"; import { DateTime } from "luxon"; import { + ChartDocument, Game, + GenericFormatGradeDelta, GetGameConfig, GetGamePTConfig, + Grades, IDStrings, - Playtype, - UGSRatingsLookup, integer, - ChartDocument, + PBScoreDocument, + Playtype, + Playtypes, ScoreCalculatedDataLookup, + ScoreDocument, + UGSRatingsLookup, } from "tachi-common"; import { GameClassSets } from "tachi-common/js/game-classes"; import { BotConfig } from "../config"; @@ -187,3 +192,103 @@ export function CreateChartLink(chart: ChartDocument, game: Game) { return `${BotConfig.TACHI_SERVER_LOCATION}/dashboard/games/${game}/${chart.playtype}/songs/${chart.songID}/${chart.chartID}`; } + +type ScOrPBDoc = ScoreDocument | PBScoreDocument; + +export function FormatScoreData(score: ScOrPBDoc) { + const game = score.game; + + let lampStr: string = score.scoreData.lamp; + let scoreStr = `${score.scoreData.score.toLocaleString()} (${ + score.scoreData.grade + }, ${score.scoreData.percent.toFixed(2)}%)`; + + if (game === "iidx" || game === "bms" || game === "pms") { + const bp = ( + score as ScOrPBDoc< + "iidx:SP" | "iidx:DP" | "pms:Controller" | "pms:Keyboard" | "bms:14K" | "bms:7K" + > + ).scoreData.hitMeta.bp; + + lampStr = `${score.scoreData.lamp} (BP: ${bp ?? "No Data"})`; + + const { lower, upper, closer } = GenericFormatGradeDelta( + game, + score.playtype, + score.scoreData.score, + score.scoreData.percent, + score.scoreData.grade + ); + + scoreStr = `${closer === "lower" ? lower : upper} (${ + score.scoreData.score + }, ${score.scoreData.percent.toFixed(2)}%)`; + } + + return { lampStr, scoreStr }; +} + +/** + * Util for getting a games' grade for a given percent. + */ +export function GetGradeFromPercent( + game: Game, + playtype: Playtype, + percent: number +): Grades[I] { + const gptConfig = GetGamePTConfig(game, playtype); + const boundaries = gptConfig.gradeBoundaries; + const grades = gptConfig.grades; + + if (!boundaries) { + throw new Error( + `Invalid call to GetGradeFromPercent! GPT ${game}:${playtype} does not use grade boundaries.` + ); + } + + // (hey, this for loop is backwards!) + for (let i = boundaries.length; i >= 0; i--) { + if (percent + Number.EPSILON >= boundaries[i]) { + return grades[i] as Grades[I]; + } + } + + throw new Error(`Couldn't find grade for ${game}:${playtype} (${percent}%)`); +} + +export function FormatIIDXEXScore( + exscore: integer, + notecount: integer, + playtype: Playtypes["iidx"] +) { + const percent = (exscore * 100) / (notecount * 2); + + const { closer, upper, lower } = GenericFormatGradeDelta( + "iidx", + playtype, + exscore, + percent, + GetGradeFromPercent("iidx", playtype, percent) + ); + + return `${closer === "lower" ? lower : upper} (${exscore}, ${percent.toFixed(2)}%)`; +} + +export function GetChartPertinentInfo(game: Game, chart: ChartDocument) { + if (game === "iidx") { + const ch = chart as ChartDocument<"iidx:DP" | "iidx:SP">; + + if (!ch.data.kaidenAverage || !ch.data.worldRecord) { + return null; + } + + return `皆伝 Average: ${FormatIIDXEXScore( + ch.data.kaidenAverage, + ch.data.notecount, + ch.playtype + )} +e-amusement WR: ${FormatIIDXEXScore(ch.data.worldRecord, ch.data.notecount, ch.playtype)}`; + } + + return null; +}