import { useEffect, useState } from "react"; import { selectedCharacter, useShellStore } from "../app/shellStore"; import { installDungeonShellRuntime } from "../app/dungeonShellRuntime"; import { useOnlineGroupStore } from "../app/onlineGroupStore"; import { specializationById } from "../game/specializationCatalog"; import { partyRolesForClass, PARTY_ROLE_LABELS, type PartyRole } from "../game/partyStore"; import { dungeonContentIsUsable, installContentPack } from "../content/contentManager"; import { useContentManagerSnapshot } from "../content/useContentManager"; import { RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID, dungeonContentPackId, } from "../game/dungeonContentSource"; import { dungeonById, randomRandomizedBiteSizedDungeon } from "../game/dungeonCatalog"; import { dungeonEnabledForQueue } from "../game/dungeonQueuePolicy"; import { loadDungeonQueuePolicy, useDungeonQueuePolicyStore } from "../app/dungeonQueuePolicyStore"; import { RANDOMIZED_BITE_SIZED_CREATURE_CATALOG, requireDungeonDefinition } from "../game/dungeonRegistry"; import { BrandMark, FrontSurface } from "./FrontSurface"; import { DungeonDifficultySlider } from "./DungeonDifficultySlider"; import { OnlineGroupPanel } from "./OnlineGroupPanel"; import { CompanionPoolControls } from "./CompanionPoolControls"; import { CompanionReadinessScreen } from "./CompanionReadinessScreen"; import { ensureRandomizedBiteSizedRunContent } from "../app/randomizedBiteSizedContent"; installDungeonShellRuntime(); const DUNGEON = dungeonById(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID); const CANDIDATES = RANDOMIZED_BITE_SIZED_CREATURE_CATALOG; export function RandomizedBiteSizedScreen({ onClose }: { readonly onClose: () => void }) { useContentManagerSnapshot(); const activeCharacter = useShellStore((state) => state.activeCharacter); const rosterCharacter = useShellStore(selectedCharacter); const enterDungeon = useShellStore((state) => state.enterDungeon); const session = useShellStore((state) => state.session); const notice = useShellStore((state) => state.notice); const onlineGroup = useOnlineGroupStore((state) => state.snapshot.group); const updateOnlineMember = useOnlineGroupStore((state) => state.updateMember); const startOnlineActivity = useOnlineGroupStore((state) => state.startActivity); const [bossDifficulty, setBossDifficulty] = useState(1); const [downloading, setDownloading] = useState(false); const [error, setError] = useState(""); const [readinessOpen, setReadinessOpen] = useState(false); const queuePolicy = useDungeonQueuePolicyStore((state) => state.config); const refreshQueuePolicy = useDungeonQueuePolicyStore((state) => state.refresh); useEffect(() => { void refreshQueuePolicy(session); }, [refreshQueuePolicy, session]); const character = activeCharacter ?? rosterCharacter; const specialization = specializationById(character?.specializationId); const roles = character ? character.categoryId === "rom" ? partyRolesForClass(character.classId) : specialization ? [specialization.role] : [] : []; const installed = dungeonContentIsUsable(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID); const mobCount = CANDIDATES.filter((candidate) => candidate.kind === "mob").length; const bossCount = CANDIDATES.filter((candidate) => candidate.kind === "boss").length; const queueEnabled = Boolean(DUNGEON && dungeonEnabledForQueue(queuePolicy, DUNGEON)); const launch = async (role: PartyRole) => { if (!character || !DUNGEON?.available) return; const policy = await loadDungeonQueuePolicy(); const selectedDungeon = randomRandomizedBiteSizedDungeon(undefined, Math.random, (candidate) => dungeonEnabledForQueue(policy, candidate)); if (!selectedDungeon) { setError("No Randomized Bite-Sized location is enabled for this queue."); return; } if (!onlineGroup || !session || session.kind !== "account") { const runId = globalThis.crypto.randomUUID(); setDownloading(true); setError(""); try { await ensureRandomizedBiteSizedRunContent(runId); enterDungeon(selectedDungeon.id, role, { difficultyId: requireDungeonDefinition(selectedDungeon.id).defaultDifficultyId, bossDifficulty, freshRun: true, runId, }); } catch (caught) { setError(caught instanceof Error ? caught.message : "The randomized creature download failed."); } finally { setDownloading(false); } return; } if (!await updateOnlineMember(session, character, role)) return; if (onlineGroup.leaderAccountId !== session.ownerId) { useShellStore.getState().setNotice(`Ready as ${PARTY_ROLE_LABELS[role]}. Waiting for the group leader to start.`); return; } await startOnlineActivity(session, { type: "dungeon", contentId: selectedDungeon.id, difficultyId: requireDungeonDefinition(selectedDungeon.id).defaultDifficultyId, bossDifficulty, partySize: 5, fillWithAi: true, includeBetaSpecs: false, }); }; const install = async () => { setDownloading(true); setError(""); try { await installContentPack(dungeonContentPackId(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID)); if (!dungeonContentIsUsable(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID)) { throw new Error("The Stockades content pack could not be verified."); } } catch (caught) { setError(caught instanceof Error ? caught.message : "The content download failed."); } finally { setDownloading(false); } }; if (readinessOpen) return setReadinessOpen(false)} />; return (
Main menu / Randomized Bite-SizedCellblock Shuffle

A different lineup every run

Randomized Bite-Sized

The mode picks an eligible short layout—currently Cellblock Rush—deals a different cross-dungeon creature into every enemy slot, then rolls the boss separately.

{mobCount}mob types in the shuffle
{bossCount}possible final bosses
1boss per run

The random lineup is locked to the run seed, so online party members and resumed sessions see the same enemies.

setReadinessOpen(true)} readyOnly />

Run setup

{queueEnabled ? DUNGEON?.title ?? "Cellblock Shuffle" : "No enabled location"}

undefined} /> {!queueEnabled ? null : !installed ? ( ) : roles.length ? (
Enter as {roles.map((role) => )}
) :

Choose a specialization in your Class Hall before entering.

} {!DUNGEON?.available &&

This randomized route is not enterable in the current content build.

} {!queueEnabled &&

No Randomized Bite-Sized location is enabled in GM queue settings.

} {(error || notice) &&

{error || notice}

}
); }