Files
healer-man/src/app/dungeonRaceBot.ts
T
2026-09-26 17:51:56 -04:00

40 lines
1.8 KiB
TypeScript

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));
}