Files
healer-man/src/ui/ManastormStatus.tsx
T
2026-09-10 14:14:02 -04:00

157 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { useCombatStore } from "../game/combatStore";
import { requireDungeonDefinition } from "../game/dungeonRegistry";
import {
MANASTORM_ENDGAME_MODE_ID,
manastormCacheChance,
} from "../game/manastorm";
import { manastormChaoticLinkState } from "../game/manastormChaoticLink";
import {
lootCurrentManastormBoss,
openManastormRewardCache,
} from "../game/manastormLoot";
import { useManastormStore } from "../game/manastormStore";
import { useGameStore } from "../game/store";
import { ManastormClientOverlay } from "./ManastormClientOverlay";
import { EditableHudElement } from "./EditableHudElement";
export function ManastormStatus() {
const gameMode = useGameStore((state) => state.gameMode);
const inputMode = useGameStore((state) => state.inputMode);
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
const level = useManastormStore((state) => state.level);
const modeId = useManastormStore((state) => state.modeId);
const status = useManastormStore((state) => state.status);
const phase = useManastormStore((state) => state.phase);
const encounter = useManastormStore((state) => state.currentEncounter);
const partySize = useManastormStore((state) => state.partySize);
const resurrectionCharges = useManastormStore((state) => state.resurrectionCharges);
const checkpointLevel = useManastormStore((state) => state.checkpointLevel);
const milestone = useManastormStore((state) => state.milestone);
const scaling = useManastormStore((state) => state.scaling);
const groupModifier = useManastormStore((state) => state.groupModifier);
const affixes = useManastormStore((state) => state.activeAffixes);
const cacheMeter = useManastormStore((state) => state.cacheMeter);
const cacheThreshold = useManastormStore((state) => state.bonusCacheThreshold);
const rewardCache = useManastormStore((state) => state.rewardCache);
const [waveElapsedSeconds, setWaveElapsedSeconds] = useState(0);
const pendingLootCount = useCombatStore((state) => encounter
? state.mobs[encounter.bossRuntimeId]?.pendingLoot.length ?? 0
: 0);
const mobs = useCombatStore((state) => state.mobs);
const waveActive = gameMode === "manastorm"
&& Boolean(encounter)
&& status !== "idle"
&& status !== "failed";
useEffect(() => {
if (!waveActive) {
setWaveElapsedSeconds(0);
return undefined;
}
const startedAt = Date.now();
setWaveElapsedSeconds(0);
const interval = window.setInterval(() => {
setWaveElapsedSeconds(Math.floor((Date.now() - startedAt) / 1_000));
}, 1_000);
return () => window.clearInterval(interval);
}, [encounter?.id, level, waveActive]);
if (gameMode !== "manastorm" || !encounter || status === "idle" || status === "failed") return <>
<EditableHudElement id="manastorm" /><EditableHudElement id="objective" /><EditableHudElement id="messages" /><EditableHudElement id="cache" />
</>;
const dungeon = requireDungeonDefinition(activeDungeonId);
const stageLocation = encounter.location ?? dungeon.title;
const waveElapsed = `${Math.floor(waveElapsedSeconds / 60)}:${String(waveElapsedSeconds % 60).padStart(2, "0")}`;
const statBonus = Math.round((scaling.enemyStatMultiplier - 1) * 100);
const lootBonus = Math.round(scaling.bonusLootChance * 100);
const modeLabel = modeId === MANASTORM_ENDGAME_MODE_ID ? "End-game" : "Leveling";
const cacheChance = modeId === MANASTORM_ENDGAME_MODE_ID
? Math.round(manastormCacheChance(level, cacheMeter) * 100)
: 100;
const link = manastormChaoticLinkState(encounter, mobs);
const chaoticLink = link.activeStacks > 0
? `${link.activeStacks} stack${link.activeStacks === 1 ? "" : "s"} · +${Math.round((link.bossHealthMultiplier - 1) * 100)}% HP · +${Math.round((link.bossDamageMultiplier - 1) * 100)}% DMG`
: "Broken";
const objective = phase === "trash"
? link.activeStacks > 0
? `Defeat ${link.activeStacks} linked guardian${link.activeStacks === 1 ? "" : "s"}, or challenge the empowered boss`
: `Defeat ${encounter.bossName}`
: phase === "loot"
? `Loot ${encounter.bossName} · ${pendingLootCount} item${pendingLootCount === 1 ? "" : "s"} · ${inputMode === "gamepad" ? "A" : "E"}`
: phase === "portal"
? "Enter the snowglobe portal"
: phase === "next"
? "The storm is shifting"
: `Defeat ${encounter.bossName}`;
return (
<>
<EditableHudElement id="manastorm"><section className="manastorm-status" aria-label={`Manastorm level ${level}`}>
<div>
<span>{milestone ? "Milestone" : modeLabel}</span>
<strong>{level}</strong>
<small>{waveElapsed}</small>
</div>
<p>
<b>{stageLocation} · {encounter.bossName}</b>
<small>
{partySize}P · Enemy {statBonus >= 0 ? "+" : ""}{statBonus}% · Loot {lootBonus}% · Checkpoint {checkpointLevel}
</small>
<small>
Resurrections {resurrectionCharges}/5 · Cache chance {cacheChance}%
{modeId === MANASTORM_ENDGAME_MODE_ID
? ` · Protection ${cacheMeter}/${cacheThreshold}`
: ""}
{` · Chaotic Link ${chaoticLink}`}
{partySize > 1 ? ` · Party scale ×${groupModifier.enemyStatMultiplier.toFixed(2)}` : ""}
</small>
{affixes.length ? (
<span className="manastorm-affixes">
{affixes.map((affix) => (
<i key={affix.id} title={affix.description}>
{affix.iconReference ? (
<img
src={`/assets/ui/spells/${affix.iconReference.toLowerCase()}.png`}
alt=""
/>
) : null}
{affix.name}
</i>
))}
</span>
) : null}
</p>
</section></EditableHudElement>
<EditableHudElement id="messages"><ManastormClientOverlay /></EditableHudElement>
<EditableHudElement id="objective">{phase === "loot" ? (
<button
type="button"
className={`manastorm-objective manastorm-objective--${status}`}
onPointerDown={(event) => event.stopPropagation()}
onClick={() => lootCurrentManastormBoss()}
>
<span aria-hidden="true">◆</span>
<strong>{objective}</strong>
</button>
) : (
<div className={`manastorm-objective manastorm-objective--${status}`} role="status">
<span aria-hidden="true">{phase === "portal" ? "✦" : "⚔"}</span>
<strong>{objective}</strong>
</div>
)}</EditableHudElement>
<EditableHudElement id="cache">{rewardCache[0] ? (
<button
type="button"
className="manastorm-cache-open"
onPointerDown={(event) => event.stopPropagation()}
onClick={() => openManastormRewardCache(rewardCache[0].id)}
>
Open {rewardCache[0].cacheId.replace(/-/g, " ")}
</button>
) : null}</EditableHudElement>
</>
);
}