import catalog from "../game/dungeonRaceBotCatalog.json"; export type RaceBotTier = "novice" | "regular" | "veteran" | "elite"; export type RaceBotSource = "wow" | "rom"; export interface RaceBot { readonly type: "bot"; readonly name: string; readonly tier: RaceBotTier; readonly source: RaceBotSource; readonly roster: readonly { readonly name: string; readonly classId: string; readonly role: string }[]; readonly finishMs: number; readonly milestones: readonly { readonly atMs: number; readonly progress: number; readonly label: string }[]; } export function hashRaceSeed(value: string): number { let hash = 2166136261; for (const char of value) { hash ^= char.charCodeAt(0); hash = Math.imul(hash, 16777619); } return hash >>> 0; } export function createRaceBot(seed: string, tierId: RaceBotTier, source: RaceBotSource): RaceBot { const tier = catalog.tiers.find((entry) => entry.id === tierId) ?? catalog.tiers[1]; const variance = 0.92 + (hashRaceSeed(`${seed}:pace`) % 161) / 1000; return { type: "bot", name: `${tier.label} ${source === "rom" ? "RuneWaker" : "Azeroth"} Team`, tier: tier.id as RaceBotTier, source, roster: catalog.teams[source], finishMs: Math.round(catalog.baselineMs * tier.multiplier * variance), milestones: catalog.milestones.map((entry) => ({ ...entry, atMs: Math.round(catalog.baselineMs * tier.multiplier * variance * entry.at) })) }; } export function raceBotProgress(elapsedMs: number, bot: RaceBot): number { 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)); }