forked from phenom/healer-man
157 lines
7.0 KiB
TypeScript
157 lines
7.0 KiB
TypeScript
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>
|
||
</>
|
||
);
|
||
}
|