Files
healer-man/src/game/actionBindings.ts
T
2026-10-06 22:25:34 -04:00

216 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const ACTION_LAYERS = ["primary", "secondary", "tertiary"] as const;
export type ActionBindingLayer = typeof ACTION_LAYERS[number];
export const ACTION_CONTROLS = [
"face-top",
"face-right",
"face-bottom",
"face-left",
"r1",
"r2",
"dpad-left",
"dpad-right",
] as const;
export type ActionBindingControl = typeof ACTION_CONTROLS[number];
export type ActionBindingId = `${ActionBindingLayer}:${ActionBindingControl}`;
export type ActionBindings = Readonly<Record<ActionBindingId, string | null>>;
export interface ActionBindingControlDefinition {
readonly id: ActionBindingControl;
readonly label: string;
readonly gamepadLabel: string;
readonly keyboardLabel: string;
readonly gamepadToken: string;
readonly keyboardCode: string;
}
export const ACTION_LAYER_LABELS: Readonly<Record<ActionBindingLayer, string>> = Object.freeze({
primary: "Base",
secondary: "L1",
tertiary: "L2",
});
export const ACTION_CONTROL_DEFINITIONS: Readonly<Record<ActionBindingControl, ActionBindingControlDefinition>> = Object.freeze({
"face-top": {
id: "face-top",
label: "Triangle",
gamepadLabel: "△",
keyboardLabel: "3",
gamepadToken: "Button3",
keyboardCode: "Digit3",
},
"face-right": {
id: "face-right",
label: "Circle",
gamepadLabel: "○",
keyboardLabel: "4",
gamepadToken: "Button1",
keyboardCode: "Digit4",
},
"face-bottom": {
id: "face-bottom",
label: "Cross",
gamepadLabel: "×",
keyboardLabel: "1",
gamepadToken: "Button0",
keyboardCode: "Digit1",
},
"face-left": {
id: "face-left",
label: "Square",
gamepadLabel: "□",
keyboardLabel: "2",
gamepadToken: "Button2",
keyboardCode: "Digit2",
},
r1: {
id: "r1",
label: "R1",
gamepadLabel: "R1",
keyboardLabel: "5",
gamepadToken: "Button5",
keyboardCode: "Digit5",
},
r2: {
id: "r2",
label: "R2",
gamepadLabel: "R2",
keyboardLabel: "6",
gamepadToken: "Button7",
keyboardCode: "Digit6",
},
"dpad-left": {
id: "dpad-left",
label: "D-pad Left",
gamepadLabel: "←",
keyboardLabel: "7",
gamepadToken: "Button14",
keyboardCode: "Digit7",
},
"dpad-right": {
id: "dpad-right",
label: "D-pad Right",
gamepadLabel: "→",
keyboardLabel: "8",
gamepadToken: "Button15",
keyboardCode: "Digit8",
},
});
// Preserve the prototype's existing Cross, Square, Triangle progression for
// the first three abilities, then finish the face diamond before filling the
// remaining base and modifier slots.
const DEFAULT_ASSIGNMENT_CONTROLS: readonly ActionBindingControl[] = [
"face-bottom",
"face-left",
"face-top",
"face-right",
"r1",
"r2",
"dpad-left",
"dpad-right",
];
export function actionBindingId(
layer: ActionBindingLayer,
control: ActionBindingControl,
): ActionBindingId {
return `${layer}:${control}`;
}
export function emptyActionBindings(): Record<ActionBindingId, string | null> {
return Object.fromEntries(ACTION_LAYERS.flatMap((layer) => ACTION_CONTROLS.map((control) => [
actionBindingId(layer, control),
null,
]))) as Record<ActionBindingId, string | null>;
}
export function defaultActionBindings(abilityIds: readonly string[]): ActionBindings {
const bindings = emptyActionBindings();
const assignmentOrder = ACTION_LAYERS.flatMap((layer) => DEFAULT_ASSIGNMENT_CONTROLS.map((control) => (
actionBindingId(layer, control)
)));
abilityIds.slice(0, assignmentOrder.length).forEach((abilityId, index) => {
bindings[assignmentOrder[index]] = abilityId;
});
return bindings;
}
export function normalizeActionBindings(
value: unknown,
defaultAbilityIds: readonly string[],
allowedAbilityIds: readonly string[] = defaultAbilityIds,
): ActionBindings {
const defaults = defaultActionBindings(defaultAbilityIds);
if (!value || typeof value !== "object" || Array.isArray(value)) return defaults;
const source = value as Record<string, unknown>;
const allowed = new Set(allowedAbilityIds);
const bindings = { ...defaults };
for (const layer of ACTION_LAYERS) {
for (const control of ACTION_CONTROLS) {
const id = actionBindingId(layer, control);
if (!Object.prototype.hasOwnProperty.call(source, id)) continue;
const abilityId = source[id];
bindings[id] = abilityId === null
? null
: typeof abilityId === "string" && allowed.has(abilityId)
? abilityId
: null;
}
}
return bindings;
}
export function withActionBinding(
bindings: ActionBindings,
layer: ActionBindingLayer,
control: ActionBindingControl,
abilityId: string | null,
): ActionBindings {
return { ...bindings, [actionBindingId(layer, control)]: abilityId };
}
export function resetActionBindingLayer(
bindings: ActionBindings,
layer: ActionBindingLayer,
abilityIds: readonly string[],
): ActionBindings {
const defaults = defaultActionBindings(abilityIds);
const next = { ...bindings };
for (const control of ACTION_CONTROLS) {
const id = actionBindingId(layer, control);
next[id] = defaults[id];
}
return next;
}
export function actionLayerForModifiers(l1: boolean, l2: boolean): ActionBindingLayer {
if (l2) return "tertiary";
if (l1) return "secondary";
return "primary";
}
export function actionControlForControllerToken(token: string): ActionBindingControl | null {
return ACTION_CONTROLS.find((control) => ACTION_CONTROL_DEFINITIONS[control].gamepadToken === token) ?? null;
}
export function actionControlForKeyboardCode(code: string): ActionBindingControl | null {
return ACTION_CONTROLS.find((control) => ACTION_CONTROL_DEFINITIONS[control].keyboardCode === code) ?? null;
}
export function actionBindingChordLabel(
layer: ActionBindingLayer,
control: ActionBindingControl,
inputMode: "keyboard" | "gamepad" = "gamepad",
): string {
const definition = ACTION_CONTROL_DEFINITIONS[control];
if (inputMode === "keyboard") {
const modifier = layer === "secondary" ? "Shift + " : layer === "tertiary" ? "Alt + " : "";
return `${modifier}${definition.keyboardLabel}`;
}
const modifier = layer === "secondary" ? "L1 + " : layer === "tertiary" ? "L2 + " : "";
return `${modifier}${definition.label}`;
}