Release Healer Man 0.1.22

This commit is contained in:
phenom committed 2026-09-09 11:21:33 -04:00
1 parent f6f2375145
commit 7f6001723a
144 files changed
+54107 -690

No files matched your search

+48 -13
View File
@@ -1,4 +1,11 @@
import { useCallback, useEffect, useMemo, useState, type CSSProperties } from "react";
import {
useCallback,
useEffect,
useMemo,
useState,
useSyncExternalStore,
type CSSProperties,
} from "react";
import { useProgress } from "@react-three/drei";
import { classById, raceById } from "../app/characterCatalog";
import { useShellStore } from "../app/shellStore";
@@ -19,7 +26,6 @@ import { ExperienceBar } from "./ExperienceBar";
import { Minimap } from "./Minimap";
import { PlayerStatusPanel } from "./PlayerStatusPanel";
import { EncounterTimeline } from "./EncounterTimeline";
import { PlayerFrame } from "./PlayerFrame";
import { PartyFrames } from "./PartyFrames";
import { TimedEffectStrip } from "./TimedEffectStrip";
import { AuraStrip } from "./AuraStrip";
@@ -29,6 +35,11 @@ import { BossLootWindow } from "./BossLootWindow";
import { SummonFrames } from "./SummonFrames";
import { useOnlineSessionStore } from "../app/onlineSessionStore";
import { onlineAccountIdFromActor, onlinePlayerActorId } from "../game/onlineSessionRuntime";
import {
activeDungeonEncounterRevision,
activeDungeonRunIsComplete,
subscribeDungeonEncounterSnapshots,
} from "../game/dungeonEncounterRuntime";
function LoadingOverlay() {
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
@@ -106,6 +117,27 @@ export function DefeatedPrompt({
);
}
export function DungeonCompletePrompt({
dungeonTitle,
onFinish,
}: {
readonly dungeonTitle: string;
readonly onFinish: () => void;
}) {
return (
<section className="dungeon-complete-banner" role="status" aria-live="polite">
<span aria-hidden="true">◆</span>
<p>
<strong>{dungeonTitle} complete</strong>
<small>Every required encounter is defeated. Loot the room or finish the run.</small>
</p>
<button type="button" className="button button--primary" onClick={onFinish}>
Finish dungeon
</button>
</section>
);
}
export function Hud() {
const character = useShellStore((state) => state.activeCharacter);
const gameMode = useGameStore((state) => state.gameMode);
@@ -130,10 +162,6 @@ export function Hud() {
const xpToNext = useCombatStore((state) => state.xpToNext);
const health = useCombatStore((state) => state.health);
const maxHealth = useCombatStore((state) => state.maxHealth);
const shield = useCombatStore((state) => state.shield);
const namedAbsorb = useCombatStore((state) => state.auras
.filter((aura) => aura.targetId === PLAYER_AGGRO_ID)
.reduce((total, aura) => total + aura.absorbs.reduce((sum, pool) => sum + pool.remaining, 0), 0));
const resource = useCombatStore((state) => state.resource);
const maxResource = useCombatStore((state) => state.maxResource);
const resourcePools = useCombatStore((state) => state.resourcePools);
@@ -160,6 +188,13 @@ export function Hud() {
const settings = useCombatStore((state) => state.settings);
const onlineSession = useOnlineSessionStore((state) => state.snapshot);
const [clock, setClock] = useState(() => Date.now());
useSyncExternalStore(
subscribeDungeonEncounterSnapshots,
activeDungeonEncounterRevision,
activeDungeonEncounterRevision,
);
const dungeonComplete = gameMode === "dungeon"
&& activeDungeonRunIsComplete(activeDungeonId);
useEffect(() => {
const timer = window.setInterval(() => setClock(Date.now()), 100);
@@ -318,24 +353,24 @@ export function Hud() {
}
respawnDefeatedPlayer();
};
const finishDungeon = () => {
useShellStore.getState().saveActiveDungeonRunCheckpoint(null);
useShellStore.getState().returnToMainMenu();
};
return (
<div className="hud" style={{ "--game-ui-scale": settings.uiScale } as CSSProperties}>
<ManastormStatus />
<BossLootWindow />
{dungeonComplete && health > 0 ? (
<DungeonCompletePrompt dungeonTitle={dungeon.title} onFinish={finishDungeon} />
) : null}
{feedback?.kind === "loot" ? (
<section className="loot-reward" role="status" aria-live="polite">
<span aria-hidden="true">◆</span>
<p><strong>Loot acquired</strong><small>{feedback.message}</small><em>Press I to inspect and equip</em></p>
</section>
) : null}
<PlayerFrame
character={character ? { ...character, level } : null}
health={{ current: health, maximum: maxHealth, label: "Health", tone: "health" }}
resource={{ current: resource, maximum: maxResource, label: resourceName, tone: resourceTone }}
shield={shield + namedAbsorb}
dead={health <= 0}
/>
<PartyFrames />
<SummonFrames now={clock} />