61 lines
2.8 KiB
JavaScript
61 lines
2.8 KiB
JavaScript
import catalog from "../src/game/generated/dungeonLeaderboardCatalog.generated.json" with { type: "json" };
|
|
import bots from "../src/game/dungeonRaceBotCatalog.json" with { type: "json" };
|
|
|
|
export const RACE_QUEUE_WAIT_MS = 5_000;
|
|
export const RACE_PREPARE_MS = 5 * 60_000;
|
|
export const RACE_COUNTDOWN_MS = 3_000;
|
|
export const RACE_DISCONNECT_MS = 30_000;
|
|
export const RACE_MAX_MS = 2 * 60 * 60_000;
|
|
export const RACE_PERIOD_MS = 30 * 24 * 60 * 60_000;
|
|
|
|
export function racePoolForDungeon(id) {
|
|
if (id.startsWith("randomized-bite-sized-")) return "randomized";
|
|
return id.startsWith("bite-sized-") ? "authored" : null;
|
|
}
|
|
|
|
export function raceDungeons(pool, policy) {
|
|
const disabled = new Set(policy?.disabled?.[pool === "randomized" ? "randomizedBiteSized" : "biteSized"] ?? []);
|
|
return catalog.dungeons.filter((dungeon) => racePoolForDungeon(dungeon.id) === pool
|
|
&& dungeon.difficulties.some((difficulty) => difficulty.finalBossId) && !disabled.has(dungeon.id));
|
|
}
|
|
|
|
export function compatibleDungeons(left, right, policy) {
|
|
if (left.pool !== right.pool) return [];
|
|
return raceDungeons(left.pool, policy).filter((dungeon) =>
|
|
(!left.dungeonId || left.dungeonId === dungeon.id) && (!right.dungeonId || right.dungeonId === dungeon.id));
|
|
}
|
|
|
|
export function hashRaceSeed(value) {
|
|
let hash = 2166136261;
|
|
for (const char of value) { hash ^= char.charCodeAt(0); hash = Math.imul(hash, 16777619); }
|
|
return hash >>> 0;
|
|
}
|
|
|
|
export function botTierForRecord(wins, losses, seed) {
|
|
const total = wins + losses;
|
|
const rate = total < 5 ? 0.5 : wins / total;
|
|
const base = rate < 0.4 ? 0 : rate < 0.6 ? 1 : rate < 0.8 ? 2 : 3;
|
|
const roll = hashRaceSeed(seed) % 4;
|
|
return bots.tiers[Math.max(0, Math.min(3, base + (roll === 0 ? -1 : roll === 3 ? 1 : 0)))].id;
|
|
}
|
|
|
|
export function botOpponent(seed, tierId, source = null) {
|
|
const tier = bots.tiers.find((entry) => entry.id === tierId) ?? bots.tiers[1];
|
|
const resolvedSource = source ?? (hashRaceSeed(`${seed}:source`) % 2 ? "rom" : "wow");
|
|
const variance = 0.92 + (hashRaceSeed(`${seed}:pace`) % 161) / 1000;
|
|
const finishMs = Math.round(bots.baselineMs * tier.multiplier * variance);
|
|
return { type: "bot", name: `${tier.label} ${resolvedSource === "rom" ? "RuneWaker" : "Azeroth"} Team`,
|
|
tier: tier.id, source: resolvedSource, roster: bots.teams[resolvedSource], finishMs,
|
|
milestones: bots.milestones.map((entry) => ({ ...entry, atMs: Math.round(finishMs * entry.at) })) };
|
|
}
|
|
|
|
export function botProgress(elapsedMs, bot) {
|
|
if (elapsedMs <= 0) return 0;
|
|
const previous = [{ atMs: 0, progress: 0 }, ...bot.milestones]
|
|
.filter((entry) => entry.atMs <= elapsedMs).at(-1);
|
|
const next = bot.milestones.find((entry) => entry.atMs > elapsedMs);
|
|
if (!next) return 100;
|
|
return Math.floor(previous.progress + (next.progress - previous.progress)
|
|
* (elapsedMs - previous.atMs) / (next.atMs - previous.atMs));
|
|
}
|