forked from phenom/healer-man
Release Healer Man 0.1.43
This commit is contained in:
1 parent
c7e8163790
commit
ccc01396cd
86 files changed
+2951
-198
No files matched your search
@@ -10,7 +10,7 @@ import {
|
||||
type ActionBindingLayer,
|
||||
} from "../game/actionBindings";
|
||||
import { useCombatStore } from "../game/combatStore";
|
||||
import { defaultActionBarForCharacter } from "../game/abilityCatalog";
|
||||
import { abilityAtLevel, defaultActionBarForCharacter, type AbilityDefinition } from "../game/abilityCatalog";
|
||||
import { useGameStore } from "../game/store";
|
||||
import { actionKeyLabel, assignDesktopAbility, resetDesktopAbilities, type UiLayout } from "../game/uiLayout";
|
||||
import { currentUiDevice, useUiLayoutStore } from "../game/uiLayoutStore";
|
||||
@@ -20,6 +20,7 @@ import { getInputPreferences, isCapturingInput } from "../input/keybindRuntime";
|
||||
import { useInputBindingLabels } from "../input/useInputBindingLabels";
|
||||
import { useMenuController, type MenuAction } from "../input/useMenuController";
|
||||
import { ControllerButton } from "./ControllerButton";
|
||||
import { AbilityTooltipTarget, type AbilityTooltipData } from "./AbilityTooltip";
|
||||
|
||||
function AbilityBindingIcon({ icon, sigil, color }: { icon?: string; sigil: string; color: string }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
@@ -97,7 +98,7 @@ const MAIN_ACTION_NEIGHBORS: Readonly<Record<string, NonNullable<MenuAction["nei
|
||||
},
|
||||
};
|
||||
|
||||
function AbilityBindingsDialog() {
|
||||
function AbilityBindingsDialog({ onClose }: { onClose?: () => void }) {
|
||||
const panel = useRef<HTMLElement>(null);
|
||||
const keyLabel = useInputBindingLabels();
|
||||
const bindingChord = (layer: ActionBindingLayer, control: ActionBindingControl) => `${keyLabel("controller", `ability:${layer}:${control}`)} / ${keyLabel("pc", `ability:${layer}:${control}`)}`;
|
||||
@@ -106,6 +107,19 @@ function AbilityBindingsDialog() {
|
||||
const secondaryClassId = useCombatStore((state) => state.secondaryClassId);
|
||||
const level = useCombatStore((state) => state.level);
|
||||
const abilities = useCombatStore((state) => state.abilities);
|
||||
const talentRanks = useCombatStore((state) => state.talentRanks);
|
||||
const tooltipData = (definition: AbilityDefinition | null | undefined): AbilityTooltipData | null => {
|
||||
if (!definition) return null;
|
||||
const talentRank = definition.talentEntryId === undefined ? undefined : talentRanks[`coa-entry-${definition.talentEntryId}`] ?? 0;
|
||||
const ability = abilityAtLevel(definition, level, talentRank);
|
||||
return {
|
||||
name: ability.name, description: ability.description, target: ability.target,
|
||||
resourceCost: ability.cost.amount, resourceLabel: ability.cost.resource.replace(/-/g, " "),
|
||||
cooldownSeconds: ability.cooldownMs / 1_000, castSeconds: ability.castTimeMs / 1_000,
|
||||
range: ability.range.max, lockedUntilLevel: ability.unlockLevel > level ? ability.unlockLevel : undefined,
|
||||
talentLocked: ability.talentEntryId !== undefined && !talentRank,
|
||||
};
|
||||
};
|
||||
const bindings = useCombatStore((state) => state.actionBindings);
|
||||
const pcLayout = useUiLayoutStore((state) => state.preferences.current.pc);
|
||||
const [layout, setLayout] = useState<BindingLayout>(() => currentUiDevice() === "pc" && pcLayout.wowBars ? "wow" : "controller");
|
||||
@@ -133,7 +147,7 @@ function AbilityBindingsDialog() {
|
||||
]
|
||||
: ordered;
|
||||
}, [abilities, editingAbilityId]);
|
||||
const close = () => openOverlay("pause");
|
||||
const close = onClose ?? (() => openOverlay("pause"));
|
||||
const saveWowLayout = (update: (current: UiLayout) => UiLayout) => {
|
||||
try {
|
||||
const ui = useUiLayoutStore.getState();
|
||||
@@ -335,6 +349,7 @@ function AbilityBindingsDialog() {
|
||||
const ability = abilityId ? abilities.find((candidate) => candidate.id === abilityId) ?? null : null;
|
||||
const locked = Boolean(ability && ability.unlockLevel > level);
|
||||
return (
|
||||
<AbilityTooltipTarget key={control} ability={tooltipData(ability)} bindingLabel={bindingChord(layer, control)}>
|
||||
<ControllerButton
|
||||
key={control}
|
||||
controlId={slotControlId(control)}
|
||||
@@ -352,6 +367,7 @@ function AbilityBindingsDialog() {
|
||||
: <span className="ability-bindings__empty" aria-hidden="true">+</span>}
|
||||
<span><strong>{definition.label}</strong><small>{locked ? `${ability!.name} · Level ${ability!.unlockLevel}` : ability?.name ?? "Empty"}</small></span>
|
||||
</ControllerButton>
|
||||
</AbilityTooltipTarget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -363,7 +379,7 @@ function AbilityBindingsDialog() {
|
||||
<p className="eyebrow">{isWow ? "WoW loadout · 5 bars · 60 customizable slots" : "Controller loadout · 24 customizable slots"}</p>
|
||||
<h2 id="ability-bindings-title">Ability controls</h2>
|
||||
</div>
|
||||
<button type="button" className="icon-button" onClick={close} aria-label="Return to pause menu">×</button>
|
||||
<button type="button" className="icon-button" onClick={close} aria-label="Close button assignments">×</button>
|
||||
</header>
|
||||
|
||||
<nav className="ability-bindings__layouts" aria-label="Action bar style">
|
||||
@@ -407,14 +423,15 @@ function AbilityBindingsDialog() {
|
||||
const ability = abilities.find((candidate) => candidate.id === slot.abilityId);
|
||||
const locked = Boolean(ability && ability.unlockLevel > level);
|
||||
const name = ability?.name ?? (slot.abilityId ? "Unavailable for this character" : "Empty");
|
||||
return <ControllerButton key={index} controlId={slotControlId(index)} selectedId={controller.selectedId} select={controller.select}
|
||||
return <AbilityTooltipTarget key={index} ability={tooltipData(ability)} bindingLabel={controlLabel(index)}>
|
||||
<ControllerButton controlId={slotControlId(index)} selectedId={controller.selectedId} select={controller.select}
|
||||
type="button" className={`ability-binding-slot ${locked ? "is-locked" : ""} ${editingControl === index ? "is-editing" : ""}`}
|
||||
aria-label={`${controlLabel(index)}: ${name}${locked ? `, unlocks at level ${ability!.unlockLevel}` : ""}`}
|
||||
aria-pressed={editingControl === index} title={`${controlLabel(index)}: ${name}`} onClick={() => beginEditing(index)}>
|
||||
<kbd>{actionKeyLabel(slot.key) || "—"}</kbd>
|
||||
{ability ? <AbilityBindingIcon icon={ability.icon} sigil={ability.sigil} color={classColor} /> : <span className="ability-bindings__empty" aria-hidden="true">+</span>}
|
||||
<span><strong>Button {index + 1}</strong><small>{locked ? `${name} · Level ${ability!.unlockLevel}` : name}</small></span>
|
||||
</ControllerButton>;
|
||||
</ControllerButton></AbilityTooltipTarget>;
|
||||
})}
|
||||
</div>
|
||||
<p className="ability-bindings__wow-note">{!pcLayout.wowBars ? "Enable WoW action bars in UI layout to use these assignments on PC." : bar >= pcLayout.barCount ? "This bar is inactive. Increase the visible bar count in UI layout to use it." : "Choose a button to change its ability. Changes save immediately."}</p>
|
||||
@@ -475,10 +492,12 @@ function AbilityBindingsDialog() {
|
||||
<h3>Choose an ability</h3>
|
||||
<div className="ability-bindings__choices">
|
||||
{pickerAbilities.map((ability) => (
|
||||
<ControllerButton key={ability.id} controlId={choiceControlId(ability.id)} selectedId={controller.selectedId} select={controller.select} type="button" className={`${ability.id === editingAbilityId ? "is-current" : ""} ${ability.unlockLevel > level ? "is-locked" : ""}`} onClick={() => assign(ability.id)}>
|
||||
<AbilityTooltipTarget key={ability.id} ability={tooltipData(ability)}>
|
||||
<ControllerButton controlId={choiceControlId(ability.id)} selectedId={controller.selectedId} select={controller.select} type="button" className={`${ability.id === editingAbilityId ? "is-current" : ""} ${ability.unlockLevel > level ? "is-locked" : ""}`} onClick={() => assign(ability.id)}>
|
||||
<AbilityBindingIcon icon={ability.icon} sigil={ability.sigil} color={classColor} />
|
||||
<span><strong>{ability.name}</strong><small>{ability.unlockLevel > level ? `Unlocks at level ${ability.unlockLevel}` : ability.id === editingAbilityId ? "Current assignment" : ability.target === "friendly" ? "Friendly / self" : ability.target}</small></span>
|
||||
</ControllerButton>
|
||||
</AbilityTooltipTarget>
|
||||
))}
|
||||
<ControllerButton controlId="binding-choice-empty" selectedId={controller.selectedId} select={controller.select} type="button" className={editingAbilityId === null ? "is-current" : ""} onClick={() => assign(null)}>
|
||||
<span className="ability-bindings__empty" aria-hidden="true">×</span>
|
||||
@@ -507,7 +526,7 @@ function AbilityBindingsDialog() {
|
||||
);
|
||||
}
|
||||
|
||||
export function AbilityBindingsPanel() {
|
||||
const open = useGameStore((state) => state.overlay === "bindings");
|
||||
return open ? <AbilityBindingsDialog /> : null;
|
||||
export function AbilityBindingsPanel({ open, onClose }: { open?: boolean; onClose?: () => void } = {}) {
|
||||
const storeOpen = useGameStore((state) => state.overlay === "bindings");
|
||||
return (open ?? storeOpen) ? <AbilityBindingsDialog onClose={onClose} /> : null;
|
||||
}
|
||||
Reference in new issue
Block a user