Controller customization feature

This commit is contained in:
redphx
2024-12-22 17:17:03 +07:00
parent 8ef5a95c88
commit 7b60ba3a3e
89 changed files with 3286 additions and 1188 deletions

View File

@@ -1,32 +1,33 @@
import { PrefKey } from "@/enums/pref-keys";
import { PrefKey, type PrefTypeMap } from "@/enums/pref-keys";
import { ControllerSettingsTable } from "./local-db/controller-settings-table";
import { ControllerShortcutsTable } from "./local-db/controller-shortcuts-table";
import { getPref, setPref } from "./settings-storages/global-settings-storage";
import type { ControllerShortcutPresetRecord, KeyboardShortcutConvertedPresetData, MkbConvertedPresetData } from "@/types/presets";
import type { ControllerCustomizationConvertedPresetData, ControllerCustomizationPresetData, ControllerShortcutPresetRecord, KeyboardShortcutConvertedPresetData, MkbConvertedPresetData } from "@/types/presets";
import { STATES } from "./global";
import { DeviceVibrationMode } from "@/enums/pref-values";
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
import { hasGamepad } from "./gamepad";
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
import type { GamepadKey } from "@/enums/gamepad";
import { GamepadKey } from "@/enums/gamepad";
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table";
import { ShortcutAction } from "@/enums/shortcut-actions";
import { KeyHelper } from "@/modules/mkb/key-helper";
import { BxEventBus } from "./bx-event-bus";
import { ControllerCustomizationsTable } from "./local-db/controller-customizations-table";
export type StreamSettingsData = {
settings: Partial<Record<PrefKey, any>>;
xCloudPollingMode: 'none' | 'callbacks' | 'navigation' | 'all';
deviceVibrationIntensity: DeviceVibrationIntensity;
deviceVibrationIntensity: number;
controllerPollingRate: ControllerPollingRate;
controllerPollingRate: number;
controllers: {
[gamepadId: string]: {
vibrationIntensity: number;
shortcuts: ControllerShortcutPresetRecord['data']['mapping'] | null;
customization: ControllerCustomizationConvertedPresetData | null;
};
};
@@ -50,7 +51,33 @@ export class StreamSettings {
keyboardShortcuts: {},
};
static getPref<T=boolean>(key: PrefKey) {
private static CONTROLLER_CUSTOMIZATION_MAPPING: { [key in GamepadKey]?: keyof XcloudGamepad } = {
[GamepadKey.A]: 'A',
[GamepadKey.B]: 'B',
[GamepadKey.X]: 'X',
[GamepadKey.Y]: 'Y',
[GamepadKey.UP]: 'DPadUp',
[GamepadKey.RIGHT]: 'DPadRight',
[GamepadKey.DOWN]: 'DPadDown',
[GamepadKey.LEFT]: 'DPadLeft',
[GamepadKey.LB]: 'LeftShoulder',
[GamepadKey.RB]: 'RightShoulder',
[GamepadKey.LT]: 'LeftTrigger',
[GamepadKey.RT]: 'RightTrigger',
[GamepadKey.L3]: 'LeftThumb',
[GamepadKey.R3]: 'RightThumb',
[GamepadKey.LS]: 'LeftStickAxes',
[GamepadKey.RS]: 'RightStickAxes',
[GamepadKey.SELECT]: 'View',
[GamepadKey.START]: 'Menu',
[GamepadKey.SHARE]: 'Share',
};
static getPref<T extends keyof PrefTypeMap>(key: T) {
return getPref<T>(key);
}
@@ -60,6 +87,7 @@ export class StreamSettings {
const settingsTable = ControllerSettingsTable.getInstance();
const shortcutsTable = ControllerShortcutsTable.getInstance();
const mappingTable = ControllerCustomizationsTable.getInstance();
const gamepads = window.navigator.getGamepads();
for (const gamepad of gamepads) {
@@ -74,17 +102,17 @@ export class StreamSettings {
const settingsData = await settingsTable.getControllerData(gamepad.id);
let shortcutsMapping;
const preset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
if (!preset) {
shortcutsMapping = null;
} else {
shortcutsMapping = preset.data.mapping;
}
// Shortcuts
const shortcutsPreset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
const shortcutsMapping = !shortcutsPreset ? null : shortcutsPreset.data.mapping;
// Mapping
const customizationPreset = await mappingTable.getPreset(settingsData.customizationPresetId);
const customizationData = StreamSettings.convertControllerCustomization(customizationPreset?.data);
controllers[gamepad.id] = {
vibrationIntensity: settingsData.vibrationIntensity,
shortcuts: shortcutsMapping,
customization: customizationData,
}
}
settings.controllers = controllers;
@@ -95,18 +123,66 @@ export class StreamSettings {
await StreamSettings.refreshDeviceVibration();
}
private static preCalculateControllerRange(obj: Record<string, [number, number]>, target: keyof XcloudGamepad, values: [number, number] | undefined) {
if (values && Array.isArray(values)) {
const [from, to] = values;
if (from > 1 || to < 100) {
obj[target] = [from / 100, to / 100];
}
}
}
private static convertControllerCustomization(customization: ControllerCustomizationPresetData | null | undefined) {
if (!customization) {
return null;
}
const converted = {
mapping: {},
ranges: {},
vibrationIntensity: 1,
} as ControllerCustomizationConvertedPresetData;
// Swap GamepadKey.A with "A"
let gamepadKey: unknown;
for (gamepadKey in customization.mapping) {
const gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey as GamepadKey];
if (!gamepadStr) {
continue;
}
const mappedKey = customization.mapping[gamepadKey as GamepadKey];
if (typeof mappedKey === 'number') {
converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey as GamepadKey];
} else {
converted.mapping[gamepadStr] = false;
}
}
// Pre-calculate ranges & deadzone
StreamSettings.preCalculateControllerRange(converted.ranges, 'LeftTrigger', customization.settings.leftTriggerRange);
StreamSettings.preCalculateControllerRange(converted.ranges, 'RightTrigger', customization.settings.rightTriggerRange);
StreamSettings.preCalculateControllerRange(converted.ranges, 'LeftThumb', customization.settings.leftStickDeadzone);
StreamSettings.preCalculateControllerRange(converted.ranges, 'RightThumb', customization.settings.rightStickDeadzone);
// Pre-calculate virabtionIntensity
converted.vibrationIntensity = customization.settings.vibrationIntensity / 100;
return converted;
}
private static async refreshDeviceVibration() {
if (!STATES.browser.capabilities.deviceVibration) {
return;
}
const mode = StreamSettings.getPref<DeviceVibrationMode>(PrefKey.DEVICE_VIBRATION_MODE);
const mode = StreamSettings.getPref(PrefKey.DEVICE_VIBRATION_MODE);
let intensity = 0; // Disable
// Enable when no controllers are detected in Auto mode
if (mode === DeviceVibrationMode.ON || (mode === DeviceVibrationMode.AUTO && !hasGamepad())) {
// Set intensity
intensity = StreamSettings.getPref<DeviceVibrationIntensity>(PrefKey.DEVICE_VIBRATION_INTENSITY) / 100;
intensity = StreamSettings.getPref(PrefKey.DEVICE_VIBRATION_INTENSITY) / 100;
}
StreamSettings.settings.deviceVibrationIntensity = intensity;
@@ -116,7 +192,7 @@ export class StreamSettings {
static async refreshMkbSettings() {
const settings = StreamSettings.settings;
let presetId = StreamSettings.getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID);
let presetId = StreamSettings.getPref(PrefKey.MKB_P1_MAPPING_PRESET_ID);
const orgPreset = (await MkbMappingPresetsTable.getInstance().getPreset(presetId))!;
const orgPresetData = orgPreset.data;
@@ -154,7 +230,7 @@ export class StreamSettings {
static async refreshKeyboardShortcuts() {
const settings = StreamSettings.settings;
let presetId = StreamSettings.getPref<KeyboardShortcutsPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID);
let presetId = StreamSettings.getPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID);
if (presetId === KeyboardShortcutDefaultId.OFF) {
settings.keyboardShortcuts = null;