Release Healer Man 0.1.59

This commit is contained in:
phenom committed 2026-09-27 10:16:42 -04:00
1 parent 11934a3979
commit ab1434a3b8
44 files changed
+1242 -103

No files matched your search

+4 -2
View File
@@ -4,7 +4,7 @@ import { formatDungeonDuration } from "../game/dungeonRunTypes";
import { useGameStore } from "../game/store";
import "./dungeonRace.css";
export function DungeonRaceHud() {
export function DungeonRaceHud({ region }: { readonly region: "status" | "overlays" }) {
const race = useDungeonRaceStore();
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
const [now, setNow] = useState(() => Date.now());
@@ -18,13 +18,15 @@ export function DungeonRaceHud() {
const elapsed = race.startAt ? Math.max(0, serverNow - race.startAt) : 0;
const shownTime = race.clearDurationMs ?? elapsed;
const toast = race.result && race.toastAt && now - race.toastAt < 4_000;
return <>
if (region === "status") return (
<section className="dungeon-race-hud" aria-label="Dungeon race status">
<span>Dungeon Race · {race.mode === "practice" ? "Practice" : "Ranked"}</span>
<strong>{formatDungeonDuration(shownTime)}</strong>
<small>vs {race.opponent.name}{race.opponent.type === "bot" ? ` · ${race.opponent.tier}` : ""}
{race.opponentProgress !== null ? ` · ${race.opponentProgress}%` : ""}</small>
</section>
);
return <>
{race.status === "preparing" && <div className="dungeon-race-wait" role="status">Waiting for the rival party to be ready…</div>}
{remaining !== null && remaining > 0 && race.status === "countdown" &&
<div className="dungeon-race-countdown" role="status" aria-live="polite">Race starts in <strong>{remaining}</strong></div>}
+7
View File
@@ -13,8 +13,15 @@ export function EncounterTimeline({ now }: { readonly now: number }) {
const mobs = useCombatStore((state) => state.mobs);
const model = useMemo(() => encounterTimeline(mobs, now), [mobs, now]);
if (!model) return null;
const boss = mobs[model.bossId];
const healthPercent = Math.max(0, Math.min(100, boss.health / Math.max(1, boss.maxHealth) * 100));
return (
<section className="encounter-timeline" aria-label={`${model.bossName} mechanic timeline`}>
<div className="encounter-timeline__boss-health" aria-label={`${model.bossName} health: ${Math.ceil(boss.health)} of ${boss.maxHealth}`}>
<div><span>Boss health</span><strong>{Math.ceil(healthPercent)}%</strong></div>
<div className="encounter-timeline__health-track"><span style={{ width: `${healthPercent}%` }} /></div>
<small>{Math.ceil(boss.health)} / {boss.maxHealth}</small>
</div>
<header><span>Mechanics</span><strong>{model.bossName}</strong></header>
<div className="encounter-timeline__rows">
{model.rows.map((row) => (
+2
View File
@@ -30,6 +30,7 @@ import { ExperienceBar } from "./ExperienceBar";
import { Minimap } from "./Minimap";
import { PlayerStatusPanel } from "./PlayerStatusPanel";
import { EncounterTimeline } from "./EncounterTimeline";
import { DungeonRaceHud } from "./DungeonRaceHud";
import { PartyFrames } from "./PartyFrames";
import { ResurrectionDestination, useResurrectionCheckpoints } from "./ResurrectionDestination";
import type { ResurrectionCheckpoint } from "../game/resurrectionCheckpoints";
@@ -441,6 +442,7 @@ export function Hud({ surface = "top", onLayoutChange, completion, onFinish, onR
) : null}
<div className="hud-top-right">
<DungeonRaceHud region="status" />
<EditableHudElement id="minimap"><Minimap
position={playerPosition}
yaw={cameraYaw}
+2 -2
View File
@@ -1,4 +1,4 @@
.dungeon-race-hud{position:absolute;z-index:42;top:8px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:1px;min-width:180px;padding:5px 14px;border:1px solid #a79261;border-radius:9px;background:#091217e8;color:#f5e8ca;text-align:center;pointer-events:none;box-shadow:0 5px 18px #0009}
.dungeon-race-hud{position:absolute;z-index:42;top:0;right:calc(100% + 8px);display:flex;flex-direction:column;align-items:center;gap:1px;min-width:180px;padding:5px 14px;border:1px solid #a79261;border-radius:9px;background:#091217e8;color:#f5e8ca;text-align:center;pointer-events:none;box-shadow:0 5px 18px #0009}
.dungeon-race-hud span{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#c2ac78}
.dungeon-race-hud strong{font-variant-numeric:tabular-nums;font-size:21px;line-height:1.1}
.dungeon-race-hud small{font-size:10px;white-space:nowrap}
@@ -7,7 +7,7 @@
.dungeon-race-toast{position:absolute;z-index:65;top:74px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;padding:9px 18px;border:1px solid #a79261;border-radius:9px;background:#101516f2;color:#ffe4a3;font-size:22px;font-weight:700;pointer-events:none}
.dungeon-race-toast--loss{border-color:#bd6b63;color:#ffb2a8}
.dungeon-race-toast small{font-size:11px;color:#f5e8ca}
@media(max-width:650px){.dungeon-race-hud{top:4px;min-width:145px;padding:3px 8px}.dungeon-race-hud strong{font-size:17px}.dungeon-race-toast{top:64px}}
@media(max-width:650px){.dungeon-race-hud{min-width:145px;padding:3px 8px}.dungeon-race-hud strong{font-size:17px}.dungeon-race-toast{top:64px}}
.dungeon-race-screen{min-height:100%;padding:16px;color:#f5e8ca}
.dungeon-race-screen__header{display:flex;align-items:center;gap:18px}.dungeon-race-screen__header h1{margin:2px 0;font-size:25px}.dungeon-race-screen__header button{margin-left:auto}
.dungeon-race-screen__layout{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(280px,.9fr);gap:18px;margin-top:16px}
+3 -2
View File
@@ -31,8 +31,9 @@ const mainMenuSource = readFileSync(
);
describe("gameplay panel loading integration", () => {
it("loads talent resources behind the main-menu catalog gate, not during gameplay", () => {
it("loads talent resources at gameplay entry and on demand in the menu editor", () => {
expect(appSource).toContain("preloadTalentGameplayResources(talentClassId)");
expect(appSource).toContain('preloadGameplay={phase === "game"}');
expect(talentResourcesSource).toContain("loadTalentUiCatalog(classId)");
expect(talentResourcesSource).toContain("loadTalentGameplayPanel()");
expect(talentResourcesSource).toContain("image.decode()");
@@ -44,7 +45,7 @@ describe("gameplay panel loading integration", () => {
expect(gameplayPanelsSource).not.toContain("<Suspense fallback={null}><TalentGameplayPanel />");
});
it("warms the shared runtime and first-open panels before showing the main menu", () => {
it("warms the shared runtime and first-open panels before gameplay mounts", () => {
expect(appSource).toContain("preloadSharedGameplayResources(originalMediaEnabled)");
expect(appSource).toContain("getLoadedGameRuntime()");
expect(gameplayResourcesSource).toContain('import("../GameRuntime")');