migrate some values to ssr data tracking so we don't need to rely on BL as much
This commit is contained in:
parent
5ff0d11f5a
commit
9626931b91
@ -71,6 +71,6 @@ export default class ScoresController {
|
||||
};
|
||||
query: { search?: string };
|
||||
}): Promise<unknown> {
|
||||
return (await ScoreService.getPreviousScores(playerId, leaderboardId, page)).toJSON();
|
||||
return (await ScoreService.getScoreHistory(playerId, leaderboardId, page)).toJSON();
|
||||
}
|
||||
}
|
||||
|
@ -61,27 +61,30 @@ connectBeatLeaderWebsocket({
|
||||
});
|
||||
|
||||
export const app = new Elysia();
|
||||
app.use(
|
||||
cron({
|
||||
name: "player-statistics-tracker-cron",
|
||||
pattern: "1 0 * * *", // Every day at 00:01
|
||||
timezone: "Europe/London", // UTC time
|
||||
run: async () => {
|
||||
await PlayerService.updatePlayerStatistics();
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
cron({
|
||||
name: "player-scores-tracker-cron",
|
||||
pattern: "0 4 * * *", // Every day at 04:00
|
||||
timezone: "Europe/London", // UTC time
|
||||
protect: true,
|
||||
run: async () => {
|
||||
await PlayerService.refreshPlayerScores();
|
||||
},
|
||||
})
|
||||
);
|
||||
if (isProduction()) {
|
||||
app.use(
|
||||
cron({
|
||||
name: "player-statistics-tracker-cron",
|
||||
pattern: "1 0 * * *", // Every day at 00:01
|
||||
timezone: "Europe/London", // UTC time
|
||||
protect: true,
|
||||
run: async () => {
|
||||
await PlayerService.updatePlayerStatistics();
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
cron({
|
||||
name: "player-scores-tracker-cron",
|
||||
pattern: "0 4 * * *", // Every day at 04:00
|
||||
timezone: "Europe/London", // UTC time
|
||||
protect: true,
|
||||
run: async () => {
|
||||
await PlayerService.refreshPlayerScores();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom error handler
|
||||
|
@ -7,7 +7,6 @@ import BeatSaverService from "./beatsaver.service";
|
||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||
import { ScoreSort } from "@ssr/common/score/score-sort";
|
||||
import { Leaderboards } from "@ssr/common/leaderboard";
|
||||
import Leaderboard from "@ssr/common/leaderboard/leaderboard";
|
||||
import LeaderboardService from "./leaderboard.service";
|
||||
import { BeatSaverMap } from "@ssr/common/model/beatsaver/map";
|
||||
import { PlayerScore } from "@ssr/common/score/player-score";
|
||||
@ -27,13 +26,18 @@ import {
|
||||
import { BeatLeaderScoreImprovementToken } from "@ssr/common/types/token/beatleader/score/score-improvement";
|
||||
import { ScoreType } from "@ssr/common/model/score/score";
|
||||
import { getScoreSaberLeaderboardFromToken, getScoreSaberScoreFromToken } from "@ssr/common/token-creators";
|
||||
import { ScoreSaberScore, ScoreSaberScoreModel } from "@ssr/common/model/score/impl/scoresaber-score";
|
||||
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
|
||||
import {
|
||||
ScoreSaberPreviousScore,
|
||||
ScoreSaberScore,
|
||||
ScoreSaberScoreModel,
|
||||
} from "@ssr/common/model/score/impl/scoresaber-score";
|
||||
import ScoreSaberScoreToken from "@ssr/common/types/token/scoresaber/score-saber-score-token";
|
||||
import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token";
|
||||
import { MapDifficulty } from "@ssr/common/score/map-difficulty";
|
||||
import { MapCharacteristic } from "@ssr/common/types/map-characteristic";
|
||||
import { Page, Pagination } from "@ssr/common/pagination";
|
||||
import ScoreSaberLeaderboard from "@ssr/common/model/leaderboard/impl/scoresaber-leaderboard";
|
||||
import Leaderboard from "@ssr/common/model/leaderboard/leaderboard";
|
||||
|
||||
const playerScoresCache = new SSRCache({
|
||||
ttl: 1000 * 60, // 1 minute
|
||||
@ -394,6 +398,10 @@ export class ScoreService {
|
||||
if (additionalData !== undefined) {
|
||||
score.additionalData = additionalData;
|
||||
}
|
||||
const previousScore = await this.getPreviousScore(playerId, leaderboard.id + "", score.timestamp);
|
||||
if (previousScore !== undefined) {
|
||||
score.previousScore = previousScore;
|
||||
}
|
||||
|
||||
scores.push({
|
||||
score: score,
|
||||
@ -491,13 +499,13 @@ export class ScoreService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous scores for a player.
|
||||
* Gets the player's score history for a map.
|
||||
*
|
||||
* @param playerId the player's id to get the previous scores for
|
||||
* @param leaderboardId the leaderboard to get the previous scores on
|
||||
* @param page the page to get
|
||||
*/
|
||||
public static async getPreviousScores(
|
||||
public static async getScoreHistory(
|
||||
playerId: string,
|
||||
leaderboardId: string,
|
||||
page: number
|
||||
@ -533,6 +541,10 @@ export class ScoreService {
|
||||
if (additionalData !== undefined) {
|
||||
score.additionalData = additionalData;
|
||||
}
|
||||
const previousScore = await this.getPreviousScore(playerId, leaderboardId, score.timestamp);
|
||||
if (previousScore !== undefined) {
|
||||
score.previousScore = previousScore;
|
||||
}
|
||||
|
||||
toReturn.push({
|
||||
score: score as unknown as ScoreSaberScore,
|
||||
@ -544,4 +556,55 @@ export class ScoreService {
|
||||
return toReturn;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player's previous score for a map.
|
||||
*
|
||||
* @param playerId the player's id to get the previous score for
|
||||
* @param leaderboardId the leaderboard to get the previous score on
|
||||
* @param timestamp the score's timestamp to get the previous score for
|
||||
* @returns the score, or undefined if none
|
||||
*/
|
||||
public static async getPreviousScore(
|
||||
playerId: string,
|
||||
leaderboardId: string,
|
||||
timestamp: Date
|
||||
): Promise<ScoreSaberPreviousScore | undefined> {
|
||||
const scores = await ScoreSaberScoreModel.find({ playerId: playerId, leaderboardId: leaderboardId });
|
||||
if (scores == null || scores.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const scoreIndex = scores.findIndex(score => score.timestamp.getTime() == timestamp.getTime());
|
||||
const score = scores.find(score => score.timestamp.getTime() == timestamp.getTime());
|
||||
if (scoreIndex == -1 || score == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const previousScore = scores[scoreIndex - 1];
|
||||
if (previousScore == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
score: previousScore.score,
|
||||
accuracy: previousScore.accuracy,
|
||||
modifiers: previousScore.modifiers,
|
||||
misses: previousScore.misses,
|
||||
missedNotes: previousScore.missedNotes,
|
||||
badCuts: previousScore.badCuts,
|
||||
fullCombo: previousScore.fullCombo,
|
||||
pp: previousScore.pp,
|
||||
weight: previousScore.weight,
|
||||
maxCombo: previousScore.maxCombo,
|
||||
change: {
|
||||
score: score.score - previousScore.score,
|
||||
accuracy: score.accuracy - previousScore.accuracy,
|
||||
misses: score.misses - previousScore.misses,
|
||||
missedNotes: score.missedNotes - previousScore.missedNotes,
|
||||
badCuts: score.badCuts - previousScore.badCuts,
|
||||
pp: score.pp - previousScore.pp,
|
||||
weight: score.weight && previousScore.weight && score.weight - previousScore.weight,
|
||||
maxCombo: score.maxCombo - previousScore.maxCombo,
|
||||
},
|
||||
} as ScoreSaberPreviousScore;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import Score from "../score";
|
||||
import { type ScoreSaberLeaderboardPlayerInfoToken } from "../../../types/token/scoresaber/score-saber-leaderboard-player-info-token";
|
||||
import { Document } from "mongoose";
|
||||
import { AutoIncrementID } from "@typegoose/auto-increment";
|
||||
import { PreviousScore } from "../previous-score";
|
||||
|
||||
@modelOptions({
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
@ -58,6 +59,11 @@ export class ScoreSaberScoreInternal extends Score {
|
||||
*/
|
||||
@Prop({ required: true })
|
||||
public readonly maxCombo!: number;
|
||||
|
||||
/**
|
||||
* The previous score, if any.
|
||||
*/
|
||||
public previousScore?: ScoreSaberPreviousScore;
|
||||
}
|
||||
|
||||
class ScoreSaberScorePublic extends ScoreSaberScoreInternal {
|
||||
@ -67,6 +73,28 @@ class ScoreSaberScorePublic extends ScoreSaberScoreInternal {
|
||||
public playerInfo!: ScoreSaberLeaderboardPlayerInfoToken;
|
||||
}
|
||||
|
||||
export type ScoreSaberPreviousScore = PreviousScore & {
|
||||
/**
|
||||
* The pp of the previous score.
|
||||
*/
|
||||
pp: number;
|
||||
|
||||
/**
|
||||
* The weight of the previous score.
|
||||
*/
|
||||
weight: number;
|
||||
|
||||
/**
|
||||
* The max combo of the previous score.
|
||||
*/
|
||||
maxCombo: number;
|
||||
|
||||
/**
|
||||
* The change between the previous score and the current score.
|
||||
*/
|
||||
change?: ScoreSaberPreviousScore;
|
||||
};
|
||||
|
||||
export type ScoreSaberScore = InstanceType<typeof ScoreSaberScorePublic>;
|
||||
export type ScoreSaberScoreDocument = ScoreSaberScore & Document;
|
||||
export const ScoreSaberScoreModel: ReturnModelType<typeof ScoreSaberScoreInternal> =
|
||||
|
38
projects/common/src/model/score/previous-score.ts
Normal file
38
projects/common/src/model/score/previous-score.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Modifier } from "../../score/modifier";
|
||||
|
||||
export type PreviousScore = {
|
||||
/**
|
||||
* The score of the previous score.
|
||||
*/
|
||||
score: number;
|
||||
|
||||
/**
|
||||
* The accuracy of the previous score.
|
||||
*/
|
||||
accuracy: number;
|
||||
|
||||
/**
|
||||
* The modifiers of the previous score.
|
||||
*/
|
||||
modifiers?: Modifier[];
|
||||
|
||||
/**
|
||||
* The misses of the previous score.
|
||||
*/
|
||||
misses: number;
|
||||
|
||||
/**
|
||||
* The missed notes of the previous score.
|
||||
*/
|
||||
missedNotes: number;
|
||||
|
||||
/**
|
||||
* The bad cuts of the previous score.
|
||||
*/
|
||||
badCuts: number;
|
||||
|
||||
/**
|
||||
* The full combo of the previous score.
|
||||
*/
|
||||
fullCombo?: boolean;
|
||||
};
|
@ -14,8 +14,7 @@ type ScoreAccuracyProps = ScoreBadgeProps & {
|
||||
};
|
||||
|
||||
export function ScoreAccuracyBadge({ score, leaderboard }: ScoreAccuracyProps) {
|
||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
||||
const previousAccuracy = scoreImprovement ? score.accuracy - scoreImprovement.accuracy : undefined;
|
||||
const previousScore = score.previousScore;
|
||||
|
||||
const fcAccuracy = score.additionalData?.fcAccuracy;
|
||||
const scoreBadge = getScoreBadgeFromAccuracy(score.accuracy);
|
||||
@ -57,9 +56,13 @@ export function ScoreAccuracyBadge({ score, leaderboard }: ScoreAccuracyProps) {
|
||||
{modCount > 0 && <ScoreModifiers type="simple" limit={1} score={score} />}
|
||||
</p>
|
||||
</Tooltip>
|
||||
{scoreImprovement && previousAccuracy && (
|
||||
<Tooltip display={`Previous Accuracy: ${previousAccuracy.toFixed(2)}%`}>
|
||||
<Change className="text-xs" change={scoreImprovement.accuracy} formatValue={num => `${num.toFixed(2)}%`} />
|
||||
{previousScore && previousScore.change && (
|
||||
<Tooltip display={`Previous Accuracy: ${previousScore.accuracy.toFixed(2)}%`}>
|
||||
<Change
|
||||
className="text-xs"
|
||||
change={previousScore.change.accuracy}
|
||||
formatValue={num => `${num.toFixed(2)}%`}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
@ -4,7 +4,6 @@ import Tooltip from "@/components/tooltip";
|
||||
import { ensurePositiveNumber, formatPp } from "@ssr/common/utils/number-utils";
|
||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||
import { Change } from "@/common/change";
|
||||
import { Warning } from "@/components/warning";
|
||||
|
||||
type ScorePpProps = ScoreBadgeProps & {
|
||||
/**
|
||||
@ -14,8 +13,7 @@ type ScorePpProps = ScoreBadgeProps & {
|
||||
};
|
||||
|
||||
export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
||||
const previousAccuracy = scoreImprovement ? score.accuracy - scoreImprovement?.accuracy : undefined;
|
||||
const previousScore = score.previousScore;
|
||||
const fcAccuracy = score.additionalData?.fcAccuracy;
|
||||
const pp = score.pp;
|
||||
const weight = score.weight;
|
||||
@ -28,39 +26,29 @@ export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
display={
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<p className="font-semibold">Performance Points</p>
|
||||
<p>Raw: {formatPp(pp)}pp</p>
|
||||
<p>
|
||||
Weighted: {formatPp(weightedPp)}pp ({(100 * weight).toFixed(2)}%)
|
||||
</p>
|
||||
{fcPp && <p>Full Combo: {fcPp}pp</p>}
|
||||
</div>
|
||||
|
||||
{previousAccuracy && (
|
||||
<Warning>
|
||||
<p className="text-red-700">
|
||||
The previous pp may not be 100% accurate due to ScoreSaber API limitations.
|
||||
<div className="flex flex-col items-center justify-center cursor-default">
|
||||
<Tooltip
|
||||
display={
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<p className="font-semibold">Performance Points</p>
|
||||
<p>Raw: {formatPp(pp)}pp</p>
|
||||
<p>
|
||||
Weighted: {formatPp(weightedPp)}pp ({(100 * weight).toFixed(2)}%)
|
||||
</p>
|
||||
</Warning>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center cursor-default">
|
||||
{fcPp && <p>Full Combo: {fcPp}pp</p>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>{formatPp(pp)}pp</p>
|
||||
{previousAccuracy && (
|
||||
<Change
|
||||
className="text-xs"
|
||||
change={ensurePositiveNumber(pp - scoresaberService.getPp(leaderboard.stars, previousAccuracy))}
|
||||
isPp
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
{previousScore && previousScore.change && (
|
||||
<Tooltip display={<p>Previous PP: {formatPp(previousScore.pp)}pp</p>}>
|
||||
<Change className="text-xs" change={ensurePositiveNumber(previousScore.change.pp)} isPp />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
|
||||
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
|
||||
import { Change } from "@/common/change";
|
||||
import Tooltip from "@/components/tooltip";
|
||||
|
||||
export function ScoreScoreBadge({ score }: ScoreBadgeProps) {
|
||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
||||
const previousScore = score.previousScore;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<p>{formatNumberWithCommas(Number(score.score.toFixed(0)))}</p>
|
||||
{scoreImprovement && <Change className="text-xs" change={scoreImprovement.score} />}
|
||||
{previousScore && previousScore.change && (
|
||||
<Tooltip display={<p>Previous Score: {previousScore.score}</p>}>
|
||||
<Change className="text-xs" change={previousScore.change.score} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user