mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 09:20:24 -05:00
173 lines
4.8 KiB
TypeScript
173 lines
4.8 KiB
TypeScript
import { cachified } from "@epic-web/cachified";
|
|
import * as Seasons from "~/features/mmr/core/Seasons";
|
|
import { USER_LEADERBOARD_MIN_ENTRIES_FOR_LEVIATHAN } from "~/features/mmr/mmr-constants";
|
|
import { freshUserSkills, userSkills } from "~/features/mmr/tiered.server";
|
|
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
|
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
|
import { weaponCategories } from "~/modules/in-game-lists/weapon-ids";
|
|
import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server";
|
|
import type { Unwrapped } from "~/utils/types";
|
|
import { DEFAULT_LEADERBOARD_MAX_SIZE } from "../leaderboards-constants";
|
|
import { seasonHasTopTen } from "../leaderboards-utils";
|
|
import type { SeasonPopularUsersWeapon } from "../queries/seasonPopularUsersWeapon.server";
|
|
import { seasonPopularUsersWeapon } from "../queries/seasonPopularUsersWeapon.server";
|
|
import type { UserSPLeaderboardItem } from "../queries/userSPLeaderboard.server";
|
|
import { userSPLeaderboard } from "../queries/userSPLeaderboard.server";
|
|
|
|
export type UserLeaderboardWithAdditionsItem = Unwrapped<
|
|
typeof cachedFullUserLeaderboard
|
|
>;
|
|
export async function cachedFullUserLeaderboard(season: number) {
|
|
return cachified({
|
|
key: `user-leaderboard-season-${season}`,
|
|
cache,
|
|
ttl: ttl(IN_MILLISECONDS.HALF_HOUR),
|
|
async getFreshValue() {
|
|
const leaderboard = userSPLeaderboard(season);
|
|
const withTiers = addTiers(leaderboard, season);
|
|
|
|
const shouldAddPendingPlusTier =
|
|
season === Seasons.current()?.nth &&
|
|
leaderboard.length >= USER_LEADERBOARD_MIN_ENTRIES_FOR_LEVIATHAN;
|
|
const withPendingPlusTiers = shouldAddPendingPlusTier
|
|
? addPendingPlusTiers(
|
|
withTiers,
|
|
await UserRepository.findAllPlusServerMembers(),
|
|
season,
|
|
)
|
|
: withTiers;
|
|
|
|
return addWeapons(withPendingPlusTiers, seasonPopularUsersWeapon(season));
|
|
},
|
|
});
|
|
}
|
|
|
|
function addTiers(entries: UserSPLeaderboardItem[], season: number) {
|
|
const tiers = freshUserSkills(season);
|
|
|
|
const encounteredTiers = new Set<string>();
|
|
return entries.map((entry, i) => {
|
|
const tier = tiers.userSkills[entry.id].tier;
|
|
if (i < 10 && seasonHasTopTen(season)) {
|
|
return { ...entry, tier, firstOfTier: undefined };
|
|
}
|
|
|
|
const tierKey = `${tier.name}${tier.isPlus ? "+" : ""}`;
|
|
const tierAlreadyEncountered = encounteredTiers.has(tierKey);
|
|
if (!tierAlreadyEncountered) {
|
|
encounteredTiers.add(tierKey);
|
|
}
|
|
|
|
return {
|
|
...entry,
|
|
tier,
|
|
firstOfTier: !tierAlreadyEncountered ? tier : undefined,
|
|
};
|
|
});
|
|
}
|
|
|
|
const PLUS_TIER_QUOTA = {
|
|
"+1": 5,
|
|
"+2": 10,
|
|
"+3": 15,
|
|
} as const;
|
|
export function addPendingPlusTiers<T extends UserSPLeaderboardItem>(
|
|
entries: T[],
|
|
plusTiers: Array<{
|
|
userId: number;
|
|
plusTier: number;
|
|
}>,
|
|
seasonNth: number,
|
|
) {
|
|
const quota: { "+1": number; "+2": number; "+3": number } = {
|
|
...PLUS_TIER_QUOTA,
|
|
};
|
|
|
|
const resolveHighestPlusTierWithSpace = () => {
|
|
if (quota["+1"] > 0) return 1;
|
|
if (quota["+2"] > 0) return 2;
|
|
if (quota["+3"] > 0) return 3;
|
|
|
|
return null;
|
|
};
|
|
|
|
for (const entry of entries) {
|
|
const highestPlusTierWithSpace = resolveHighestPlusTierWithSpace();
|
|
if (!highestPlusTierWithSpace) break;
|
|
|
|
const plusTier = plusTiers.find((t) => t.userId === entry.id)?.plusTier;
|
|
|
|
if (plusTier && plusTier <= highestPlusTierWithSpace) continue;
|
|
if (entry.plusSkippedForSeasonNth === seasonNth) {
|
|
entry.plusSkippedForSeasonNth = null;
|
|
continue;
|
|
}
|
|
|
|
entry.pendingPlusTier = highestPlusTierWithSpace;
|
|
const key = `+${highestPlusTierWithSpace}` as const;
|
|
quota[key] -= 1;
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
function addWeapons<T extends { id: number }>(
|
|
entries: T[],
|
|
weapons: SeasonPopularUsersWeapon,
|
|
) {
|
|
return entries.map((entry) => {
|
|
const weaponSplId = weapons[entry.id] as MainWeaponId | undefined;
|
|
return {
|
|
...entry,
|
|
weaponSplId,
|
|
};
|
|
});
|
|
}
|
|
|
|
export function filterByWeaponCategory<
|
|
T extends { weaponSplId?: MainWeaponId },
|
|
>(entries: Array<T>, category: (typeof weaponCategories)[number]["name"]) {
|
|
const weaponIdsOfCategory = new Set(
|
|
weaponCategories.find((c) => c.name === category)!.weaponIds,
|
|
);
|
|
|
|
return entries.filter(
|
|
(entry) => entry.weaponSplId && weaponIdsOfCategory.has(entry.weaponSplId),
|
|
);
|
|
}
|
|
|
|
export function ownEntryPeek({
|
|
leaderboard,
|
|
userId,
|
|
season,
|
|
}: {
|
|
leaderboard: UserSPLeaderboardItem[];
|
|
userId: number;
|
|
season: number;
|
|
}) {
|
|
const found = leaderboard.find(
|
|
(entry) =>
|
|
entry.id === userId && entry.placementRank > DEFAULT_LEADERBOARD_MAX_SIZE,
|
|
);
|
|
|
|
if (!found) return null;
|
|
|
|
const withTier = addTiers([found], season)[0];
|
|
|
|
const { intervals } = userSkills(season);
|
|
|
|
const currentTierIndex = intervals.findIndex(
|
|
(interval) =>
|
|
interval.name === withTier.tier.name &&
|
|
interval.isPlus === withTier.tier.isPlus,
|
|
);
|
|
|
|
const nextTier =
|
|
currentTierIndex > 0 ? intervals[currentTierIndex - 1] : undefined;
|
|
|
|
return {
|
|
entry: withTier,
|
|
nextTier,
|
|
};
|
|
}
|