Release Healer Man 0.1.29

This commit is contained in:
phenom committed 2026-09-11 14:26:15 -04:00
1 parent 8c9809d3e0
commit 544c1bee91
120 files changed
+4363 -1036

No files matched your search

+30 -15
View File
@@ -5,14 +5,16 @@ import {
ACTION_CONTROL_DEFINITIONS,
ACTION_LAYERS,
ACTION_LAYER_LABELS,
actionBindingChordLabel,
actionBindingId,
type ActionBindingControl,
type ActionBindingLayer,
} from "../game/actionBindings";
import { useCombatStore } from "../game/combatStore";
import { useGameStore } from "../game/store";
import { subscribeControllerToken } from "../input/controller";
import { getControllerHeldTokens, subscribeControllerToken } from "../input/controller";
import { INPUT_ACTIONS, controllerBindingMatches, inputKeyForEvent } from "../input/keybinds";
import { getInputPreferences, isCapturingInput } from "../input/keybindRuntime";
import { useInputBindingLabels } from "../input/useInputBindingLabels";
import { useMenuController, type MenuAction } from "../input/useMenuController";
import { ControllerButton } from "./ControllerButton";
@@ -82,6 +84,8 @@ const MAIN_ACTION_NEIGHBORS: Readonly<Record<string, NonNullable<MenuAction["nei
};
function AbilityBindingsDialog() {
const keyLabel = useInputBindingLabels();
const bindingChord = (layer: ActionBindingLayer, control: ActionBindingControl) => `${keyLabel("controller", `ability:${layer}:${control}`)} / ${keyLabel("pc", `ability:${layer}:${control}`)}`;
const openOverlay = useGameStore((state) => state.openOverlay);
const classId = useCombatStore((state) => state.classId);
const level = useCombatStore((state) => state.level);
@@ -130,14 +134,14 @@ function AbilityBindingsDialog() {
const control = editingControl;
const ability = abilityId ? abilities.find((candidate) => candidate.id === abilityId) ?? null : null;
useCombatStore.getState().setActionBinding(layer, control, abilityId);
setNotice(`${actionBindingChordLabel(layer, control)}: ${ability?.name ?? "Empty"}.`);
setNotice(`${bindingChord(layer, control)}: ${ability?.name ?? "Empty"}.`);
setEditingControl(null);
controller.select(slotControlId(control));
};
const clearSelected = (control: ActionBindingControl | null = editingControl) => {
if (!control) return;
useCombatStore.getState().setActionBinding(layer, control, null);
setNotice(`${actionBindingChordLabel(layer, control)} cleared.`);
setNotice(`${bindingChord(layer, control)} cleared.`);
setEditingControl(null);
controller.select(slotControlId(control));
};
@@ -224,19 +228,30 @@ function AbilityBindingsDialog() {
});
useEffect(() => {
return subscribeControllerToken(({ token, repeat, pressed }) => {
if (pressed === false || repeat) return;
const run = (id: string | undefined) => {
if (restoreRequest) return;
if (token === "Button4") chooseLayer("secondary");
else if (token === "Button6") chooseLayer("tertiary");
else if (token === "Button2") {
if (id === "editor-layer-2") chooseLayer("secondary");
else if (id === "editor-layer-3") chooseLayer("tertiary");
else if (id === "editor-clear") {
if (editingControl) clearSelected(editingControl);
else {
const prefix = "binding-slot-";
if (controller.selectedId.startsWith(prefix)) clearSelected(controller.selectedId.slice(prefix.length) as ActionBindingControl);
}
} else if (token === "Button3") requestRestore("layer", controller.selectedId);
} else if (id === "editor-reset") requestRestore("layer", controller.selectedId);
};
const unsubscribe = subscribeControllerToken(({ token, repeat, pressed }) => {
if (pressed === false || repeat || isCapturingInput()) return;
run(INPUT_ACTIONS.find((entry) => entry.context === "ability-editor" && controllerBindingMatches(getInputPreferences().controller[entry.id], token, getControllerHeldTokens()))?.id);
});
const onKey = (event: KeyboardEvent) => {
if (event.repeat || event.defaultPrevented || isCapturingInput() || (event.target instanceof Element && event.target.closest("input, textarea, select, [contenteditable='true']"))) return;
const key = inputKeyForEvent(event);
const id = key && INPUT_ACTIONS.find((entry) => entry.context === "ability-editor" && getInputPreferences().pc[entry.id] === key)?.id;
if (id) { event.preventDefault(); run(id); }
};
window.addEventListener("keydown", onKey);
return () => { unsubscribe(); window.removeEventListener("keydown", onKey); };
}, [controller.selectedId, editingControl, layer, restoreRequest]);
const bindingSlot = (control: ActionBindingControl) => {
@@ -252,11 +267,11 @@ function AbilityBindingsDialog() {
select={controller.select}
type="button"
className={`ability-binding-slot ability-binding-slot--${control} ${editingControl === control ? "is-editing" : ""} ${locked ? "is-locked" : ""}`}
aria-label={`${actionBindingChordLabel(layer, control)}: ${ability?.name ?? "Empty"}${locked ? `, unlocks at level ${ability!.unlockLevel}` : ""}`}
aria-label={`${bindingChord(layer, control)}: ${ability?.name ?? "Empty"}${locked ? `, unlocks at level ${ability!.unlockLevel}` : ""}`}
aria-pressed={editingControl === control}
onClick={() => beginEditing(control)}
>
<kbd>{definition.gamepadLabel}</kbd>
<kbd>{keyLabel("controller", `ability:${layer}:${control}`)}</kbd>
{ability
? <AbilityBindingIcon icon={ability.icon} sigil={ability.sigil} color={classColor} />
: <span className="ability-bindings__empty" aria-hidden="true">+</span>}
@@ -340,7 +355,7 @@ function AbilityBindingsDialog() {
</>
) : editingControl ? (
<>
<p className="eyebrow">Assign {actionBindingChordLabel(layer, editingControl)}</p>
<p className="eyebrow">Assign {bindingChord(layer, editingControl)}</p>
<h3>Choose an ability</h3>
<div className="ability-bindings__choices">
{pickerAbilities.map((ability) => (
@@ -368,8 +383,8 @@ function AbilityBindingsDialog() {
<footer className="game-panel__footer">
{restoreRequest
? <><span><kbd>×</kbd> Choose</span><span><kbd>○</kbd> Cancel</span></>
: <><span><kbd>×</kbd> Assign</span><span><kbd>□</kbd> Clear</span><span><kbd>○</kbd> Back</span><span><kbd>△</kbd> Restore layer</span><span><kbd>L1 / L2</kbd> Preview layer</span></>}
? <><span><kbd>{keyLabel("controller", "menu-confirm")}</kbd> Choose</span><span><kbd>{keyLabel("controller", "menu-back")}</kbd> Cancel</span></>
: <><span><kbd>{keyLabel("controller", "menu-confirm")}</kbd> Assign</span><span><kbd>{keyLabel("controller", "editor-clear")}</kbd> Clear</span><span><kbd>{keyLabel("controller", "menu-back")}</kbd> Back</span><span><kbd>{keyLabel("controller", "editor-reset")}</kbd> Restore layer</span></>}
</footer>
</section>
</div>