forked from phenom/healer-man
Release Healer Man 0.1.29
This commit is contained in:
1 parent
8c9809d3e0
commit
544c1bee91
120 files changed
+4363
-1036
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user