Updated curve stuff and added ScoreSaber pp calc

This commit is contained in:
Liam 2022-10-21 16:25:30 +01:00
parent f7de7425f9
commit 4de24a34d0
8 changed files with 230 additions and 29 deletions

@ -30,9 +30,7 @@ export default async function handler(req, res) {
const before = Date.now(); const before = Date.now();
const data = await fetch( const data = await fetch(
WebsiteTypes.BeatLeader.ApiUrl.MapData.replace("%h", mapHash) WebsiteTypes.BeatLeader.ApiUrl.MapData.replace("%h", mapHash),
.replace("%d", difficulty)
.replace("%m", characteristic),
{ {
headers: { headers: {
"X-Requested-With": "BeatSaber Overlay", "X-Requested-With": "BeatSaber Overlay",

@ -0,0 +1,70 @@
import fetch from "node-fetch";
import WebsiteTypes from "../../../src/consts/LeaderboardType";
import RedisUtils from "../../../src/utils/redisUtils";
import { diffToScoreSaberDiff } from "../../../src/utils/scoreSaberUtils";
const KEY = "SS_MAP_STAR_";
/**
*
* @param {Request} req
* @param {Response} res
* @returns
*/
export default async function handler(req, res) {
const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase();
const difficulty = req.query.difficulty.replace(" ", "");
const characteristic = req.query.characteristic;
const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`;
const exists = await RedisUtils.exists(key);
if (exists) {
const data = await RedisUtils.getValue(key);
res.setHeader("Cache-Status", "hit");
return res.status(200).json({
status: "OK",
stars: Number.parseFloat(data),
difficulty: difficulty,
});
}
const before = Date.now();
const data = await fetch(
WebsiteTypes.ScoreSaber.ApiUrl.MapData.replace("%h", mapHash).replace(
"%d",
diffToScoreSaberDiff(difficulty)
),
{
headers: {
"X-Requested-With": "BeatSaber Overlay",
},
}
);
if (data.status === 404) {
return res.status(404).json({
status: 404,
message: "Unknown Map Hash",
});
}
const json = await data.json();
console.log(json);
let starCount = json.stars;
if (starCount === undefined) {
return res.status(404).json({
status: 404,
message: "Unknown Map Hash",
});
}
await RedisUtils.setValue(key, starCount);
console.log(
`[Cache]: Cached SS Star Count for hash ${mapHash} in ${
Date.now() - before
}ms`
);
res.setHeader("Cache-Status", "miss");
return res.status(200).json({
status: "OK",
stars: starCount,
});
}

@ -266,16 +266,24 @@ export default class Overlay extends Component {
const json = await data.json(); const json = await data.json();
this.setState({ beatSaverData: json }); this.setState({ beatSaverData: json });
if (this.state.websiteType == "BeatLeader") {
const { characteristic, levelId, difficulty } = songData; const { characteristic, levelId, difficulty } = songData;
let mapHash = levelId.replace("custom_level_", ""); let mapHash = levelId.replace("custom_level_", "");
const mapStars = await LeaderboardType.BeatLeader.getMapStarCount( let mapStars = undefined;
if (this.state.websiteType === "BeatLeader") {
mapStars = await LeaderboardType.BeatLeader.getMapStarCount(
mapHash, mapHash,
difficulty.replace("+", "Plus"), difficulty.replace("+", "Plus"),
characteristic characteristic
); );
this.setState({ mapStarCount: mapStars });
} }
if (this.state.websiteType === "ScoreSaber") {
mapStars = await LeaderboardType.ScoreSaber.getMapStarCount(
mapHash,
difficulty.replace("+", "Plus"),
characteristic
);
}
this.setState({ mapStarCount: mapStars });
} }
/** /**

@ -4,6 +4,15 @@ const WebsiteTypes = {
PlayerData: PlayerData:
process.env.NEXT_PUBLIC_HTTP_PROXY + process.env.NEXT_PUBLIC_HTTP_PROXY +
"/https://scoresaber.com/api/player/%s/basic", "/https://scoresaber.com/api/player/%s/basic",
MapData:
"https://scoresaber.com/api/leaderboard/by-hash/%h/info?difficulty=%d",
},
async getMapStarCount(mapHash, mapDiff, characteristic) {
const data = await fetch(
`/api/scoresaber/stars?hash=${mapHash}&difficulty=${mapDiff}&characteristic=${characteristic}`
);
const json = await data.json();
return json.stars || undefined;
}, },
}, },
BeatLeader: { BeatLeader: {
@ -20,20 +29,6 @@ const WebsiteTypes = {
const json = await data.json(); const json = await data.json();
return json.stars || undefined; return json.stars || undefined;
}, },
curve(acc, stars) {
var l = 1 - (0.03 * (stars - 3.0)) / 11.0;
var a = 0.96 * l;
var f = 1.2 - (0.6 * stars) / 14.0;
return Math.pow(Math.log10(l / (l - acc)) / Math.log10(l / (l - a)), f);
},
ppFromAcc(acc, stars) {
if (stars === undefined || acc === undefined) {
return undefined;
}
const pp = this.curve(acc / 100, stars - 0.5) * (stars + 0.5) * 42;
return Number.isNaN(pp) ? undefined : pp;
},
}, },
}; };

@ -0,0 +1,18 @@
export default class BeatLeaderCurve {
static curve(acc, stars) {
var l = 1 - (0.03 * (stars - 3.0)) / 11.0;
var a = 0.96 * l;
var f = 1.2 - (0.6 * stars) / 14.0;
return Math.pow(Math.log10(l / (l - acc)) / Math.log10(l / (l - a)), f);
}
static getPP(acc, stars) {
if (stars === undefined || acc === undefined) {
return undefined;
}
const pp =
BeatLeaderCurve.curve(acc / 100, stars - 0.5) * (stars + 0.5) * 42;
return Number.isNaN(pp) ? undefined : pp;
}
}

@ -0,0 +1,82 @@
// Yoinked from https://github.com/Shurdoof/pp-calculator/blob/c24b5ca452119339928831d74e6d603fb17fd5ef/src/lib/pp/calculator.ts
// Thank for for this I have no fucking idea what the maths is doing but it works!
export default class ScoreSaberCurve {
static starMultiplier = 42.11;
static ppCurve = [
[1, 7],
[0.999, 5.8],
[0.9975, 4.7],
[0.995, 3.76],
[0.9925, 3.17],
[0.99, 2.73],
[0.9875, 2.38],
[0.985, 2.1],
[0.9825, 1.88],
[0.98, 1.71],
[0.9775, 1.57],
[0.975, 1.45],
[0.9725, 1.37],
[0.97, 1.31],
[0.965, 1.2],
[0.96, 1.11],
[0.955, 1.045],
[0.95, 1],
[0.94, 0.94],
[0.93, 0.885],
[0.92, 0.835],
[0.91, 0.79],
[0.9, 0.75],
[0.875, 0.655],
[0.85, 0.57],
[0.825, 0.51],
[0.8, 0.47],
[0.75, 0.4],
[0.7, 0.34],
[0.65, 0.29],
[0.6, 0.25],
[0.0, 0.0],
];
static clamp(value, min, max) {
if (min !== null && value < min) {
return min;
}
if (max !== null && value > max) {
return max;
}
return value;
}
static lerp(v0, v1, t) {
return v0 + t * (v1 - v0);
}
static calculatePPModifier(c1, c2, acc) {
const distance = (c2[0] - acc) / (c2[0] - c1[0]);
return ScoreSaberCurve.lerp(c2[1], c1[1], distance);
}
static findPPModifier(acc, curve) {
acc = ScoreSaberCurve.clamp(acc, 0, 100) / 100;
let prev = curve[1];
for (const item of curve) {
if (item[0] <= acc) {
return ScoreSaberCurve.calculatePPModifier(item, prev, acc);
}
prev = item;
}
}
static getPP(acc, stars) {
const ppValue = stars * ScoreSaberCurve.starMultiplier;
const modifier = ScoreSaberCurve.findPPModifier(
acc,
ScoreSaberCurve.ppCurve
);
return modifier * ppValue;
}
}

@ -0,0 +1,30 @@
function diffToScoreSaberDiff(diff) {
console.log(
"🚀 ~ file: scoreSaberUtils.js ~ line 2 ~ diffToScoreSaberDiff ~ diff",
diff
);
switch (diff) {
case "Easy": {
return 1;
}
case "Normal": {
return 3;
}
case "Hard": {
return 5;
}
case "Expert": {
return 7;
}
case "Expert+": {
return 9;
}
case "ExpertPlus": {
return 9;
}
}
}
module.exports = {
diffToScoreSaberDiff,
};

@ -1,11 +1,8 @@
import { import { default as LeaderboardType } from "../consts/LeaderboardType";
default as LeaderboardType, import BeatLeaderCurve from "../curve/BeatLeaderCurve";
default as WebsiteTypes, import ScoreSaberCurve from "../curve/ScoreSaberCurve";
} from "../consts/LeaderboardType";
export default class Utils { export default class Utils {
constructor() {}
/** /**
* Returns the information for the given website type. * Returns the information for the given website type.
* *
@ -35,7 +32,10 @@ export default class Utils {
static calculatePP(stars, acc, type) { static calculatePP(stars, acc, type) {
if (type === "BeatLeader") { if (type === "BeatLeader") {
return WebsiteTypes.BeatLeader.ppFromAcc(acc, stars); return BeatLeaderCurve.getPP(acc, stars);
}
if (type === "ScoreSaber") {
return ScoreSaberCurve.getPP(acc, stars);
} }
return undefined; return undefined;
} }