Update Healer Man server
This commit is contained in:
1 parent
97ee0c13f9
commit
f864b5aea7
20 files changed
+468
-211
No files matched your search
@@ -103,11 +103,13 @@ describe("browser Downloads actions", () => {
|
||||
...content.packs,
|
||||
{ id: "core:arenas", label: "Rated Arenas", bytes: 1, fileCount: 1, bundledOnAndroid: false, dependencies: [] },
|
||||
{ id: "manastorm:deadmines", label: "Deadmines Manastorm", bytes: 1, fileCount: 1, bundledOnAndroid: false, dependencies: [] },
|
||||
{ id: "creatures:deadmines", label: "Deadmines Creatures", bytes: 1, fileCount: 1, bundledOnAndroid: false, dependencies: [] },
|
||||
];
|
||||
const markup = renderToStaticMarkup(createElement(ContentManagerScreen, { onClose: () => undefined }));
|
||||
|
||||
expect(markup).toContain("Test Characters");
|
||||
expect(markup).not.toContain("Rated Arenas");
|
||||
expect(markup).not.toContain("Deadmines Manastorm");
|
||||
expect(markup).not.toContain("Deadmines Creatures");
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import { awaitSharedDungeonRunPlacement, localDungeonRunPlacement, saveLocalDung
|
||||
import { useOnlineSessionStore } from "../app/onlineSessionStore";
|
||||
import { startAnotherBiteSizedDungeon } from "../app/biteSizedDungeonRuntime";
|
||||
import { dungeonById } from "../game/dungeonCatalog";
|
||||
import { isRandomizedBiteSizedDungeonId } from "../game/dungeonContentSource";
|
||||
import { completedDungeonRun, useDungeonRunStore } from "../game/dungeonRunStore";
|
||||
import { formatDungeonDuration, type DungeonRunPlacement, type DungeonRunProgress } from "../game/dungeonRunTypes";
|
||||
import catalog from "../game/generated/dungeonLeaderboardCatalog.generated.json";
|
||||
@@ -35,6 +36,7 @@ export function DungeonRunResultsDialog({ progress, ranking, onFinish, onLoot, o
|
||||
const definition = catalog.dungeons.find((dungeon) => dungeon.id === progress.dungeonId);
|
||||
const difficulty = definition?.difficulties.find((entry) => entry.id === progress.difficultyId);
|
||||
const fullClear = difficulty ? isLeaderboardFullClear(difficulty, progress.killedBossIds) : false;
|
||||
const randomizedBiteSized = isRandomizedBiteSizedDungeonId(progress.dungeonId);
|
||||
const controller = useMenuController([
|
||||
...(onAnother ? [{ id: "run-another", run: onAnother, enabled: !startingAnother }] : []),
|
||||
{ id: "run-loot", run: onLoot }, { id: "run-finish", run: onFinish },
|
||||
@@ -78,7 +80,9 @@ export function DungeonRunResultsDialog({ progress, ranking, onFinish, onLoot, o
|
||||
<footer>
|
||||
{onAnother ? <ControllerButton {...controls} controlId="run-another" className="button button--primary"
|
||||
disabled={startingAnother} onClick={onAnother}>
|
||||
{startingAnother ? "Starting next dungeon…" : "Another random Bite-Sized dungeon"}
|
||||
{startingAnother ? "Starting next dungeon…" : randomizedBiteSized
|
||||
? "Another Randomized Bite-Sized dungeon"
|
||||
: "Another random Bite-Sized dungeon"}
|
||||
</ControllerButton> : null}
|
||||
<ControllerButton {...controls} controlId="run-loot" className="button" onClick={onLoot}>Stay and loot</ControllerButton>
|
||||
<ControllerButton {...controls} controlId="run-finish" className={`button ${onAnother ? "" : "button--primary"}`} onClick={onFinish}>Return to main menu</ControllerButton>
|
||||
|
||||
+127
-140
@@ -106,6 +106,7 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
readonly onOpenRandomizedBiteSized: () => void;
|
||||
readonly onOpenEditor: (editor: MainMenuEditor) => void;
|
||||
}) {
|
||||
const [gmMenuOpen, setGmMenuOpen] = useState(false);
|
||||
const activeCharacter = useShellStore((state) => state.activeCharacter);
|
||||
const rosterCharacter = useShellStore(selectedCharacter);
|
||||
const openDungeons = useShellStore((state) => state.openDungeons);
|
||||
@@ -120,35 +121,23 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
const isRom = character?.categoryId === "rom";
|
||||
const isGm = sessionCanUseGmTools(session);
|
||||
|
||||
const actions = useMemo<MenuAction[]>(() => [
|
||||
const actions = useMemo<MenuAction[]>(() => gmMenuOpen ? [
|
||||
...(isGm ? [{ id: "gm-menu-gm-tools", run: openGmAdmin, neighbors: { up: "gm-menu-close" } }] : []),
|
||||
{ id: "gm-menu-boss-testing", run: onOpenBossTesting, ...(!isGm ? { neighbors: { up: "gm-menu-close" } } : {}) },
|
||||
{ id: "gm-menu-companion-readiness", run: onOpenReadiness },
|
||||
...(isGm ? [{ id: "gm-menu-balance-lab", run: onOpenBalanceLab }] : []),
|
||||
{ id: "gm-menu-reports", run: onOpenReports },
|
||||
{ id: "gm-menu-close", run: () => setGmMenuOpen(false), neighbors: { down: isGm ? "gm-menu-gm-tools" : "gm-menu-boss-testing" } },
|
||||
] : [
|
||||
{
|
||||
id: "mode-dungeons",
|
||||
run: openDungeons,
|
||||
neighbors: { right: "mode-randomized-bite-sized", down: "back-to-roster" },
|
||||
neighbors: { up: "back-to-roster", right: "mode-randomized-bite-sized" },
|
||||
},
|
||||
{
|
||||
id: "mode-randomized-bite-sized",
|
||||
run: onOpenRandomizedBiteSized,
|
||||
neighbors: { left: "mode-dungeons", right: SHELVED_MODES_ENABLED ? "mode-manastorms" : "mode-downloads", down: "back-to-roster" },
|
||||
},
|
||||
...(SHELVED_MODES_ENABLED ? [{
|
||||
id: "mode-manastorms",
|
||||
run: openManastorms,
|
||||
neighbors: { left: "mode-randomized-bite-sized", down: "back-to-roster" },
|
||||
}, {
|
||||
id: "mode-arenas",
|
||||
run: openArenas,
|
||||
neighbors: { left: "mode-manastorms", down: "back-to-roster" },
|
||||
}] : []),
|
||||
{
|
||||
id: "mode-downloads",
|
||||
run: onOpenDownloads,
|
||||
neighbors: { left: SHELVED_MODES_ENABLED ? "mode-manastorms" : "mode-dungeons", down: "back-to-roster" },
|
||||
},
|
||||
{
|
||||
id: "back-to-roster",
|
||||
run: returnToCharacters,
|
||||
neighbors: { up: "mode-dungeons", left: "mode-dungeons" },
|
||||
neighbors: { up: "back-to-roster", left: "mode-dungeons" },
|
||||
},
|
||||
...(character ? [
|
||||
{ id: "mode-class-hall", run: openClassHall },
|
||||
@@ -156,23 +145,59 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
{ id: "mode-keybinds", run: () => onOpenEditor("keybinds") },
|
||||
{ id: "mode-bindings", run: () => onOpenEditor("bindings") },
|
||||
] : []),
|
||||
...(isGm ? [
|
||||
{ id: "mode-gm-tools", run: openGmAdmin },
|
||||
{ id: "mode-balance-lab", run: onOpenBalanceLab },
|
||||
] : []),
|
||||
{ id: "mode-reports", run: onOpenReports },
|
||||
{ id: "mode-cards", run: onOpenCards },
|
||||
{ id: "mode-boss-testing", run: onOpenBossTesting },
|
||||
{ id: "mode-companion-readiness", run: onOpenReadiness },
|
||||
], [character, isGm, onOpenBalanceLab, onOpenBossTesting, onOpenCards, onOpenDownloads, onOpenEditor, onOpenRandomizedBiteSized, onOpenReadiness, onOpenReports, openArenas, openClassHall, openDungeons, openGmAdmin, openManastorms, returnToCharacters]);
|
||||
{ id: "mode-gm-menu", run: () => setGmMenuOpen(true) },
|
||||
...(SHELVED_MODES_ENABLED ? [
|
||||
{ id: "mode-manastorms", run: openManastorms },
|
||||
{ id: "mode-arenas", run: openArenas },
|
||||
] : []),
|
||||
{ id: "mode-downloads", run: onOpenDownloads },
|
||||
{ id: "back-to-roster", run: returnToCharacters, neighbors: { down: "mode-dungeons" } },
|
||||
], [character, gmMenuOpen, isGm, onOpenBalanceLab, onOpenBossTesting, onOpenCards, onOpenDownloads, onOpenEditor, onOpenRandomizedBiteSized, onOpenReadiness, onOpenReports, openArenas, openClassHall, openDungeons, openGmAdmin, openManastorms, returnToCharacters]);
|
||||
const controller = useMenuController(actions, {
|
||||
initialId: "mode-dungeons",
|
||||
onBack: returnToCharacters,
|
||||
columns: 2,
|
||||
initialId: gmMenuOpen ? (isGm ? "gm-menu-gm-tools" : "gm-menu-boss-testing") : "mode-dungeons",
|
||||
onBack: gmMenuOpen ? () => setGmMenuOpen(false) : returnToCharacters,
|
||||
});
|
||||
|
||||
const characterClass = character ? classById(character.classId) : null;
|
||||
const specialization = specializationById(character?.specializationId);
|
||||
const secondaryClass = character?.secondaryClassId ? classById(character.secondaryClassId) : null;
|
||||
const renderGameMode = (mode: (typeof GAME_MODES)[number]) => mode.available ? (
|
||||
<ControllerButton
|
||||
key={mode.id}
|
||||
controlId={`mode-${mode.id}`}
|
||||
selectedId={controller.selectedId}
|
||||
select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available"
|
||||
type="button"
|
||||
onClick={mode.id === "manastorms"
|
||||
? openManastorms
|
||||
: mode.id === "arenas"
|
||||
? openArenas
|
||||
: mode.id === "downloads"
|
||||
? onOpenDownloads
|
||||
: mode.id === "randomized-bite-sized"
|
||||
? onOpenRandomizedBiteSized
|
||||
: openDungeons}
|
||||
>
|
||||
<b aria-hidden="true">{mode.number}</b>
|
||||
<span><strong>{mode.name}</strong><small>{mode.description}</small></span>
|
||||
<em>Available</em>
|
||||
</ControllerButton>
|
||||
) : (
|
||||
<button
|
||||
key={mode.id}
|
||||
className="front-button main-menu-mode main-menu-mode--coming-soon"
|
||||
type="button"
|
||||
disabled
|
||||
aria-label={`${mode.name}, coming soon`}
|
||||
>
|
||||
<b aria-hidden="true">{mode.number}</b>
|
||||
<span><strong>{mode.name}</strong><small>{mode.description}</small></span>
|
||||
<em>Coming soon</em>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<DualDisplayFrame
|
||||
@@ -214,120 +239,82 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
|
||||
<main className="main-menu-modes">
|
||||
<nav className="main-menu-mode-grid" aria-label="Main menu destinations">
|
||||
<ControllerButton controlId="mode-companion-readiness" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={onOpenReadiness}>
|
||||
<b aria-hidden="true">CR</b><span><strong>Companion Readiness</strong><small>Review the certified NPC specialization pool and Playerbots identities.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-boss-testing" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={onOpenBossTesting}>
|
||||
<b aria-hidden="true">BT</b><span><strong>Boss Testing</strong><small>Choose a boss or fight a random encounter in an empty testing room.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
{character && <>
|
||||
<ControllerButton controlId="mode-talents" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("talents")}>
|
||||
<b aria-hidden="true">T</b><span><strong>{isRom ? "Skills" : "Talents"}</strong><small>{isRom ? "Review your primary, secondary, and elite skills." : "View your talent trees, spend points, or reset your build."}</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-keybinds" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("keybinds")}>
|
||||
<b aria-hidden="true">K</b><span><strong>Keybinds</strong><small>View and change keyboard or controller bindings.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-bindings" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("bindings")}>
|
||||
<b aria-hidden="true">A</b><span><strong>Button assignments</strong><small>Choose the abilities on your action bars and controller buttons.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
</>}
|
||||
<ControllerButton controlId="mode-cards" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={onOpenCards}>
|
||||
<b aria-hidden="true">◆</b><span><strong>Boss Cards</strong><small>Track your collection, rarities, and copies from every dungeon.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-reports" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={onOpenReports}>
|
||||
<b aria-hidden="true">!</b>
|
||||
<span><strong>View Reports</strong><small>Browse dungeon issues and follow their status.</small></span>
|
||||
<em>Open</em>
|
||||
</ControllerButton>
|
||||
{isGm && (
|
||||
<div className="main-menu-gm-stack">
|
||||
<ControllerButton
|
||||
controlId="mode-gm-tools"
|
||||
selectedId={controller.selectedId}
|
||||
select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available main-menu-mode--gm"
|
||||
type="button"
|
||||
onClick={openGmAdmin}
|
||||
>
|
||||
<b aria-hidden="true">GM</b>
|
||||
<span><strong>GM Tools</strong><small>Run dungeon route simulations, inspect findings, and review recordings.</small></span>
|
||||
<em>Authorized</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton
|
||||
controlId="mode-balance-lab"
|
||||
selectedId={controller.selectedId}
|
||||
select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available main-menu-mode--gm"
|
||||
type="button"
|
||||
onClick={onOpenBalanceLab}
|
||||
>
|
||||
<b aria-hidden="true">BL</b>
|
||||
<span><strong>Balance Lab</strong><small>Queue automatic party and boss simulations on the game server.</small></span>
|
||||
<em>Server</em>
|
||||
</ControllerButton>
|
||||
<section className="main-menu-card main-menu-card--adventures" aria-labelledby="main-menu-adventures-title">
|
||||
<header><span>01</span><strong id="main-menu-adventures-title">Adventures</strong></header>
|
||||
<div className="main-menu-card__grid">
|
||||
{GAME_MODES.filter((mode) => mode.id === "dungeons" || mode.id === "randomized-bite-sized").map(renderGameMode)}
|
||||
</div>
|
||||
)}
|
||||
{character && (
|
||||
<ControllerButton
|
||||
controlId="mode-class-hall"
|
||||
selectedId={controller.selectedId}
|
||||
select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available main-menu-mode--class-hall"
|
||||
type="button"
|
||||
onClick={openClassHall}
|
||||
>
|
||||
<b aria-hidden="true">CH</b>
|
||||
<span><strong>Class Hall</strong><small>{isRom ? "Choose a permanent partner class and swap your active ordering." : "Change specialization and retune your heirlooms."}</small></span>
|
||||
<em>{isRom && character.level < 10 ? "Unlocks at 10" : "Open"}</em>
|
||||
</ControllerButton>
|
||||
)}
|
||||
{GAME_MODES.map((mode) => mode.available ? (
|
||||
<ControllerButton
|
||||
key={mode.id}
|
||||
controlId={`mode-${mode.id}`}
|
||||
selectedId={controller.selectedId}
|
||||
select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available"
|
||||
type="button"
|
||||
onClick={mode.id === "manastorms"
|
||||
? openManastorms
|
||||
: mode.id === "arenas"
|
||||
? openArenas
|
||||
: mode.id === "downloads"
|
||||
? onOpenDownloads
|
||||
: mode.id === "randomized-bite-sized"
|
||||
? onOpenRandomizedBiteSized
|
||||
: openDungeons}
|
||||
>
|
||||
<b aria-hidden="true">{mode.number}</b>
|
||||
<span><strong>{mode.name}</strong><small>{mode.description}</small></span>
|
||||
<em>Available</em>
|
||||
</ControllerButton>
|
||||
) : (
|
||||
<button
|
||||
key={mode.id}
|
||||
className="front-button main-menu-mode main-menu-mode--coming-soon"
|
||||
type="button"
|
||||
disabled
|
||||
aria-label={`${mode.name}, coming soon`}
|
||||
>
|
||||
<b aria-hidden="true">{mode.number}</b>
|
||||
<span><strong>{mode.name}</strong><small>{mode.description}</small></span>
|
||||
<em>Coming soon</em>
|
||||
</button>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="main-menu-card main-menu-card--secondary" aria-labelledby="main-menu-secondary-title">
|
||||
<header><span>02</span><strong id="main-menu-secondary-title">Character & Collection</strong></header>
|
||||
<div className="main-menu-card__grid">
|
||||
{character && <>
|
||||
<ControllerButton controlId="mode-class-hall" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available main-menu-mode--class-hall" type="button" onClick={openClassHall}>
|
||||
<b aria-hidden="true">CH</b><span><strong>Class Hall</strong><small>{isRom ? "Choose a permanent partner class and swap your active ordering." : "Change specialization and retune your heirlooms."}</small></span><em>{isRom && character.level < 10 ? "Unlocks at 10" : "Open"}</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-talents" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("talents")}>
|
||||
<b aria-hidden="true">T</b><span><strong>{isRom ? "Skills" : "Talents"}</strong><small>{isRom ? "Review your primary, secondary, and elite skills." : "View your talent trees, spend points, or reset your build."}</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-keybinds" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("keybinds")}>
|
||||
<b aria-hidden="true">K</b><span><strong>Keybinds</strong><small>View and change keyboard or controller bindings.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-bindings" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={() => onOpenEditor("bindings")}>
|
||||
<b aria-hidden="true">A</b><span><strong>Button assignments</strong><small>Choose the abilities on your action bars and controller buttons.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
</>}
|
||||
<ControllerButton controlId="mode-cards" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available" type="button" onClick={onOpenCards}>
|
||||
<b aria-hidden="true">◆</b><span><strong>Boss Cards</strong><small>Track your collection, rarities, and copies from every dungeon.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="mode-gm-menu" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--available main-menu-mode--gm-menu" type="button" onClick={() => setGmMenuOpen(true)}>
|
||||
<b aria-hidden="true">GM</b><span><strong>GM Menu</strong><small>Testing, readiness, reports, and balance tools.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
{GAME_MODES.filter((mode) => mode.id !== "dungeons" && mode.id !== "randomized-bite-sized").map(renderGameMode)}
|
||||
</div>
|
||||
</section>
|
||||
</nav>
|
||||
{notice && <p className="front-notice main-menu-notice" role="status" aria-live="polite">{notice}</p>}
|
||||
</main>
|
||||
|
||||
<ControllerLegend back />
|
||||
{gmMenuOpen && (
|
||||
<div className="front-modal-backdrop">
|
||||
<section className="main-menu-gm-modal" role="dialog" aria-modal="true" aria-labelledby="gm-menu-title">
|
||||
<header>
|
||||
<div><span>Authorized utilities</span><h2 id="gm-menu-title">GM Menu</h2></div>
|
||||
<ControllerButton controlId="gm-menu-close" selectedId={controller.selectedId} select={controller.select} type="button" onClick={() => setGmMenuOpen(false)}>Close</ControllerButton>
|
||||
</header>
|
||||
<div className="main-menu-gm-grid">
|
||||
<ControllerButton controlId="gm-menu-gm-tools" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--gm" type="button" disabled={!isGm} onClick={openGmAdmin}>
|
||||
<b aria-hidden="true">GM</b><span><strong>GM Tools</strong><small>Run dungeon simulations, inspect findings, and review recordings.</small></span><em>{isGm ? "Authorized" : "GM account required"}</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="gm-menu-boss-testing" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--gm" type="button" onClick={onOpenBossTesting}>
|
||||
<b aria-hidden="true">BT</b><span><strong>Boss Testing</strong><small>Fight a selected or random encounter in an empty testing room.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="gm-menu-companion-readiness" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--gm" type="button" onClick={onOpenReadiness}>
|
||||
<b aria-hidden="true">CR</b><span><strong>Companion Readiness</strong><small>Review certified NPC specializations and Playerbots identities.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="gm-menu-balance-lab" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--gm" type="button" disabled={!isGm} onClick={onOpenBalanceLab}>
|
||||
<b aria-hidden="true">BL</b><span><strong>Balance Lab</strong><small>Queue automatic party and boss simulations on the server.</small></span><em>{isGm ? "Server" : "GM account required"}</em>
|
||||
</ControllerButton>
|
||||
<ControllerButton controlId="gm-menu-reports" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button main-menu-mode main-menu-mode--gm" type="button" onClick={onOpenReports}>
|
||||
<b aria-hidden="true">!</b><span><strong>View Reports</strong><small>Browse dungeon issues and follow their status.</small></span><em>Open</em>
|
||||
</ControllerButton>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</FrontSurface>
|
||||
}
|
||||
bottom={<LazyLocalCompanionPanel />}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function RandomizedCreatureReadinessScreen({ onClose }: { readonly onClos
|
||||
(kind === "all" || creature.kind === kind)
|
||||
&& (status === "all" || statusOf(creature.key) === status)
|
||||
&& (!query.trim() || creature.name.toLowerCase().includes(query.trim().toLowerCase())
|
||||
|| String(creature.serverEntry ?? "").includes(query.trim()))
|
||||
|| String(creature.sourceEntry).includes(query.trim()))
|
||||
)), [kind, query, reviews, status]);
|
||||
const selected = CREATURES.find((creature) => creature.key === selectedKey) ?? filtered[0] ?? null;
|
||||
const counts = Object.fromEntries(CREATURE_READINESS_STATUSES.map((candidate) => [
|
||||
@@ -59,7 +59,7 @@ export function RandomizedCreatureReadinessScreen({ onClose }: { readonly onClos
|
||||
<main className="randomized-creature-readiness__layout">
|
||||
<section className="randomized-creature-readiness__catalog">
|
||||
<div className="randomized-creature-readiness__filters">
|
||||
<label><span>Search</span><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Name or server entry" /></label>
|
||||
<label><span>Search</span><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Name or source entry" /></label>
|
||||
<label><span>Kind</span><select value={kind} onChange={(event) => setKind(event.target.value as KindFilter)}>
|
||||
<option value="all">All</option><option value="mob">Mobs</option><option value="boss">Bosses</option>
|
||||
</select></label>
|
||||
@@ -70,7 +70,7 @@ export function RandomizedCreatureReadinessScreen({ onClose }: { readonly onClos
|
||||
<div className="randomized-creature-readiness__list">
|
||||
{filtered.map((creature) => (
|
||||
<button key={creature.key} className={creature.key === selected?.key ? "is-active" : ""} type="button" onClick={() => setSelectedKey(creature.key)}>
|
||||
<span><strong>{creature.name}</strong><small>{creature.kind} · entry {creature.serverEntry ?? "unknown"}</small></span>
|
||||
<span><strong>{creature.name}</strong><small>{creature.kind} · entry {creature.sourceEntry}</small></span>
|
||||
<em className={`creature-readiness creature-readiness--${statusOf(creature.key)}`}>{CREATURE_READINESS_LABELS[statusOf(creature.key)]}</em>
|
||||
</button>
|
||||
))}
|
||||
@@ -81,7 +81,7 @@ export function RandomizedCreatureReadinessScreen({ onClose }: { readonly onClos
|
||||
{selected ? <>
|
||||
<p className="front-kicker">{selected.kind === "boss" ? "Boss candidate" : "Mob candidate"}</p>
|
||||
<h2>{selected.name}</h2>
|
||||
<dl><div><dt>Source</dt><dd>{selected.sourceDungeonTitle}</dd></div><div><dt>Entity</dt><dd>{selected.entityId}</dd></div><div><dt>Server entry</dt><dd>{selected.serverEntry ?? "Unknown"}</dd></div></dl>
|
||||
<dl><div><dt>Source</dt><dd>{selected.sourceDungeonTitle}</dd></div><div><dt>Entity</dt><dd>{selected.entityId}</dd></div><div><dt>Source entry</dt><dd>{selected.sourceEntry}</dd></div></dl>
|
||||
<p>Current status: <strong>{CREATURE_READINESS_LABELS[statusOf(selected.key)]}</strong></p>
|
||||
<div className="randomized-creature-readiness__status-actions">
|
||||
{CREATURE_READINESS_STATUSES.map((candidate) => (
|
||||
|
||||
@@ -37,6 +37,14 @@ describe("dungeon results and leaderboard", () => {
|
||||
expect(html).toContain("Another random Bite-Sized dungeon");
|
||||
expect(html).toContain("Return to main menu");
|
||||
});
|
||||
it("labels randomized Bite-Sized replay without crossing into the regular pool", () => {
|
||||
const randomizedProgress = { ...progress, id: "randomized-bite-sized-ui-test",
|
||||
dungeonId: "randomized-bite-sized-stormwind-stockade" };
|
||||
const html = renderToStaticMarkup(createElement(DungeonRunResultsDialog, { progress: randomizedProgress,
|
||||
ranking: { scope: "world", loading: false, placement: null },
|
||||
onFinish: noop, onLoot: noop, onRetry: noop, onAnother: noop }));
|
||||
expect(html).toContain("Another Randomized Bite-Sized dungeon");
|
||||
});
|
||||
it("offers both clear filters and expandable party/boss records in the dungeon picker", () => {
|
||||
const html = renderToStaticMarkup(createElement(DungeonLeaderboard, { dungeonTitle: caves.title,
|
||||
data: { entries: [{ ...progress, durationMs: progress.durationMs!, finishedAt: progress.finishedAt!, fullClear: true, rank: 1 }], total: 1 },
|
||||
|
||||
@@ -25,6 +25,10 @@ const shelvedCompanionSource = readFileSync(
|
||||
new URL("./ShelvedCompanionPanel.enabled.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const mainMenuSource = readFileSync(
|
||||
new URL("./MainMenuScreen.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
describe("gameplay panel loading integration", () => {
|
||||
it("loads talent resources behind the main-menu catalog gate, not during gameplay", () => {
|
||||
@@ -52,4 +56,19 @@ describe("gameplay panel loading integration", () => {
|
||||
expect(gameplayPanelsSource).toContain("<PreloadedBossGuidePanel />");
|
||||
expect(companionPanelSource).toContain("<ShelvedCompanionPanel");
|
||||
});
|
||||
|
||||
it("groups primary adventures separately and keeps testing utilities in the GM menu", () => {
|
||||
expect(mainMenuSource).toContain('className="main-menu-card main-menu-card--adventures"');
|
||||
expect(mainMenuSource).toContain('mode.id === "dungeons" || mode.id === "randomized-bite-sized"');
|
||||
expect(mainMenuSource).toContain('controlId="mode-gm-menu"');
|
||||
for (const controlId of [
|
||||
"gm-menu-gm-tools",
|
||||
"gm-menu-boss-testing",
|
||||
"gm-menu-companion-readiness",
|
||||
"gm-menu-balance-lab",
|
||||
"gm-menu-reports",
|
||||
]) {
|
||||
expect(mainMenuSource).toContain(`controlId="${controlId}"`);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user