mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-06 13:18:27 +02:00
Controller customization feature
This commit is contained in:
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user