SpaceNinjaServer/src/services/leaderboardService.ts
AMelonInsideLemon d0743654dd feat: orphix venom (#2637)
Without rotation on last mission
Re #1103
Thanks to https://wiki.warframe.com/w/World_State/Example

Reviewed-on: OpenWF/SpaceNinjaServer#2637
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-16 09:52:58 -07:00

126 lines
4.5 KiB
TypeScript

import { Guild } from "@/src/models/guildModel";
import { Leaderboard, TLeaderboardEntryDocument } from "@/src/models/leaderboardModel";
import { ILeaderboardEntryClient } from "@/src/types/leaderboardTypes";
import { handleGuildGoalProgress } from "@/src/services/guildService";
import { getWorldState } from "@/src/services/worldStateService";
import { Types } from "mongoose";
export const submitLeaderboardScore = async (
schedule: "weekly" | "daily" | "events",
leaderboard: string,
ownerId: string,
displayName: string,
score: number,
guildId: string | undefined
): Promise<void> => {
let expiry: Date | undefined;
if (schedule == "daily") {
expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000);
} else if (schedule == "weekly") {
const EPOCH = 1734307200 * 1000; // Monday
const week = Math.trunc((Date.now() - EPOCH) / 604800000);
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
expiry = new Date(weekEnd);
}
if (guildId) {
const guild = (await Guild.findById(guildId, "Name Tier GoalProgress VaultDecoRecipes"))!;
if (schedule == "events") {
const prevAccount = await Leaderboard.findOne(
{ leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
"score"
);
const delta = score - (prevAccount?.score ?? 0);
if (delta > 0) {
await Leaderboard.findOneAndUpdate(
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
{ $inc: { score: delta }, $set: { displayName: guild.Name, guildTier: guild.Tier } },
{ upsert: true }
);
const goal = getWorldState().Goals.find(x => x.ScoreMaxTag == leaderboard);
if (goal) {
await handleGuildGoalProgress(guild, {
Count: delta,
Tag: goal.Tag,
goalId: new Types.ObjectId(goal._id.$oid)
});
}
}
} else {
await Leaderboard.findOneAndUpdate(
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
{ $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } },
{ upsert: true, new: true }
);
}
}
await Leaderboard.findOneAndUpdate(
{ leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
{ $max: { score }, $set: { displayName, guildId, expiry } },
{ upsert: true }
);
};
export const getLeaderboard = async (
leaderboard: string,
before: number,
after: number,
pivotId: string | undefined,
guildId: string | undefined,
guildTier: number | undefined
): Promise<ILeaderboardEntryClient[]> => {
leaderboard = leaderboard.replace("archived", guildTier || guildId ? "events.guilds" : "events.accounts");
const filter: { leaderboard: string; guildId?: string; guildTier?: number } = { leaderboard };
if (guildId) {
filter.guildId = guildId;
}
if (guildTier) {
filter.guildTier = guildTier;
}
let entries: TLeaderboardEntryDocument[];
let r: number;
if (pivotId) {
const pivotDoc = await Leaderboard.findOne({ ...filter, ownerId: pivotId });
if (!pivotDoc) {
return [];
}
const beforeDocs = await Leaderboard.find({
...filter,
score: { $gt: pivotDoc.score }
})
.sort({ score: 1 })
.limit(before);
const afterDocs = await Leaderboard.find({
...filter,
score: { $lt: pivotDoc.score }
})
.sort({ score: -1 })
.limit(after);
entries = [...beforeDocs.reverse(), pivotDoc, ...afterDocs];
r =
(await Leaderboard.countDocuments({
...filter,
score: { $gt: pivotDoc.score }
})) - beforeDocs.length;
} else {
entries = await Leaderboard.find(filter)
.sort({ score: -1 })
.skip(before)
.limit(after - before);
r = before;
}
const res: ILeaderboardEntryClient[] = [];
for (const entry of entries) {
res.push({
_id: entry.ownerId.toString(),
s: entry.score,
r: ++r,
n: entry.displayName
});
}
return res;
};