Release Healer Man 0.1.6

This commit is contained in:
phenom committed 2026-08-20 14:05:50 -04:00
1 parent 55cfd43d66
commit b12fb84859
96 files changed
+10874 -609

No files matched your search

+70 -5
View File
@@ -24,6 +24,9 @@ import { AuraStrip } from "./AuraStrip";
import { ManastormStatus } from "./ManastormStatus";
import { ThreatMeterWidget } from "./ThreatMeterWidget";
import { BossLootWindow } from "./BossLootWindow";
import { SummonFrames } from "./SummonFrames";
import { useOnlineSessionStore } from "../app/onlineSessionStore";
import { onlineAccountIdFromActor, onlinePlayerActorId } from "../game/onlineSessionRuntime";
function LoadingOverlay() {
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
@@ -61,12 +64,14 @@ export function DefeatedPrompt({
gameMode,
resurrectionCharges,
onReturn,
sharedOnline = false,
}: {
gameMode: "dungeon" | "manastorm";
resurrectionCharges: number;
onReturn: () => void;
sharedOnline?: boolean;
}) {
const canReturn = gameMode === "dungeon" || resurrectionCharges > 0;
const canReturn = !sharedOnline && (gameMode === "dungeon" || resurrectionCharges > 0);
return (
<section
className="defeated-prompt"
@@ -86,10 +91,12 @@ export function DefeatedPrompt({
? `Resurrect now · ${resurrectionCharges} remaining`
: "Respawn at entrance"}
</button>
) : sharedOnline ? (
<small>Your shared run is still active. A living player can resurrect you.</small>
) : (
<small>No shared resurrection charges remain. A living ally must resurrect you.</small>
)}
{gameMode === "dungeon" ? (
{gameMode === "dungeon" && !sharedOnline ? (
<small>Respawning resets the current encounter and restores the party.</small>
) : null}
</div>
@@ -122,6 +129,9 @@ export function Hud() {
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);
@@ -132,8 +142,10 @@ export function Hud() {
const cooldowns = useCombatStore((state) => state.cooldowns);
const globalCooldownEndsAt = useCombatStore((state) => state.globalCooldownEndsAt);
const activeCast = useCombatStore((state) => state.activeCast);
const pendingGroundTarget = useCombatStore((state) => state.pendingGroundTarget);
const feedback = useCombatStore((state) => state.feedback);
const target = useCombatStore((state) => state.selectedTargetId ? state.mobs[state.selectedTargetId] ?? null : null);
const selectedFriendlyActorId = useCombatStore((state) => state.selectedFriendlyActorId);
const friendlyTarget = usePartyStore((state) => state.selectedMemberId
? state.members.find((member) => member.id === state.selectedMemberId) ?? null
: null);
@@ -141,6 +153,7 @@ export function Hud() {
const partyMembers = usePartyStore((state) => state.members);
const playerRole = usePartyStore((state) => state.playerRole);
const settings = useCombatStore((state) => state.settings);
const onlineSession = useOnlineSessionStore((state) => state.snapshot);
const [clock, setClock] = useState(() => Date.now());
useEffect(() => {
@@ -148,6 +161,15 @@ export function Hud() {
return () => window.clearInterval(timer);
}, []);
useEffect(() => {
if (!pendingGroundTarget) return;
const cancel = (event: KeyboardEvent) => {
if (event.key === "Escape") useCombatStore.getState().cancelGroundTarget();
};
window.addEventListener("keydown", cancel);
return () => window.removeEventListener("keydown", cancel);
}, [pendingGroundTarget]);
const updateThreatMeterPosition = useCallback((threatMeterPosition: typeof settings.threatMeterPosition) => {
useCombatStore.getState().updateSettings({ threatMeterPosition });
}, []);
@@ -211,11 +233,25 @@ export function Hud() {
role: member.role,
alive: member.health > 0,
})),
...(onlineSession?.players ?? [])
.filter((player) => player.accountId !== onlineSession?.localAccountId)
.map((player) => ({
id: onlinePlayerActorId(player.accountId),
name: player.character?.name ?? player.username,
role: player.role ?? "damage",
alive: player.health > 0,
})),
],
target.targetActorId,
target.forcedTarget,
clock,
) : [];
const onlineFriendlyAccountId = selectedFriendlyActorId
? onlineAccountIdFromActor(selectedFriendlyActorId)
: null;
const onlineFriendly = onlineFriendlyAccountId
? onlineSession?.players.find((player) => player.accountId === onlineFriendlyAccountId) ?? null
: null;
const selectedFriendly = selfSelected
? {
name: character?.name ?? "Adventurer",
@@ -223,6 +259,7 @@ export function Hud() {
health,
maxHealth,
self: true,
targetId: PLAYER_AGGRO_ID,
}
: friendlyTarget
? {
@@ -231,7 +268,17 @@ export function Hud() {
health: friendlyTarget.health,
maxHealth: friendlyTarget.maxHealth,
self: false,
targetId: friendlyTarget.id,
}
: onlineFriendly
? {
name: onlineFriendly.character?.name ?? onlineFriendly.username,
detail: `Online player · Level ${onlineFriendly.character?.level ?? 1}`,
health: onlineFriendly.health,
maxHealth: onlineFriendly.maxHealth,
self: false,
targetId: onlinePlayerActorId(onlineFriendly.accountId),
}
: null;
const friendlyTargetPercent = selectedFriendly
? Math.max(0, Math.min(100, selectedFriendly.health / selectedFriendly.maxHealth * 100))
@@ -275,10 +322,27 @@ export function Hud() {
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}
shield={shield + namedAbsorb}
dead={health <= 0}
/>
<PartyFrames />
<SummonFrames now={clock} />
{pendingGroundTarget ? (
<section className="ground-target-reticle" role="status" aria-live="polite">
<div aria-hidden="true"><i /><b /></div>
<strong>Place ground effect</strong>
<small>Move with WASD / left stick. Confirm with Enter / A; Escape / B cancels.</small>
<span>
<button
type="button"
disabled={!pendingGroundTarget.valid}
onClick={() => useCombatStore.getState().confirmGroundTarget()}
>Confirm</button>
<button type="button" onClick={() => useCombatStore.getState().cancelGroundTarget()}>Cancel</button>
</span>
</section>
) : null}
<Minimap
position={playerPosition}
@@ -325,11 +389,11 @@ export function Hud() {
</header>
<div className="target-frame__track"><span style={{ width: `${friendlyTargetPercent}%` }} /></div>
<TimedEffectStrip
targetId={selectedFriendly.self ? null : friendlyTarget?.id ?? null}
targetId={selectedFriendly.self ? null : selectedFriendly.targetId}
className="timed-effect-strip--target"
/>
<AuraStrip
targetId={selectedFriendly.self ? PLAYER_AGGRO_ID : friendlyTarget?.id ?? PLAYER_AGGRO_ID}
targetId={selectedFriendly.targetId}
className="aura-strip--target"
/>
<small>{Math.ceil(selectedFriendly.health)} / {selectedFriendly.maxHealth}</small>
@@ -384,6 +448,7 @@ export function Hud() {
gameMode={gameMode}
resurrectionCharges={resurrectionCharges}
onReturn={returnFromDefeat}
sharedOnline={onlineSession !== null}
/>
) : null}
</div>