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 };
|
query: { search?: string };
|
||||||
}): Promise<unknown> {
|
}): Promise<unknown> {
|
||||||
return (await ScoreService.getPreviousScores(playerId, leaderboardId, page)).toJSON();
|
return (await ScoreService.getScoreHistory(playerId, leaderboardId, page)).toJSON();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,17 +61,19 @@ connectBeatLeaderWebsocket({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const app = new Elysia();
|
export const app = new Elysia();
|
||||||
app.use(
|
if (isProduction()) {
|
||||||
|
app.use(
|
||||||
cron({
|
cron({
|
||||||
name: "player-statistics-tracker-cron",
|
name: "player-statistics-tracker-cron",
|
||||||
pattern: "1 0 * * *", // Every day at 00:01
|
pattern: "1 0 * * *", // Every day at 00:01
|
||||||
timezone: "Europe/London", // UTC time
|
timezone: "Europe/London", // UTC time
|
||||||
|
protect: true,
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await PlayerService.updatePlayerStatistics();
|
await PlayerService.updatePlayerStatistics();
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
cron({
|
cron({
|
||||||
name: "player-scores-tracker-cron",
|
name: "player-scores-tracker-cron",
|
||||||
pattern: "0 4 * * *", // Every day at 04:00
|
pattern: "0 4 * * *", // Every day at 04:00
|
||||||
@ -81,7 +83,8 @@ app.use(
|
|||||||
await PlayerService.refreshPlayerScores();
|
await PlayerService.refreshPlayerScores();
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom error handler
|
* Custom error handler
|
||||||
|
@ -7,7 +7,6 @@ import BeatSaverService from "./beatsaver.service";
|
|||||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||||
import { ScoreSort } from "@ssr/common/score/score-sort";
|
import { ScoreSort } from "@ssr/common/score/score-sort";
|
||||||
import { Leaderboards } from "@ssr/common/leaderboard";
|
import { Leaderboards } from "@ssr/common/leaderboard";
|
||||||
import Leaderboard from "@ssr/common/leaderboard/leaderboard";
|
|
||||||
import LeaderboardService from "./leaderboard.service";
|
import LeaderboardService from "./leaderboard.service";
|
||||||
import { BeatSaverMap } from "@ssr/common/model/beatsaver/map";
|
import { BeatSaverMap } from "@ssr/common/model/beatsaver/map";
|
||||||
import { PlayerScore } from "@ssr/common/score/player-score";
|
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 { BeatLeaderScoreImprovementToken } from "@ssr/common/types/token/beatleader/score/score-improvement";
|
||||||
import { ScoreType } from "@ssr/common/model/score/score";
|
import { ScoreType } from "@ssr/common/model/score/score";
|
||||||
import { getScoreSaberLeaderboardFromToken, getScoreSaberScoreFromToken } from "@ssr/common/token-creators";
|
import { getScoreSaberLeaderboardFromToken, getScoreSaberScoreFromToken } from "@ssr/common/token-creators";
|
||||||
import { ScoreSaberScore, ScoreSaberScoreModel } from "@ssr/common/model/score/impl/scoresaber-score";
|
import {
|
||||||
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
|
ScoreSaberPreviousScore,
|
||||||
|
ScoreSaberScore,
|
||||||
|
ScoreSaberScoreModel,
|
||||||
|
} from "@ssr/common/model/score/impl/scoresaber-score";
|
||||||
import ScoreSaberScoreToken from "@ssr/common/types/token/scoresaber/score-saber-score-token";
|
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 ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token";
|
||||||
import { MapDifficulty } from "@ssr/common/score/map-difficulty";
|
import { MapDifficulty } from "@ssr/common/score/map-difficulty";
|
||||||
import { MapCharacteristic } from "@ssr/common/types/map-characteristic";
|
import { MapCharacteristic } from "@ssr/common/types/map-characteristic";
|
||||||
import { Page, Pagination } from "@ssr/common/pagination";
|
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({
|
const playerScoresCache = new SSRCache({
|
||||||
ttl: 1000 * 60, // 1 minute
|
ttl: 1000 * 60, // 1 minute
|
||||||
@ -394,6 +398,10 @@ export class ScoreService {
|
|||||||
if (additionalData !== undefined) {
|
if (additionalData !== undefined) {
|
||||||
score.additionalData = additionalData;
|
score.additionalData = additionalData;
|
||||||
}
|
}
|
||||||
|
const previousScore = await this.getPreviousScore(playerId, leaderboard.id + "", score.timestamp);
|
||||||
|
if (previousScore !== undefined) {
|
||||||
|
score.previousScore = previousScore;
|
||||||
|
}
|
||||||
|
|
||||||
scores.push({
|
scores.push({
|
||||||
score: score,
|
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 playerId the player's id to get the previous scores for
|
||||||
* @param leaderboardId the leaderboard to get the previous scores on
|
* @param leaderboardId the leaderboard to get the previous scores on
|
||||||
* @param page the page to get
|
* @param page the page to get
|
||||||
*/
|
*/
|
||||||
public static async getPreviousScores(
|
public static async getScoreHistory(
|
||||||
playerId: string,
|
playerId: string,
|
||||||
leaderboardId: string,
|
leaderboardId: string,
|
||||||
page: number
|
page: number
|
||||||
@ -533,6 +541,10 @@ export class ScoreService {
|
|||||||
if (additionalData !== undefined) {
|
if (additionalData !== undefined) {
|
||||||
score.additionalData = additionalData;
|
score.additionalData = additionalData;
|
||||||
}
|
}
|
||||||
|
const previousScore = await this.getPreviousScore(playerId, leaderboardId, score.timestamp);
|
||||||
|
if (previousScore !== undefined) {
|
||||||
|
score.previousScore = previousScore;
|
||||||
|
}
|
||||||
|
|
||||||
toReturn.push({
|
toReturn.push({
|
||||||
score: score as unknown as ScoreSaberScore,
|
score: score as unknown as ScoreSaberScore,
|
||||||
@ -544,4 +556,55 @@ export class ScoreService {
|
|||||||
return toReturn;
|
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 { type ScoreSaberLeaderboardPlayerInfoToken } from "../../../types/token/scoresaber/score-saber-leaderboard-player-info-token";
|
||||||
import { Document } from "mongoose";
|
import { Document } from "mongoose";
|
||||||
import { AutoIncrementID } from "@typegoose/auto-increment";
|
import { AutoIncrementID } from "@typegoose/auto-increment";
|
||||||
|
import { PreviousScore } from "../previous-score";
|
||||||
|
|
||||||
@modelOptions({
|
@modelOptions({
|
||||||
options: { allowMixed: Severity.ALLOW },
|
options: { allowMixed: Severity.ALLOW },
|
||||||
@ -58,6 +59,11 @@ export class ScoreSaberScoreInternal extends Score {
|
|||||||
*/
|
*/
|
||||||
@Prop({ required: true })
|
@Prop({ required: true })
|
||||||
public readonly maxCombo!: number;
|
public readonly maxCombo!: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The previous score, if any.
|
||||||
|
*/
|
||||||
|
public previousScore?: ScoreSaberPreviousScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScoreSaberScorePublic extends ScoreSaberScoreInternal {
|
class ScoreSaberScorePublic extends ScoreSaberScoreInternal {
|
||||||
@ -67,6 +73,28 @@ class ScoreSaberScorePublic extends ScoreSaberScoreInternal {
|
|||||||
public playerInfo!: ScoreSaberLeaderboardPlayerInfoToken;
|
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 ScoreSaberScore = InstanceType<typeof ScoreSaberScorePublic>;
|
||||||
export type ScoreSaberScoreDocument = ScoreSaberScore & Document;
|
export type ScoreSaberScoreDocument = ScoreSaberScore & Document;
|
||||||
export const ScoreSaberScoreModel: ReturnModelType<typeof ScoreSaberScoreInternal> =
|
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) {
|
export function ScoreAccuracyBadge({ score, leaderboard }: ScoreAccuracyProps) {
|
||||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
const previousScore = score.previousScore;
|
||||||
const previousAccuracy = scoreImprovement ? score.accuracy - scoreImprovement.accuracy : undefined;
|
|
||||||
|
|
||||||
const fcAccuracy = score.additionalData?.fcAccuracy;
|
const fcAccuracy = score.additionalData?.fcAccuracy;
|
||||||
const scoreBadge = getScoreBadgeFromAccuracy(score.accuracy);
|
const scoreBadge = getScoreBadgeFromAccuracy(score.accuracy);
|
||||||
@ -57,9 +56,13 @@ export function ScoreAccuracyBadge({ score, leaderboard }: ScoreAccuracyProps) {
|
|||||||
{modCount > 0 && <ScoreModifiers type="simple" limit={1} score={score} />}
|
{modCount > 0 && <ScoreModifiers type="simple" limit={1} score={score} />}
|
||||||
</p>
|
</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{scoreImprovement && previousAccuracy && (
|
{previousScore && previousScore.change && (
|
||||||
<Tooltip display={`Previous Accuracy: ${previousAccuracy.toFixed(2)}%`}>
|
<Tooltip display={`Previous Accuracy: ${previousScore.accuracy.toFixed(2)}%`}>
|
||||||
<Change className="text-xs" change={scoreImprovement.accuracy} formatValue={num => `${num.toFixed(2)}%`} />
|
<Change
|
||||||
|
className="text-xs"
|
||||||
|
change={previousScore.change.accuracy}
|
||||||
|
formatValue={num => `${num.toFixed(2)}%`}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,6 @@ import Tooltip from "@/components/tooltip";
|
|||||||
import { ensurePositiveNumber, formatPp } from "@ssr/common/utils/number-utils";
|
import { ensurePositiveNumber, formatPp } from "@ssr/common/utils/number-utils";
|
||||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||||
import { Change } from "@/common/change";
|
import { Change } from "@/common/change";
|
||||||
import { Warning } from "@/components/warning";
|
|
||||||
|
|
||||||
type ScorePpProps = ScoreBadgeProps & {
|
type ScorePpProps = ScoreBadgeProps & {
|
||||||
/**
|
/**
|
||||||
@ -14,8 +13,7 @@ type ScorePpProps = ScoreBadgeProps & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
||||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
const previousScore = score.previousScore;
|
||||||
const previousAccuracy = scoreImprovement ? score.accuracy - scoreImprovement?.accuracy : undefined;
|
|
||||||
const fcAccuracy = score.additionalData?.fcAccuracy;
|
const fcAccuracy = score.additionalData?.fcAccuracy;
|
||||||
const pp = score.pp;
|
const pp = score.pp;
|
||||||
const weight = score.weight;
|
const weight = score.weight;
|
||||||
@ -28,6 +26,7 @@ export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex flex-col items-center justify-center cursor-default">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
display={
|
display={
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@ -39,28 +38,17 @@ export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
|
|||||||
</p>
|
</p>
|
||||||
{fcPp && <p>Full Combo: {fcPp}pp</p>}
|
{fcPp && <p>Full Combo: {fcPp}pp</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{previousAccuracy && (
|
|
||||||
<Warning>
|
|
||||||
<p className="text-red-700">
|
|
||||||
The previous pp may not be 100% accurate due to ScoreSaber API limitations.
|
|
||||||
</p>
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center justify-center cursor-default">
|
|
||||||
<p>{formatPp(pp)}pp</p>
|
<p>{formatPp(pp)}pp</p>
|
||||||
{previousAccuracy && (
|
</Tooltip>
|
||||||
<Change
|
{previousScore && previousScore.change && (
|
||||||
className="text-xs"
|
<Tooltip display={<p>Previous PP: {formatPp(previousScore.pp)}pp</p>}>
|
||||||
change={ensurePositiveNumber(pp - scoresaberService.getPp(leaderboard.stars, previousAccuracy))}
|
<Change className="text-xs" change={ensurePositiveNumber(previousScore.change.pp)} isPp />
|
||||||
isPp
|
</Tooltip>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
|
import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
|
||||||
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
|
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
|
||||||
import { Change } from "@/common/change";
|
import { Change } from "@/common/change";
|
||||||
|
import Tooltip from "@/components/tooltip";
|
||||||
|
|
||||||
export function ScoreScoreBadge({ score }: ScoreBadgeProps) {
|
export function ScoreScoreBadge({ score }: ScoreBadgeProps) {
|
||||||
const scoreImprovement = score.additionalData?.scoreImprovement;
|
const previousScore = score.previousScore;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
<p>{formatNumberWithCommas(Number(score.score.toFixed(0)))}</p>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user