rg-stats
Version:
A library for calculating various rhythm game stats.
278 lines • 9.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inverseVF6 = exports.calculateVF6 = exports.inverseVF5 = exports.calculateVF5 = exports.inverseVF4 = exports.calculateVF4 = void 0;
const misc_1 = require("../util/misc");
const throw_if_1 = require("../util/throw-if");
const VF4GradeCoefficients = {
S: 1.0,
"AAA+": 0.99,
AAA: 0.98,
"AA+": 0.97,
AA: 0.96,
"A+": 0.95,
A: 0.94,
B: 0.93,
C: 0.92,
D: 0.91,
};
const VF5GradeCoefficients = {
S: 105,
"AAA+": 102,
AAA: 100,
"AA+": 97,
AA: 94,
// everything below this point is marked with a (?)
// in bemaniwiki, so maybe it can't be trusted?
"A+": 91,
A: 88,
B: 85,
C: 82,
D: 80,
};
const VF5LampCoefficients = {
"PERFECT ULTIMATE CHAIN": 110,
"ULTIMATE CHAIN": 105,
"MAXXIVE CLEAR": 104,
"EXCESSIVE CLEAR": 102,
CLEAR: 100,
FAILED: 50,
};
/**
* Calculate VOLFORCE as it's defined in SDVX4.
*
* @param score - The user's score. Between 0 and 10million.
* @param level - The level of the chart. Between 0 and 20, but this is not enforced.
*/
function calculateVF4(score, level) {
AssertProvidedScore(score);
const grade = SDVXScoreToGrade(score);
const gradeCoefficient = VF4GradeCoefficients[grade];
return Math.floor(25 * (level + 1) * (score / 10000000) * gradeCoefficient);
}
exports.calculateVF4 = calculateVF4;
/**
* Given a VF4 value and a chart level, return what score is needed to get that
* VF4.
*
* If the score needed is greater than 10million, this function will throw.
**
* @param vf4 - The VF4 to invert.
* @param level - The level of the chart you're inverting for.
*/
function inverseVF4(vf4, level) {
const scoreTimesGradeCoef = (10000000 * vf4) / (25 * (level + 1));
const score = AttemptGradeCoefficientDivide(scoreTimesGradeCoef, VF4GradeCoefficients);
(0, throw_if_1.ThrowIf)(score === null, `A VF4 of ${vf4} is not possible on a chart with level ${level}`, {
vf4,
level,
});
return score;
}
exports.inverseVF4 = inverseVF4;
/**
* Calculate VOLFORCE as it's defined in SDVX5.
*
* @param score - The user's score. Between 0 and 10million.
* @param level - The level of the chart. Between 0 and 20, but this is not enforced.
*/
function calculateVF5(score, lamp, level) {
AssertProvidedScore(score);
const unroundedVF5 = CalculateWholeVF5(score, lamp, level);
return Math.floor(unroundedVF5 / 10000) / 100;
}
exports.calculateVF5 = calculateVF5;
/**
* Given a VF5 value and a chart level, return what score is needed to get that
* VF5.
*
* If the score needed is greater than 10million, this function will throw.
*
* @param vf5 - The VF5 to invert.
* @param lamp - The lamp for this score. This is necessary to know, as lampCoefficient
* plays a part in VF5.
* @param level - The level of the chart you're inverting for.
*/
function inverseVF5(vf5,
// Exclude PUC as input. It doesn't make sense as input, since the answer would
// always be 10million.
lamp, level) {
const score = InvertUnroundedVF5(vf5, lamp, level);
(0, throw_if_1.ThrowIf)(score === null, `A VF5 of ${vf5} is not possible on a chart with level ${level}.`, {
vf5,
level,
});
return score;
}
exports.inverseVF5 = inverseVF5;
/**
* Calculate VOLFORCE as it's defined in SDVX6.
*
* @param score - The user's score. Between 0 and 10million.
* @param level - The level of the chart. Between 0 and 20, but this is not enforced.
*/
function calculateVF6(score, lamp, level) {
AssertProvidedScore(score);
const unroundedVF5 = CalculateWholeVF5(score, lamp, level);
// VF6 is just unroundedVF5 to 3 decimal places instead of 2.
return Math.floor(unroundedVF5 / 1000) / 1000;
}
exports.calculateVF6 = calculateVF6;
/**
* Given a VF6 value and a chart level, return what score is needed to get that
* VF5.
*
* If the score needed is greater than 10million, this function will throw.
**
* @param vf6 - The VF6 to invert.
* @param lamp - The lamp for this score. This is necessary to know, as lampCoefficient
* plays a part in VF6. Passing "PERFECT ULTIMATE CHAIN" as a lamp is invalid, as inverting
* it into a score makes no sense.
* @param level - The level of the chart you're inverting for.
*/
function inverseVF6(vf6,
// Exclude PUC as input. It doesn't make sense as input, since the answer would
// always be 10million.
lamp, level) {
// note: this function is actually identical to inverseVF5, but with the caveat
// that the error message is different.
const score = InvertUnroundedVF5(vf6, lamp, level);
(0, throw_if_1.ThrowIf)(score === null, `A VF6 of ${vf6} is not possible on a chart with level ${level}.`, {
vf6,
level,
});
// guaranteed to not be null
return score;
}
exports.inverseVF6 = inverseVF6;
/**
* Calculate VF5 without performing any rounding. This is useful because VF5
* is floored to 2 decimal places, wherease VF6 is floored to 3. This lets us
* reuse the same algorithm.
*/
function CalculateWholeVF5(score, lamp, level) {
const grade = SDVXScoreToGrade(score);
const gradeCoefficient = VF5GradeCoefficients[grade];
const lampCoefficient = VF5LampCoefficients[lamp];
return level * 2 * (score / 10000000) * gradeCoefficient * lampCoefficient;
}
/**
* Attempt to invert VF5 into a score.
*
* @returns The score if it was possible to be achieved. Else, it returns null.
*/
function InvertUnroundedVF5(vf5, lamp, level) {
// Note: PERFECT ULTIMATE CHAIN is never passed into this function from typescript
// as the calling functions Exclude<T> it from the lamps.
// However, a JS caller may call it like this anyway, so we mayaswell throw.
(0, throw_if_1.ThrowIf)(lamp === "PERFECT ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN as a lampCoefficient does not make sense for an inversion, since the answer would always be 10million.", { lamp });
const lampCoefficient = VF5LampCoefficients[lamp];
const scoreTimesGradeCoef = (1000000 * 10000000 * vf5) / (2 * level * lampCoefficient);
const score = AttemptGradeCoefficientDivide(scoreTimesGradeCoef, VF5GradeCoefficients);
return score;
}
/**
* Convert a SDVX percent to the grade it represents.
* @param score - The score to convert - between 0 and 10million.
* @returns A string representing a grade.
*/
function SDVXScoreToGrade(score) {
if (score < 7000000) {
return "D";
}
else if (score < 8000000) {
return "C";
}
else if (score < 8700000) {
return "B";
}
else if (score < 9000000) {
return "A";
}
else if (score < 9300000) {
return "A+";
}
else if (score < 9500000) {
return "AA";
}
else if (score < 9700000) {
return "AA+";
}
else if (score < 9800000) {
return "AAA";
}
else if (score < 9900000) {
return "AAA+";
}
return "S";
}
/**
* Given a SDVX grade, return the lower and upper bounds for scoring in this grade.
* This is used to invert the gradeCoefficient function in volforce.
*
* Bounds are returned as lower <= k < upper.
*/
function SDVXGetGradeBoundaries(grade) {
if (grade === "S") {
return { lower: 9900000, upper: 10000000 };
}
else if (grade === "AAA+") {
return { lower: 9800000, upper: 9900000 };
}
else if (grade === "AAA") {
return { lower: 9700000, upper: 9800000 };
}
else if (grade === "AA+") {
return { lower: 9500000, upper: 9700000 };
}
else if (grade === "AA") {
return { lower: 9300000, upper: 9500000 };
}
else if (grade === "A+") {
return { lower: 9000000, upper: 9300000 };
}
else if (grade === "A") {
return { lower: 8700000, upper: 9000000 };
}
else if (grade === "B") {
return { lower: 8000000, upper: 8700000 };
}
else if (grade === "C") {
return { lower: 7000000, upper: 8000000 };
}
return { lower: 0, upper: 7000000 };
}
/**
* Assert necessary things about a provided score.
*/
function AssertProvidedScore(score) {
(0, throw_if_1.ThrowIf)(score > 10000000, "Score cannot be greater than 10million", { score });
throw_if_1.ThrowIf.negative(score, "Score cannot be negative", { score });
}
/**
* Go through all of the gradeBoundaries for a game and use them as guesses for score
* values.
*
* This means we try dividing by all the gradeCoefficients until we find one
* where the resulting score would have the same grade as the given coefficient.
*
* Used for inverting VF.
*
* @param scoreTimesGradeCoef - The expected score multiplied by the gradeCoefficient.
* @param coefficients - A record of SDVXGrade -> gradeCoefficient
* @returns The score divided by the gradeCoefficient. If not possible, this returns
* null.
*/
function AttemptGradeCoefficientDivide(scoreTimesGradeCoef, coefficients) {
for (const [grade, gradeCoef] of (0, misc_1.GetEntriesAsArray)(coefficients).reverse()) {
const maybeScore = scoreTimesGradeCoef / gradeCoef;
const { lower, upper } = SDVXGetGradeBoundaries(grade);
if (maybeScore <= lower) {
return lower;
}
else if (maybeScore < upper || (maybeScore === upper && upper === 10000000)) {
return Math.round(maybeScore);
}
}
return null;
}
//# sourceMappingURL=volforce.js.map