Update Healer Man server

This commit is contained in:
phenom committed 2026-09-25 18:46:09 -04:00
1 parent a868401ebc
commit 48a678dcc5
295 files changed
+7644 -467

No files matched your search

+42 -27
View File
@@ -25,7 +25,7 @@ import { DungeonLeaderboard } from "./DungeonLeaderboard";
import { useDungeonLeaderboard } from "./useDungeonLeaderboard";
import type { DungeonClearFilter } from "../game/dungeonRunTypes";
import { DUNGEON_LEADERBOARD_PAGE_SIZE } from "../app/dungeonLeaderboardClient";
import { BrandMark, ControllerLegend, FrontSurface } from "./FrontSurface";
import { ControllerLegend, FrontSurface } from "./FrontSurface";
import { OnlineGroupPanel } from "./OnlineGroupPanel";
import { useOnlineGroupStore } from "../app/onlineGroupStore";
import { specializationById } from "../game/specializationCatalog";
@@ -91,6 +91,7 @@ export function DungeonSelectScreen() {
const [bossDifficulty, setBossDifficulty] = useState(1);
const includeBetaSpecs = useCompanionPartyPreferences((state) => state.includeBetaSpecs);
const [expandedRuns, setExpandedRuns] = useState<ReadonlySet<string>>(new Set());
const [leaderboardOpen, setLeaderboardOpen] = useState(false);
const [readinessOpen, setReadinessOpen] = useState(false);
const leaderboardDifficulty = selectedDefinition?.difficultyVariants.find((variant) => variant.id === difficultyId)
?? selectedDefinition?.difficultyVariants.find((variant) => variant.id === selectedDefinition.defaultDifficultyId);
@@ -205,19 +206,24 @@ export function DungeonSelectScreen() {
id: `dungeon-${dungeon.id}`,
run: () => selectDungeon(dungeon.id),
enabled: true,
neighbors: { ...(index === 0 ? { up: `dungeon-category-${selectedCategory}` } : {}), right: "leaderboard-final" },
neighbors: { ...(index === 0 ? { up: `dungeon-category-${selectedCategory}` } : {}), right: "leaderboard-toggle" },
})),
{ id: "leaderboard-final", run: () => setClearFilter("final") },
{ id: "leaderboard-full", run: () => setClearFilter("full") },
{ id: "leaderboard-difficulty", run: cycleDifficulty, enabled: (selectedDefinition?.difficultyVariants.length ?? 0) > 1 },
{ id: "leaderboard-refresh", run: leaderboard.refresh },
{ id: "leaderboard-toggle", run: () => setLeaderboardOpen((open) => !open) },
...(leaderboardOpen ? [
{ id: "leaderboard-final", run: () => setClearFilter("final") },
{ id: "leaderboard-full", run: () => setClearFilter("full") },
{ id: "leaderboard-difficulty", run: cycleDifficulty, enabled: (selectedDefinition?.difficultyVariants.length ?? 0) > 1 },
{ id: "leaderboard-refresh", run: leaderboard.refresh },
] : []),
{ id: "boss-difficulty-down", run: () => setBossDifficulty((value) => Math.max(1, value - 1)), enabled: bossDifficulty > 1 },
{ id: "boss-difficulty-up", run: () => setBossDifficulty((value) => Math.min(10, value + 1)), enabled: bossDifficulty < 10 },
...leaderboard.data.entries.map((entry) => ({ id: `leaderboard-run-${entry.id}`, run: () => toggleRun(entry.id) })),
{ id: "leaderboard-prev", run: () => leaderboard.setOffset(Math.max(0, leaderboard.offset - DUNGEON_LEADERBOARD_PAGE_SIZE)),
enabled: !leaderboard.loading && leaderboard.offset > 0 },
{ id: "leaderboard-next", run: () => leaderboard.setOffset(leaderboard.offset + DUNGEON_LEADERBOARD_PAGE_SIZE),
enabled: !leaderboard.loading && leaderboard.offset + DUNGEON_LEADERBOARD_PAGE_SIZE < leaderboard.data.total },
...(leaderboardOpen ? [
...leaderboard.data.entries.map((entry) => ({ id: `leaderboard-run-${entry.id}`, run: () => toggleRun(entry.id) })),
{ id: "leaderboard-prev", run: () => leaderboard.setOffset(Math.max(0, leaderboard.offset - DUNGEON_LEADERBOARD_PAGE_SIZE)),
enabled: !leaderboard.loading && leaderboard.offset > 0 },
{ id: "leaderboard-next", run: () => leaderboard.setOffset(leaderboard.offset + DUNGEON_LEADERBOARD_PAGE_SIZE),
enabled: !leaderboard.loading && leaderboard.offset + DUNGEON_LEADERBOARD_PAGE_SIZE < leaderboard.data.total },
] : []),
...(rolePickerOpen
? availableRoles.map((role) => ({
id: `dungeon-role-${role}`,
@@ -233,7 +239,7 @@ export function DungeonSelectScreen() {
run: () => setRolePickerOpen(false),
}] : []),
{ id: "dungeon-back", run: returnToMainMenu },
], [availableRoles, categoryDungeons, downloading, firstCategoryDungeon, pendingDownload, returnToMainMenu, rolePickerOpen, selectedCategory, selectedDungeon, selectedDefinition, leaderboard, expandedRuns, leaderboardDifficulty, bossDifficulty]);
], [availableRoles, categoryDungeons, downloading, firstCategoryDungeon, pendingDownload, returnToMainMenu, rolePickerOpen, selectedCategory, selectedDungeon, selectedDefinition, leaderboard, leaderboardOpen, expandedRuns, leaderboardDifficulty, bossDifficulty]);
const controller = useMenuController(actions, {
initialId: firstAvailable ? `dungeon-${firstAvailable.id}` : "dungeon-back",
onBack: pendingDownload
@@ -250,10 +256,8 @@ export function DungeonSelectScreen() {
top={
<FrontSurface className="dungeon-select-screen" ariaLabel="Dungeon selection">
<header className="dungeon-select-header">
<BrandMark compact />
<div>
<div className="dungeon-select-header__breadcrumb">
<span>Main menu / Dungeons</span>
<strong>Choose an expedition</strong>
</div>
{character && race && characterClass && (
<p>
@@ -327,20 +331,26 @@ export function DungeonSelectScreen() {
</ControllerButton>
</aside>
<main className="dungeon-select-detail" aria-live="polite">
<main className={`dungeon-select-detail ${leaderboardOpen ? "dungeon-select-detail--leaderboard-open" : ""}`} aria-live="polite">
{selectedDungeon ? (
<>
<DungeonLeaderboard dungeonTitle={selectedDungeon.title} data={leaderboard.data} scope={leaderboard.scope}
loading={leaderboard.loading} filter={clearFilter} onFilter={setClearFilter}
expanded={expandedRuns} onExpand={toggleRun} offset={leaderboard.offset} onPage={leaderboard.setOffset}
onRefresh={leaderboard.refresh} difficultyLabel={`${bossDifficultyLabel(bossDifficulty)} · ${leaderboardDifficulty?.difficulty === "heroic" ? "Heroic" : "Normal"}`}
onDifficulty={(selectedDefinition?.difficultyVariants.length ?? 0) > 1 ? cycleDifficulty : undefined}
selectedId={controller.selectedId} select={controller.select} />
<section className="dungeon-select-copy">
<p className="front-kicker">{canStart ? "Playable expedition" : selectedDungeon.available ? "Content required" : "In development"}</p>
<h2>{selectedDungeon.title}</h2>
<span className="dungeon-release-stage">{DUNGEON_RELEASE_STAGE_LABELS[selectedDungeon.releaseStage]}</span>
<p>{selectedDungeon.summary}</p>
<ControllerButton
controlId="leaderboard-toggle"
selectedId={controller.selectedId}
select={controller.select}
className="front-button dungeon-select-leaderboard-toggle"
type="button"
aria-expanded={leaderboardOpen}
aria-controls={leaderboardOpen ? "dungeon-select-leaderboard" : undefined}
onClick={() => setLeaderboardOpen((open) => !open)}
>
{leaderboardOpen ? "Hide leaderboard" : "View leaderboard"}
</ControllerButton>
<DungeonDifficultySlider value={bossDifficulty} onChange={setBossDifficulty}
selectedId={controller.selectedId} select={controller.select} />
{!canStart && selectedDungeon.availabilityReason && (
@@ -391,11 +401,6 @@ export function DungeonSelectScreen() {
) : (
<>
<dl>
<div><dt>Status</dt><dd>{canStart ? "Available offline" : selectedDungeon.available ? "Download required" : "Coming soon"}</dd></div>
<div><dt>Category</dt><dd>{DUNGEON_CATEGORIES.find((category) => category.id === selectedCategory)?.fullName}</dd></div>
<div><dt>Location</dt><dd>{selectedDungeon.location}</dd></div>
<div><dt>Theme</dt><dd>{selectedDungeon.theme}</dd></div>
<div><dt>Map</dt><dd>{selectedDungeon.mapId}</dd></div>
<div><dt>Starting point</dt><dd>{selectedDungeon.entranceName}</dd></div>
</dl>
<ControllerButton
@@ -414,6 +419,16 @@ export function DungeonSelectScreen() {
)}
{notice && <p className="front-notice" role="status">{notice}</p>}
</section>
{leaderboardOpen && (
<div id="dungeon-select-leaderboard" className="dungeon-select-leaderboard-column">
<DungeonLeaderboard dungeonTitle={selectedDungeon.title} data={leaderboard.data} scope={leaderboard.scope}
loading={leaderboard.loading} filter={clearFilter} onFilter={setClearFilter}
expanded={expandedRuns} onExpand={toggleRun} offset={leaderboard.offset} onPage={leaderboard.setOffset}
onRefresh={leaderboard.refresh} difficultyLabel={`${bossDifficultyLabel(bossDifficulty)} · ${leaderboardDifficulty?.difficulty === "heroic" ? "Heroic" : "Normal"}`}
onDifficulty={(selectedDefinition?.difficultyVariants.length ?? 0) > 1 ? cycleDifficulty : undefined}
selectedId={controller.selectedId} select={controller.select} />
</div>
)}
</>
) : (
<section className="dungeon-select-empty">