Skip to content

Commit

Permalink
update score acc changer to use pp gain from current score not a fres…
Browse files Browse the repository at this point in the history
…h score
  • Loading branch information
RealFascinated committed Feb 16, 2025
1 parent 0b280ff commit c16e7bd
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 30 deletions.
4 changes: 1 addition & 3 deletions projects/backend/src/controller/player.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,6 @@ export default class PlayerController {
}: {
params: { id: string };
}): Promise<PlayerRankedPpsResponse> {
return {
pps: await PlayerService.getPlayerRankedPps(id),
};
return await PlayerService.getPlayerRankedPps(id);
}
}
39 changes: 32 additions & 7 deletions projects/backend/src/service/player.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import { logNewTrackedPlayer } from "../common/embds";
import CacheService, { ServiceCache } from "./cache.service";
import { ScoreService } from "./score/score.service";
import ScoreSaberService from "./scoresaber.service";
import { PlayerRankedPpsResponse } from "@ssr/common/response/player-ranked-pps-response";
import { updateScoreWeights } from "@ssr/common/utils/scoresaber.util";

const accountCreationLock: { [id: string]: Promise<PlayerDocument> } = {};

Expand Down Expand Up @@ -205,17 +207,32 @@ export class PlayerService {
* @param playerId the player's id
* @returns the ranked pp scores
*/
public static async getPlayerRankedPps(playerId: string): Promise<number[]> {
public static async getPlayerRankedPps(playerId: string): Promise<PlayerRankedPpsResponse> {
await this.ensurePlayerExists(playerId);

const playerScores = await ScoreService.getPlayerScores(playerId, {
ranked: true,
sort: "pp",
projection: { pp: 1 },
projection: { pp: 1, weight: 1, scoreId: 1 },
includeLeaderboard: false,
});

return playerScores.map(score => score.score.pp);
if (playerScores.length === 0) {
return {
scores: [],
};
}

const scores = playerScores.map(score => ({
pp: score.score.pp,
weight: score.score.weight,
scoreId: score.score.scoreId,
}));
updateScoreWeights(scores);

return {
scores,
};
}

/**
Expand All @@ -230,13 +247,18 @@ export class PlayerService {
): Promise<number[]> {
await this.ensurePlayerExists(playerId);
const scoresPps = await this.getPlayerRankedPps(playerId);
if (scoresPps.length === 0) {
if (scoresPps.scores.length === 0) {
return [0];
}

const boundaries: number[] = [];
for (let i = 1; i < boundary + 1; i++) {
boundaries.push(scoresaberService.calcPpBoundary(scoresPps, i));
boundaries.push(
scoresaberService.calcPpBoundary(
scoresPps.scores.map(score => score.pp),
i
)
);
}
return boundaries;
}
Expand All @@ -253,11 +275,14 @@ export class PlayerService {
): Promise<number> {
await this.ensurePlayerExists(playerId);
const scoresPps = await this.getPlayerRankedPps(playerId);
if (scoresPps.length === 0) {
if (scoresPps.scores.length === 0) {
return 0;
}

return scoresaberService.getPpBoundaryForRawPp(scoresPps, boundary);
return scoresaberService.getPpBoundaryForRawPp(
scoresPps.scores.map(score => score.pp),
boundary
);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion projects/common/src/response/player-ranked-pps-response.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ScoreSaberScore } from "../model/score/impl/scoresaber-score";

export type PlayerRankedPpsResponse = {
pps: number[];
scores: Pick<ScoreSaberScore, "pp" | "weight" | "scoreId">[];
};
14 changes: 7 additions & 7 deletions projects/common/src/service/impl/scoresaber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ const SEARCH_LEADERBOARDS_ENDPOINT = `${API_BASE}/leaderboards?search=:query`;
*/
const RANKING_REQUESTS_ENDPOINT = `${API_BASE}/ranking/requests/:query`;

const WEIGHT_COEFFICIENT = 0.965;
const STAR_MULTIPLIER = 42.117208413;

class ScoreSaberService extends Service {
readonly WEIGHT_COEFFICIENT = 0.965;
readonly STAR_MULTIPLIER = 42.117208413;

private curvePoints = [
new CurvePoint(0, 0),
new CurvePoint(0.6, 0.18223233667439062),
Expand Down Expand Up @@ -474,7 +474,7 @@ class ScoreSaberService extends Service {
if (accuracy <= 1) {
accuracy *= 100; // Convert the accuracy to a percentage
}
const pp = stars * STAR_MULTIPLIER; // Calculate base PP value
const pp = stars * this.STAR_MULTIPLIER; // Calculate base PP value
return this.getModifier(accuracy) * pp; // Calculate and return final PP value
}

Expand All @@ -492,7 +492,7 @@ class ScoreSaberService extends Service {

// 0.965^idx * rawPpToFind = expected + oldBottomPp - newBottomPp;
// rawPpToFind = (expected + oldBottomPp - newBottomPp) / 0.965^idx;
return (expected + oldBottomPp - newBottomPp) / Math.pow(WEIGHT_COEFFICIENT, idx);
return (expected + oldBottomPp - newBottomPp) / Math.pow(this.WEIGHT_COEFFICIENT, idx);
}

/**
Expand All @@ -504,9 +504,9 @@ class ScoreSaberService extends Service {
* @returns the total amount of weighted pp
* @private
*/
private getTotalWeightedPp(ppArray: Array<number>, startIdx = 0) {
public getTotalWeightedPp(ppArray: Array<number>, startIdx = 0) {
return ppArray.reduce(
(cumulative, pp, idx) => cumulative + Math.pow(WEIGHT_COEFFICIENT, idx + startIdx) * pp,
(cumulative, pp, idx) => cumulative + Math.pow(this.WEIGHT_COEFFICIENT, idx + startIdx) * pp,
0
);
}
Expand Down
13 changes: 13 additions & 0 deletions projects/common/src/utils/scoresaber.util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ScoreSaberScore } from "src/model/score/impl/scoresaber-score";
import { scoresaberService } from "src/service/impl/scoresaber";
import { HMD } from "../hmds";
import ScoreSaberPlayer from "../player/impl/scoresaber-player";
import { MapDifficulty } from "../score/map-difficulty";
Expand Down Expand Up @@ -131,3 +133,14 @@ export function getScoreSaberAvatar(
): string {
return `https://cdn.scoresaber.com/avatars/${player.id}.jpg`;
}

/**
* Updates the weights of the scores
*
* @param scores the scores
*/
export function updateScoreWeights(scores: Pick<ScoreSaberScore, "pp" | "weight" | "scoreId">[]) {
for (let i = 0; i < scores.length; i++) {
scores[i].weight = Math.pow(scoresaberService.WEIGHT_COEFFICIENT, i);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { useIsMobile } from "@/hooks/use-is-mobile";
import { ScoreSaberLeaderboard } from "@ssr/common/model/leaderboard/impl/scoresaber-leaderboard";
import { ScoreSaberScore } from "@ssr/common/model/score/impl/scoresaber-score";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import { updateScoreWeights } from "@ssr/common/utils/scoresaber.util";
import { ssrApi } from "@ssr/common/utils/ssr-api";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useMemo, useState } from "react";
import { FaCog, FaUndo } from "react-icons/fa";

type ScoreEditorButtonProps = {
Expand All @@ -33,6 +34,9 @@ export default function ScoreEditorButton({
queryFn: () => ssrApi.getPlayerRankedPps(score.playerId),
});

const [modifiedScores, setModifiedScores] =
useState<Pick<ScoreSaberScore, "pp" | "weight" | "scoreId">[]>();

const handleSliderChange = (value: number[]) => {
const newAccuracy = Math.max(0, Math.min(value[0], 100)); // Ensure the accuracy stays within 0-100
const newBaseScore = (newAccuracy / 100) * maxScore;
Expand All @@ -41,6 +45,23 @@ export default function ScoreEditorButton({
score: newBaseScore,
});
setNewAccuracy(newAccuracy);

if (rankedPps) {
let newModifiedScores = [...rankedPps.scores];
for (let i = 0; i < newModifiedScores.length; i++) {
const modifiedScore = newModifiedScores[i];
if (score.scoreId == modifiedScore.scoreId) {
newModifiedScores[i] = {
...modifiedScore,
pp: scoresaberService.getPp(leaderboard.stars, newAccuracy),
};
}
}
newModifiedScores = newModifiedScores.sort((a, b) => b.pp - a.pp);
updateScoreWeights(newModifiedScores);

setModifiedScores(newModifiedScores);
}
};

const handleSliderReset = () => {
Expand All @@ -51,20 +72,20 @@ export default function ScoreEditorButton({
setNewAccuracy(accuracy);
};

const ppGain = rankedPps
? scoresaberService.getPpBoundaryForRawPp(
rankedPps.pps,
scoresaberService.getPp(leaderboard.stars, newAccuracy)
)
: 0;
const ppGain = useMemo(() => {
if (!rankedPps || !modifiedScores) {
return 0;
}

return (
scoresaberService.getTotalWeightedPp(modifiedScores.map(score => score.pp)) -
scoresaberService.getTotalWeightedPp(rankedPps.scores.map(score => score.pp))
);
}, [modifiedScores, rankedPps]);

return (
<div className="flex items-center justify-center cursor-default relative">
<Popover
onOpenChange={open => {
handleSliderReset();
}}
>
<Popover onOpenChange={() => handleSliderReset()}>
<PopoverTrigger>
<Tooltip display={<p>Edit Score Accuracy</p>}>
<FaCog className="size-6 p-0.5 cursor-pointer hover:animate-spin-slow" />
Expand Down

0 comments on commit c16e7bd

Please sign in to comment.