Update Healer Man server

This commit is contained in:
phenom committed 2026-10-04 22:51:21 -04:00
1 parent 6cc1b9217f
commit 6114a2295f
67 files changed
+6241 -347

No files matched your search

+10
View File
@@ -6,6 +6,7 @@ import {
auraDefinitionDisplaysInStrip,
auraDefinitionIcon,
auraDefinitionIsDispellableDebuff,
auraDefinitionIsFilteredLongTerm,
auraDefinitionIsLongTerm,
auraDescription,
groupAurasForDisplay,
@@ -65,6 +66,15 @@ describe("aura strip visibility", () => {
expect(auraDefinitionIsLongTerm(aura({ durationMs: LONG_TERM_AURA_DURATION_MS - 1 }))).toBe(false);
});
it("keeps only explicitly retained persistent utility auras in clutter-filtered strips", () => {
const bloodArrow = aura({ disposition: "buff", durationMs: null, utilityTags: ["rom-self-health-drain"] });
const otherPersistentBuff = aura({ disposition: "buff", durationMs: null, utilityTags: ["other-persistent"] });
expect(auraDefinitionIsFilteredLongTerm(bloodArrow)).toBe(true);
expect(auraDefinitionIsFilteredLongTerm(bloodArrow, ["rom-self-health-drain"])).toBe(false);
expect(auraDefinitionIsFilteredLongTerm(otherPersistentBuff, ["rom-self-health-drain"])).toBe(true);
});
it("identifies only categorized debuffs as dispellable player effects", () => {
expect(auraDefinitionIsDispellableDebuff(aura({ dispelCategory: "magic" }))).toBe(true);
expect(auraDefinitionIsDispellableDebuff(aura())).toBe(false);
+12 -1
View File
@@ -1,12 +1,14 @@
import { useShallow } from "zustand/react/shallow";
import { usePresentationClock } from "./usePresentationClock";
import type { AuraDisposition } from "../game/combatAuras";
import type { ActiveAura } from "../game/combatAuras";
import { useCombatStore } from "../game/combatStore";
import { formatTimedEffectTimer } from "../game/timedEffectDisplay";
import {
auraDefinitionDisplaysInStrip,
auraDefinitionIcon,
auraDefinitionIsDispellableDebuff,
auraDefinitionIsFilteredLongTerm,
auraDefinitionIsLongTerm,
auraDescription,
auraDisplayName,
@@ -20,10 +22,12 @@ interface AuraStripProps {
readonly dispositions?: readonly AuraDisposition[];
readonly className?: string;
readonly hideLongTerm?: boolean;
readonly keepLongTermUtilityTags?: readonly string[];
readonly showOnlyLongTerm?: boolean;
readonly highlightDispellableDebuffs?: boolean;
readonly targetLabels?: Readonly<Record<string, string>>;
readonly onInspect?: StatusEffectInspectHandler;
readonly onAuraContextMenu?: (aura: ActiveAura) => boolean;
readonly tooltipId?: string;
}
@@ -33,10 +37,12 @@ export function AuraStrip({
dispositions = ["buff", "debuff"],
className = "",
hideLongTerm = false,
keepLongTermUtilityTags = [],
showOnlyLongTerm = false,
highlightDispellableDebuffs = false,
targetLabels,
onInspect,
onAuraContextMenu,
tooltipId,
}: AuraStripProps) {
const requestedTargetIds = targetIds ?? [targetId];
@@ -44,7 +50,7 @@ export function AuraStrip({
requestedTargetIds.includes(aura.targetId)
&& dispositions.includes(aura.definition.disposition)
&& auraDefinitionDisplaysInStrip(aura.definition)
&& (!hideLongTerm || !auraDefinitionIsLongTerm(aura.definition))
&& (!hideLongTerm || !auraDefinitionIsFilteredLongTerm(aura.definition, keepLongTermUtilityTags))
&& (!showOnlyLongTerm || auraDefinitionIsLongTerm(aura.definition))
))));
const hasTimedAuras = matchingAuras.some((aura) => aura.expiresAt !== null);
@@ -93,6 +99,11 @@ export function AuraStrip({
} : undefined}
onFocus={onInspect ? () => onInspect(inspection) : undefined}
onBlur={onInspect ? () => onInspect(null) : undefined}
onContextMenu={onAuraContextMenu ? (event) => {
if (!onAuraContextMenu(aura)) return;
event.preventDefault();
event.stopPropagation();
} : undefined}
>
{icon
? <img className="aura__icon" src={icon} alt="" draggable={false} />
+11
View File
@@ -22,6 +22,10 @@ import { ResurrectionDestination, useResurrectionCheckpoints } from "./Resurrect
import { useGameStore } from "../game/store";
import { useHudSurface } from "./HudSurface";
import { FrameDamageText } from "./FloatingDamageText";
import {
deactivateRomBloodArrowFromAuraContextMenu,
ROM_SCOUT_BLOOD_ARROW_AURA_TAG,
} from "../game/romBloodArrow";
import {
PARTY_HEAL_POPUP_LIFETIME_MS,
addedPartyHealPopups,
@@ -217,6 +221,7 @@ function MemberFrame({ member, selected, onSelect, healPopups }: {
.map((aura) => aura.definition.name)
.join(", "));
const resourcePercent = healthPercent(member.resource, member.maxResource);
const deactivateRomScoutBloodArrow = useCombatStore((state) => state.deactivateRomScoutBloodArrow);
return (
<button
type="button"
@@ -242,7 +247,13 @@ function MemberFrame({ member, selected, onSelect, healPopups }: {
targetId={member.id}
className="aura-strip--party"
hideLongTerm
keepLongTermUtilityTags={[ROM_SCOUT_BLOOD_ARROW_AURA_TAG]}
highlightDispellableDebuffs
onAuraContextMenu={(aura) => deactivateRomBloodArrowFromAuraContextMenu(
aura,
member.id,
deactivateRomScoutBloodArrow,
)}
/>
</span>
<HealPopups popups={healPopups} targetId={member.id} />
+8
View File
@@ -3,6 +3,7 @@ import { abilityById } from "../game/abilityCatalog";
import { PLAYER_AGGRO_ID } from "../game/aggro";
import { useCombatStore } from "../game/combatStore";
import { formatTimedEffectTimer, groupTimedEffectsForDisplay, timedEffectRemainingMs } from "../game/timedEffectDisplay";
import { deactivateRomBloodArrowFromAuraContextMenu } from "../game/romBloodArrow";
import { AuraStrip } from "./AuraStrip";
import {
auraDisplaysInPlayerStatusPanel,
@@ -26,6 +27,7 @@ function statusLabel(disposition: StatusEffectInspection["disposition"]): string
export function PlayerStatusPanel({ now }: { readonly now: number }) {
const auras = useCombatStore((state) => state.auras);
const effects = useCombatStore((state) => state.effects);
const deactivateRomScoutBloodArrow = useCombatStore((state) => state.deactivateRomScoutBloodArrow);
const shield = useCombatStore((state) => state.shield);
const shieldExpiresAt = useCombatStore((state) => state.shieldExpiresAt);
const [inspected, setInspected] = useState<StatusEffectInspection | null>(null);
@@ -131,6 +133,12 @@ export function PlayerStatusPanel({ now }: { readonly now: number }) {
dispositions={["buff"]}
className="player-status-panel__strip"
onInspect={setInspected}
onAuraContextMenu={(aura) => deactivateRomBloodArrowFromAuraContextMenu(
aura,
PLAYER_AGGRO_ID,
deactivateRomScoutBloodArrow,
now,
)}
tooltipId={TOOLTIP_ID}
/>
<TimedEffectStrip
+8
View File
@@ -57,6 +57,14 @@ export function auraDefinitionIsLongTerm(definition: AuraDefinition): boolean {
return definition.durationMs === null || definition.durationMs >= LONG_TERM_AURA_DURATION_MS;
}
export function auraDefinitionIsFilteredLongTerm(
definition: AuraDefinition,
keepLongTermUtilityTags: readonly string[] = [],
): boolean {
return auraDefinitionIsLongTerm(definition)
&& !definition.utilityTags?.some((tag) => keepLongTermUtilityTags.includes(tag));
}
export function auraDefinitionIsDispellableDebuff(definition: AuraDefinition): boolean {
return definition.disposition === "debuff" && definition.dispelCategory !== undefined;
}
+31
View File
@@ -6,6 +6,7 @@ import { activateDungeonEncounterDefinitions, resetDungeonEncounters } from "../
import { requireDungeonDefinition } from "../game/dungeonRegistry";
import { usePartyStore } from "../game/partyStore";
import { useGameStore } from "../game/store";
import { ROM_SCOUT_BLOOD_ARROW_AURA_TAG } from "../game/romBloodArrow";
import { PartyFrames } from "./PartyFrames";
// Exercise the client HUD against live stores rather than their initial SSR snapshots.
@@ -79,6 +80,36 @@ describe("party resurrection UI", () => {
expect(render()).toContain(`value="boss:${boss.id}"`);
});
it("keeps an owned NPC's cancelable permanent Blood Arrow visible while filtering other permanent buffs", () => {
const ownerId = usePartyStore.getState().members[0]!.id;
const aura = (id: string, name: string, utilityTags?: readonly string[]) => ({
instanceId: id,
definition: {
id,
name,
disposition: "buff" as const,
durationMs: null,
...(utilityTags ? { utilityTags } : {}),
},
sourceId: ownerId,
targetId: ownerId,
appliedAt: 1_000,
expiresAt: null,
stacks: 1,
absorbs: [],
procStates: [],
});
useCombatStore.setState({ auras: [
aura("blood-arrow-permanent", "Blood Arrow", [ROM_SCOUT_BLOOD_ARROW_AURA_TAG]),
aura("other-permanent", "Other Persistent Buff"),
] });
const markup = render();
expect(markup).toContain("Blood Arrow, buff");
expect(markup).not.toContain("Other Persistent Buff, buff");
});
it("does not offer free resurrection in Manastorm", () => {
killMember();
useGameStore.setState({ gameMode: "manastorm" });