Updated curve stuff and added ScoreSaber pp calc
This commit is contained in:
parent
f7de7425f9
commit
4de24a34d0
@ -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",
|
||||||
|
70
pages/api/scoresaber/stars.js
Normal file
70
pages/api/scoresaber/stars.js
Normal file
@ -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;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
18
src/curve/BeatLeaderCurve.js
Normal file
18
src/curve/BeatLeaderCurve.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
82
src/curve/ScoreSaberCurve.js
Normal file
82
src/curve/ScoreSaberCurve.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
30
src/utils/scoreSaberUtils.js
Normal file
30
src/utils/scoreSaberUtils.js
Normal file
@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user