import type { CharacterProfile, PlayerSession } from "./types"; import type { OnlineGroup, StartOnlineGroupActivityInput } from "./onlineGroupTypes"; import { dungeonContentIsUsable, installContentPack } from "../content/contentManager"; import { dungeonContentPackId } from "../game/dungeonContentSource"; import { randomDungeonByCategory, randomRegularDungeon, type DungeonCatalogEntry } from "../game/dungeonCatalog"; import { dungeonDefinitionById } from "../game/dungeonRegistry"; import { classCanFillPartyRole, type PartyRole } from "../game/partyRoles"; import { specializationById } from "../game/specializationCatalog"; import type { DungeonEntrySettings } from "./dungeonShellRuntimeBridge"; export type RandomDungeonQueueMode = "regular" | "bite-sized"; export type RandomDungeonQueueStage = "rolling" | "downloading" | "entering"; export interface RandomDungeonQueueOptions { readonly mode: RandomDungeonQueueMode; readonly role: PartyRole; readonly character: CharacterProfile | null; readonly session: PlayerSession | null; readonly group: OnlineGroup | null; readonly includeBetaSpecs: boolean; readonly fillWithAi: boolean; readonly onProgress: (stage: RandomDungeonQueueStage, dungeon: DungeonCatalogEntry | null) => void; readonly enterDungeon: (dungeonId: string, role: PartyRole, settings: DungeonEntrySettings) => boolean; readonly updateOnlineMember: (session: PlayerSession, character: CharacterProfile, role: PartyRole) => Promise; readonly startOnlineActivity: (session: PlayerSession, input: StartOnlineGroupActivityInput) => Promise; readonly failureNotice: () => string; } interface RandomDungeonQueueDependencies { readonly random: () => number; readonly contentIsUsable: (dungeonId: string) => boolean; readonly installPack: (packId: string) => Promise; readonly installDungeonRuntime: () => Promise; } const DEFAULT_DEPENDENCIES: RandomDungeonQueueDependencies = { random: Math.random, contentIsUsable: dungeonContentIsUsable, installPack: installContentPack, installDungeonRuntime: async () => { const { installDungeonShellRuntime } = await import("./dungeonShellRuntime"); installDungeonShellRuntime(); }, }; export type RandomDungeonQueueResult = | { readonly status: "entered" | "waiting" | "started"; readonly dungeonTitle?: string } | { readonly status: "error"; readonly message: string }; /** Rolls only after Queue is pressed; downloads the selected map before local or shared entry. */ export async function queueRandomDungeon( options: RandomDungeonQueueOptions, dependencies: RandomDungeonQueueDependencies = DEFAULT_DEPENDENCIES, ): Promise { const { character, session, group, role } = options; if (!character || !session) return { status: "error", message: "Select a character before queuing." }; const specialization = specializationById(character.specializationId); if ((character.categoryId !== "rom" && specialization?.role !== role) || !classCanFillPartyRole(character.classId, role)) { return { status: "error", message: "That role is unavailable for this character." }; } if (group && (session.kind !== "account" || !session.accessToken)) { return { status: "error", message: "Reconnect before queuing with your group." }; } try { if (group) { if (!await options.updateOnlineMember(session, character, role)) { return { status: "error", message: options.failureNotice() || "The group role could not be updated." }; } if (group.leaderAccountId !== session.ownerId) return { status: "waiting" }; } options.onProgress("rolling", null); const dungeon = options.mode === "regular" ? randomRegularDungeon(undefined, dependencies.random) : randomDungeonByCategory("bite-sized", undefined, dependencies.random); if (!dungeon) return { status: "error", message: "No dungeon is currently ready for this queue." }; if (!dependencies.contentIsUsable(dungeon.id)) { options.onProgress("downloading", dungeon); await dependencies.installPack(dungeonContentPackId(dungeon.id)); if (!dependencies.contentIsUsable(dungeon.id)) { return { status: "error", message: `${dungeon.title} could not be verified after downloading.` }; } } const difficultyId = dungeonDefinitionById(dungeon.id)?.defaultDifficultyId; options.onProgress("entering", dungeon); if (group) { const started = await options.startOnlineActivity(session, { type: "dungeon", contentId: dungeon.id, difficultyId, bossDifficulty: 1, partySize: 5, fillWithAi: options.fillWithAi, includeBetaSpecs: options.includeBetaSpecs, }); return started ? { status: "started", dungeonTitle: dungeon.title } : { status: "error", message: options.failureNotice() || "The group dungeon could not be started." }; } await dependencies.installDungeonRuntime(); const entered = options.enterDungeon(dungeon.id, role, { difficultyId, bossDifficulty: 1, freshRun: true, }); return entered ? { status: "entered", dungeonTitle: dungeon.title } : { status: "error", message: options.failureNotice() || `Could not enter ${dungeon.title}.` }; } catch (error) { return { status: "error", message: error instanceof Error ? error.message : "The random dungeon queue failed." }; } }