forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
7449abdad2
commit
a641a6e948
9 files changed
+363
-47
No files matched your search
@@ -68,15 +68,12 @@ export function DungeonSelectScreen() {
|
||||
const enterDungeon = useShellStore((state) => state.enterDungeon);
|
||||
const returnToMainMenu = useShellStore((state) => state.returnToMainMenu);
|
||||
const notice = useShellStore((state) => state.notice);
|
||||
const dungeonSelectionId = useShellStore((state) => state.dungeonSelectionId);
|
||||
const session = useShellStore((state) => state.session);
|
||||
const onlineGroup = useOnlineGroupStore((state) => state.snapshot.group);
|
||||
const updateOnlineMember = useOnlineGroupStore((state) => state.updateMember);
|
||||
const startOnlineActivity = useOnlineGroupStore((state) => state.startActivity);
|
||||
const character = activeCharacter ?? rosterCharacter;
|
||||
const firstAvailable = DUNGEON_CATALOG.find((dungeon) => dungeon.id === dungeonSelectionId)
|
||||
?? DUNGEON_CATALOG.find((dungeon) => dungeon.available)
|
||||
?? DUNGEON_CATALOG[0];
|
||||
const firstAvailable = DUNGEON_CATALOG.find((dungeon) => dungeon.available) ?? DUNGEON_CATALOG[0];
|
||||
const [selectedCategory, setSelectedCategory] = useState<DungeonCategoryId>(firstAvailable?.category ?? "wow");
|
||||
const [selectedDungeonId, setSelectedDungeonId] = useState<DungeonId | "">(firstAvailable?.id ?? "");
|
||||
const [rolePickerOpen, setRolePickerOpen] = useState(false);
|
||||
|
||||
+100
-6
@@ -1,7 +1,12 @@
|
||||
import { lazy, Suspense, useCallback, useMemo, useState } from "react";
|
||||
import { classById } from "../app/characterCatalog";
|
||||
import { queueRandomDungeon, type RandomDungeonQueueMode, type RandomDungeonQueueStage } from "../app/randomDungeonQueue";
|
||||
import { selectedCharacter, useShellStore } from "../app/shellStore";
|
||||
import { useOnlineGroupStore } from "../app/onlineGroupStore";
|
||||
import { useContentManagerSnapshot } from "../content/useContentManager";
|
||||
import { DualDisplayFrame } from "../components/DualDisplayFrame";
|
||||
import { useCompanionPartyPreferences } from "../game/companionPartyPreferences";
|
||||
import { partyRolesForClass, PARTY_ROLE_LABELS, type PartyRole } from "../game/partyStore";
|
||||
import { specializationById, specializationIconUrl } from "../game/specializationCatalog";
|
||||
import { useMenuController, type MenuAction } from "../input/useMenuController";
|
||||
import { LazyLocalCompanionPanel } from "./LazyLocalCompanionPanel";
|
||||
@@ -125,23 +130,78 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
readonly onOpenEditor: (editor: MainMenuEditor) => void;
|
||||
}) {
|
||||
const [gmMenuOpen, setGmMenuOpen] = useState(false);
|
||||
const [randomQueueMode, setRandomQueueMode] = useState<RandomDungeonQueueMode | null>(null);
|
||||
const [selectedRole, setSelectedRole] = useState<PartyRole | null>(null);
|
||||
const [queueing, setQueueing] = useState(false);
|
||||
const [queueStage, setQueueStage] = useState<RandomDungeonQueueStage>("rolling");
|
||||
const [queuedDungeonTitle, setQueuedDungeonTitle] = useState("");
|
||||
const [queueError, setQueueError] = useState("");
|
||||
const activeCharacter = useShellStore((state) => state.activeCharacter);
|
||||
const rosterCharacter = useShellStore(selectedCharacter);
|
||||
const openDungeons = useShellStore((state) => state.openDungeons);
|
||||
const openRandomDungeon = useCallback(() => openDungeons("random"), [openDungeons]);
|
||||
const openRandomBiteSizedDungeon = useCallback(() => openDungeons("random-bite-sized"), [openDungeons]);
|
||||
const openRandomDungeon = useCallback(() => { setRandomQueueMode("regular"); setSelectedRole(null); setQueueError(""); }, []);
|
||||
const openRandomBiteSizedDungeon = useCallback(() => { setRandomQueueMode("bite-sized"); setSelectedRole(null); setQueueError(""); }, []);
|
||||
const openManastorms = useShellStore((state) => state.openManastorms);
|
||||
const openArenas = useShellStore((state) => state.openArenas);
|
||||
const openClassHall = useShellStore((state) => state.openClassHall);
|
||||
const returnToCharacters = useShellStore((state) => state.returnToCharacters);
|
||||
const notice = useShellStore((state) => state.notice);
|
||||
const session = useShellStore((state) => state.session);
|
||||
const enterDungeon = useShellStore((state) => state.enterDungeon);
|
||||
const openGmAdmin = useShellStore((state) => state.openGmAdmin);
|
||||
const onlineGroup = useOnlineGroupStore((state) => state.snapshot.group);
|
||||
const fillWithAi = useOnlineGroupStore((state) => state.fillWithAi);
|
||||
const updateOnlineMember = useOnlineGroupStore((state) => state.updateMember);
|
||||
const startOnlineActivity = useOnlineGroupStore((state) => state.startActivity);
|
||||
const includeBetaSpecs = useCompanionPartyPreferences((state) => state.includeBetaSpecs);
|
||||
const contentState = useContentManagerSnapshot();
|
||||
const character = activeCharacter ?? rosterCharacter;
|
||||
const isRom = character?.categoryId === "rom";
|
||||
const isGm = sessionCanUseGmTools(session);
|
||||
const savedSpecialization = specializationById(character?.specializationId);
|
||||
const availableRoles = character
|
||||
? isRom ? partyRolesForClass(character.classId) : savedSpecialization ? [savedSpecialization.role] : []
|
||||
: [];
|
||||
|
||||
const actions = useMemo<MenuAction[]>(() => gmMenuOpen ? [
|
||||
const closeRandomQueue = useCallback(() => {
|
||||
if (!queueing) setRandomQueueMode(null);
|
||||
}, [queueing]);
|
||||
const startRandomQueue = useCallback(async () => {
|
||||
if (!randomQueueMode || !selectedRole || queueing) return;
|
||||
setQueueing(true);
|
||||
setQueueStage("rolling");
|
||||
setQueueError("");
|
||||
setQueuedDungeonTitle("");
|
||||
const result = await queueRandomDungeon({
|
||||
mode: randomQueueMode,
|
||||
role: selectedRole,
|
||||
character,
|
||||
session,
|
||||
group: onlineGroup,
|
||||
includeBetaSpecs,
|
||||
fillWithAi,
|
||||
onProgress: (stage, dungeon) => { setQueueStage(stage); setQueuedDungeonTitle(dungeon?.title ?? ""); },
|
||||
enterDungeon: (dungeonId, role, settings) => enterDungeon(dungeonId, role, settings),
|
||||
updateOnlineMember,
|
||||
startOnlineActivity: (groupSession, input) => startOnlineActivity(groupSession, input),
|
||||
failureNotice: () => useOnlineGroupStore.getState().error || useShellStore.getState().notice,
|
||||
});
|
||||
if (result.status === "error") setQueueError(result.message);
|
||||
else if (result.status === "waiting") {
|
||||
useShellStore.getState().setNotice(`Ready as ${PARTY_ROLE_LABELS[selectedRole]}. Waiting for the group leader to start.`);
|
||||
setRandomQueueMode(null);
|
||||
} else if (result.status === "started") {
|
||||
useShellStore.getState().setNotice(`Starting ${result.dungeonTitle} with your group.`);
|
||||
setRandomQueueMode(null);
|
||||
}
|
||||
setQueueing(false);
|
||||
}, [character, enterDungeon, fillWithAi, includeBetaSpecs, onlineGroup, queueing, randomQueueMode, selectedRole, session, startOnlineActivity, updateOnlineMember]);
|
||||
|
||||
const actions = useMemo<MenuAction[]>(() => randomQueueMode ? [
|
||||
...availableRoles.map((role) => ({ id: `random-queue-role-${role}`, run: () => setSelectedRole(role), enabled: !queueing })),
|
||||
{ id: "random-queue-confirm", run: () => void startRandomQueue(), enabled: Boolean(selectedRole) && !queueing },
|
||||
{ id: "random-queue-cancel", run: closeRandomQueue, enabled: !queueing },
|
||||
] : 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 },
|
||||
@@ -184,11 +244,11 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
] : []),
|
||||
{ id: "mode-downloads", run: onOpenDownloads },
|
||||
{ id: "back-to-roster", run: returnToCharacters, neighbors: { down: "mode-dungeons" } },
|
||||
], [character, gmMenuOpen, isGm, onOpenBalanceLab, onOpenBalanceTuning, onOpenBossTesting, onOpenCards, onOpenDownloads, onOpenEditor, onOpenRandomizedBiteSized, onOpenReadiness, onOpenReports, openArenas, openClassHall, openDungeons, openGmAdmin, openManastorms, openRandomBiteSizedDungeon, openRandomDungeon, returnToCharacters]);
|
||||
], [availableRoles, character, closeRandomQueue, gmMenuOpen, isGm, onOpenBalanceLab, onOpenBalanceTuning, onOpenBossTesting, onOpenCards, onOpenDownloads, onOpenEditor, onOpenRandomizedBiteSized, onOpenReadiness, onOpenReports, openArenas, openClassHall, openDungeons, openGmAdmin, openManastorms, openRandomBiteSizedDungeon, openRandomDungeon, queueing, randomQueueMode, returnToCharacters, selectedRole, startRandomQueue]);
|
||||
const controller = useMenuController(actions, {
|
||||
columns: 2,
|
||||
initialId: gmMenuOpen ? (isGm ? "gm-menu-gm-tools" : "gm-menu-boss-testing") : "mode-dungeons",
|
||||
onBack: gmMenuOpen ? () => setGmMenuOpen(false) : returnToCharacters,
|
||||
initialId: randomQueueMode ? `random-queue-role-${availableRoles[0]}` : gmMenuOpen ? (isGm ? "gm-menu-gm-tools" : "gm-menu-boss-testing") : "mode-dungeons",
|
||||
onBack: randomQueueMode ? closeRandomQueue : gmMenuOpen ? () => setGmMenuOpen(false) : returnToCharacters,
|
||||
});
|
||||
|
||||
const characterClass = character ? classById(character.classId) : null;
|
||||
@@ -315,6 +375,40 @@ function MainMenuDestinations({ onOpenDownloads, onOpenReports, onOpenCards, onO
|
||||
</main>
|
||||
|
||||
<ControllerLegend back />
|
||||
{randomQueueMode && (
|
||||
<div className="front-modal-backdrop">
|
||||
<section className="main-menu-random-modal" role="dialog" aria-modal="true" aria-labelledby="random-queue-title">
|
||||
<p className="front-kicker">{randomQueueMode === "regular" ? "Random Dungeon" : "Random Regular Bite-Sized Dungeon"}</p>
|
||||
<h2 id="random-queue-title">Choose your role</h2>
|
||||
<p>Pick how you want to play, then queue. A dungeon will be rolled and downloaded before you enter.</p>
|
||||
<div className="main-menu-random-modal__roles" aria-label="Party role">
|
||||
{availableRoles.map((role, index) => (
|
||||
<ControllerButton key={role} controlId={`random-queue-role-${role}`}
|
||||
selectedId={controller.selectedId} select={controller.select} autoFocus={index === 0}
|
||||
className="front-button main-menu-random-modal__role" type="button"
|
||||
aria-pressed={selectedRole === role} disabled={queueing} onClick={() => setSelectedRole(role)}>
|
||||
{PARTY_ROLE_LABELS[role]}
|
||||
</ControllerButton>
|
||||
))}
|
||||
</div>
|
||||
{!availableRoles.length && <p className="front-notice">Choose a specialization in your Class Hall before queuing.</p>}
|
||||
{queueing && <p className="main-menu-random-modal__progress" role="status" aria-live="polite">
|
||||
{queueStage === "rolling" ? "Rolling a dungeon…" : queueStage === "downloading"
|
||||
? `Downloading ${queuedDungeonTitle}${contentState.installingPackId && contentState.totalBytes > 0
|
||||
? ` — ${Math.round((contentState.downloadedBytes / contentState.totalBytes) * 100)}%` : "…"}`
|
||||
: `Entering ${queuedDungeonTitle}…`}
|
||||
</p>}
|
||||
{queueError && <p className="front-notice" role="alert">{queueError}</p>}
|
||||
<div className="main-menu-random-modal__actions">
|
||||
<ControllerButton controlId="random-queue-cancel" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button" type="button" disabled={queueing} onClick={closeRandomQueue}>Cancel</ControllerButton>
|
||||
<ControllerButton controlId="random-queue-confirm" selectedId={controller.selectedId} select={controller.select}
|
||||
className="front-button front-button--primary" type="button" disabled={!selectedRole || queueing}
|
||||
onClick={() => void startRandomQueue()}>{queueing ? "Queuing…" : "Queue"}</ControllerButton>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
{gmMenuOpen && (
|
||||
<div className="front-modal-backdrop">
|
||||
<section className="main-menu-gm-modal" role="dialog" aria-modal="true" aria-labelledby="gm-menu-title">
|
||||
|
||||
Reference in new issue
Block a user