351 lines
9.9 KiB
TypeScript
351 lines
9.9 KiB
TypeScript
import type {
|
|
PlayerStartGamepadActionBinding,
|
|
PlayerStartGamepadCameraLookBinding,
|
|
PlayerStartGamepadBinding,
|
|
PlayerStartInputBindings
|
|
} from "../entities/entity-instances";
|
|
|
|
const GAMEPAD_AXIS_DEADZONE = 0.18;
|
|
|
|
export interface PlayerStartMovementActionState {
|
|
moveForward: number;
|
|
moveBackward: number;
|
|
moveLeft: number;
|
|
moveRight: number;
|
|
}
|
|
|
|
export interface PlayerStartActionInputState
|
|
extends PlayerStartMovementActionState {
|
|
jump: number;
|
|
sprint: number;
|
|
crouch: number;
|
|
interact: number;
|
|
clearTarget: number;
|
|
pauseTime: number;
|
|
}
|
|
|
|
export interface PlayerStartLookInputState {
|
|
horizontal: number;
|
|
vertical: number;
|
|
}
|
|
|
|
function clampUnitInterval(value: number): number {
|
|
return Math.max(0, Math.min(1, value));
|
|
}
|
|
|
|
function readGamepadButtonStrength(button: GamepadButton | undefined): number {
|
|
if (button === undefined) {
|
|
return 0;
|
|
}
|
|
|
|
return clampUnitInterval(button.pressed ? Math.max(button.value, 1) : button.value);
|
|
}
|
|
|
|
function readCenteredAxisValue(value: number | undefined): number {
|
|
if (value === undefined || !Number.isFinite(value)) {
|
|
return 0;
|
|
}
|
|
|
|
const magnitude = Math.abs(value);
|
|
|
|
if (magnitude <= GAMEPAD_AXIS_DEADZONE) {
|
|
return 0;
|
|
}
|
|
|
|
const normalizedMagnitude = clampUnitInterval(
|
|
(magnitude - GAMEPAD_AXIS_DEADZONE) / (1 - GAMEPAD_AXIS_DEADZONE)
|
|
);
|
|
|
|
return Math.sign(value) * normalizedMagnitude;
|
|
}
|
|
|
|
function readPositiveAxisStrength(value: number | undefined): number {
|
|
if (value === undefined || !Number.isFinite(value) || value <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (value <= GAMEPAD_AXIS_DEADZONE) {
|
|
return 0;
|
|
}
|
|
|
|
return clampUnitInterval(
|
|
(value - GAMEPAD_AXIS_DEADZONE) / (1 - GAMEPAD_AXIS_DEADZONE)
|
|
);
|
|
}
|
|
|
|
function readNegativeAxisStrength(value: number | undefined): number {
|
|
return readPositiveAxisStrength(
|
|
value === undefined || !Number.isFinite(value) ? value : -value
|
|
);
|
|
}
|
|
|
|
function readSingleGamepadBinding(
|
|
gamepad: Gamepad,
|
|
binding: PlayerStartGamepadBinding
|
|
): number {
|
|
switch (binding) {
|
|
case "leftStickUp":
|
|
return readNegativeAxisStrength(gamepad.axes[1]);
|
|
case "leftStickDown":
|
|
return readPositiveAxisStrength(gamepad.axes[1]);
|
|
case "leftStickLeft":
|
|
return readNegativeAxisStrength(gamepad.axes[0]);
|
|
case "leftStickRight":
|
|
return readPositiveAxisStrength(gamepad.axes[0]);
|
|
case "dpadUp":
|
|
return readGamepadButtonStrength(gamepad.buttons[12]);
|
|
case "dpadDown":
|
|
return readGamepadButtonStrength(gamepad.buttons[13]);
|
|
case "dpadLeft":
|
|
return readGamepadButtonStrength(gamepad.buttons[14]);
|
|
case "dpadRight":
|
|
return readGamepadButtonStrength(gamepad.buttons[15]);
|
|
}
|
|
}
|
|
|
|
function readGamepadBindingStrength(
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined,
|
|
binding: PlayerStartGamepadBinding
|
|
): number {
|
|
if (gamepads === undefined || gamepads === null) {
|
|
return 0;
|
|
}
|
|
|
|
let strength = 0;
|
|
|
|
for (let index = 0; index < gamepads.length; index += 1) {
|
|
const gamepad = gamepads[index];
|
|
|
|
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
|
|
continue;
|
|
}
|
|
|
|
strength = Math.max(strength, readSingleGamepadBinding(gamepad, binding));
|
|
}
|
|
|
|
return strength;
|
|
}
|
|
|
|
function readSingleGamepadActionBinding(
|
|
gamepad: Gamepad,
|
|
binding: PlayerStartGamepadActionBinding
|
|
): number {
|
|
switch (binding) {
|
|
case "buttonSouth":
|
|
return readGamepadButtonStrength(gamepad.buttons[0]);
|
|
case "buttonEast":
|
|
return readGamepadButtonStrength(gamepad.buttons[1]);
|
|
case "buttonWest":
|
|
return readGamepadButtonStrength(gamepad.buttons[2]);
|
|
case "buttonNorth":
|
|
return readGamepadButtonStrength(gamepad.buttons[3]);
|
|
case "buttonMenu":
|
|
return readGamepadButtonStrength(gamepad.buttons[9]);
|
|
case "leftShoulder":
|
|
return readGamepadButtonStrength(gamepad.buttons[4]);
|
|
case "rightShoulder":
|
|
return readGamepadButtonStrength(gamepad.buttons[5]);
|
|
case "leftTrigger":
|
|
return readGamepadButtonStrength(gamepad.buttons[6]);
|
|
case "rightTrigger":
|
|
return readGamepadButtonStrength(gamepad.buttons[7]);
|
|
case "leftStickPress":
|
|
return readGamepadButtonStrength(gamepad.buttons[10]);
|
|
case "rightStickPress":
|
|
return readGamepadButtonStrength(gamepad.buttons[11]);
|
|
}
|
|
}
|
|
|
|
function readGamepadActionBindingStrength(
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined,
|
|
binding: PlayerStartGamepadActionBinding
|
|
): number {
|
|
if (gamepads === undefined || gamepads === null) {
|
|
return 0;
|
|
}
|
|
|
|
let strength = 0;
|
|
|
|
for (let index = 0; index < gamepads.length; index += 1) {
|
|
const gamepad = gamepads[index];
|
|
|
|
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
|
|
continue;
|
|
}
|
|
|
|
strength = Math.max(strength, readSingleGamepadActionBinding(gamepad, binding));
|
|
}
|
|
|
|
return strength;
|
|
}
|
|
|
|
function readSingleGamepadCameraLook(
|
|
gamepad: Gamepad,
|
|
binding: PlayerStartGamepadCameraLookBinding
|
|
): PlayerStartLookInputState {
|
|
switch (binding) {
|
|
case "rightStick":
|
|
return {
|
|
horizontal: readCenteredAxisValue(gamepad.axes[2]),
|
|
vertical: -readCenteredAxisValue(gamepad.axes[3])
|
|
};
|
|
}
|
|
}
|
|
|
|
function readGamepadCameraLook(
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined,
|
|
binding: PlayerStartGamepadCameraLookBinding
|
|
): PlayerStartLookInputState {
|
|
if (gamepads === undefined || gamepads === null) {
|
|
return {
|
|
horizontal: 0,
|
|
vertical: 0
|
|
};
|
|
}
|
|
|
|
let horizontal = 0;
|
|
let vertical = 0;
|
|
|
|
for (let index = 0; index < gamepads.length; index += 1) {
|
|
const gamepad = gamepads[index];
|
|
|
|
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
|
|
continue;
|
|
}
|
|
|
|
const look = readSingleGamepadCameraLook(gamepad, binding);
|
|
|
|
if (Math.abs(look.horizontal) > Math.abs(horizontal)) {
|
|
horizontal = look.horizontal;
|
|
}
|
|
|
|
if (Math.abs(look.vertical) > Math.abs(vertical)) {
|
|
vertical = look.vertical;
|
|
}
|
|
}
|
|
|
|
return {
|
|
horizontal,
|
|
vertical
|
|
};
|
|
}
|
|
|
|
export function getAvailableGamepads(): ArrayLike<Gamepad | null> | undefined {
|
|
if (
|
|
typeof navigator === "undefined" ||
|
|
typeof navigator.getGamepads !== "function"
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
return navigator.getGamepads();
|
|
}
|
|
|
|
export function resolvePlayerStartMovementActions(
|
|
pressedKeys: ReadonlySet<string>,
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): PlayerStartMovementActionState {
|
|
const actionInputs = resolvePlayerStartActionInputs(
|
|
pressedKeys,
|
|
bindings,
|
|
gamepads
|
|
);
|
|
|
|
return {
|
|
moveForward: actionInputs.moveForward,
|
|
moveBackward: actionInputs.moveBackward,
|
|
moveLeft: actionInputs.moveLeft,
|
|
moveRight: actionInputs.moveRight
|
|
};
|
|
}
|
|
|
|
export function resolvePlayerStartActionInputs(
|
|
pressedKeys: ReadonlySet<string>,
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): PlayerStartActionInputState {
|
|
return {
|
|
moveForward: Math.max(
|
|
pressedKeys.has(bindings.keyboard.moveForward) ? 1 : 0,
|
|
readGamepadBindingStrength(gamepads, bindings.gamepad.moveForward)
|
|
),
|
|
moveBackward: Math.max(
|
|
pressedKeys.has(bindings.keyboard.moveBackward) ? 1 : 0,
|
|
readGamepadBindingStrength(gamepads, bindings.gamepad.moveBackward)
|
|
),
|
|
moveLeft: Math.max(
|
|
pressedKeys.has(bindings.keyboard.moveLeft) ? 1 : 0,
|
|
readGamepadBindingStrength(gamepads, bindings.gamepad.moveLeft)
|
|
),
|
|
moveRight: Math.max(
|
|
pressedKeys.has(bindings.keyboard.moveRight) ? 1 : 0,
|
|
readGamepadBindingStrength(gamepads, bindings.gamepad.moveRight)
|
|
),
|
|
jump: Math.max(
|
|
pressedKeys.has(bindings.keyboard.jump) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.jump)
|
|
),
|
|
sprint: Math.max(
|
|
pressedKeys.has(bindings.keyboard.sprint) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.sprint)
|
|
),
|
|
crouch: Math.max(
|
|
pressedKeys.has(bindings.keyboard.crouch) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.crouch)
|
|
),
|
|
interact: Math.max(
|
|
pressedKeys.has(bindings.keyboard.interact) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.interact)
|
|
),
|
|
clearTarget: Math.max(
|
|
pressedKeys.has(bindings.keyboard.clearTarget) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.clearTarget)
|
|
),
|
|
pauseTime: Math.max(
|
|
pressedKeys.has(bindings.keyboard.pauseTime) ? 1 : 0,
|
|
readGamepadActionBindingStrength(gamepads, bindings.gamepad.pauseTime)
|
|
)
|
|
};
|
|
}
|
|
|
|
export function resolvePlayerStartPauseInput(
|
|
pressedKeys: ReadonlySet<string>,
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): number {
|
|
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
|
|
.pauseTime;
|
|
}
|
|
|
|
export function resolvePlayerStartInteractInput(
|
|
pressedKeys: ReadonlySet<string>,
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): number {
|
|
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
|
|
.interact;
|
|
}
|
|
|
|
export function resolvePlayerStartClearTargetInput(
|
|
pressedKeys: ReadonlySet<string>,
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): number {
|
|
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
|
|
.clearTarget;
|
|
}
|
|
|
|
export function resolveDefaultTargetCycleInput(
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): number {
|
|
return readGamepadActionBindingStrength(gamepads, "rightStickPress");
|
|
}
|
|
|
|
export function resolvePlayerStartLookInput(
|
|
bindings: PlayerStartInputBindings,
|
|
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
|
|
): PlayerStartLookInputState {
|
|
return readGamepadCameraLook(gamepads, bindings.gamepad.cameraLook);
|
|
}
|