dualsense-ts
Version:
A natural interface for your DualSense controller, with Typescript
277 lines (254 loc) โข 8.75 kB
text/typescript
import {
Momentary,
Dpad,
DpadParams,
Mute,
Unisense,
UnisenseParams,
Touchpad,
Gyroscope,
GyroscopeParams,
Accelerometer,
AccelerometerParams,
} from "./elements";
import { Input, InputSet, InputParams } from "./input";
import {
DualsenseHIDState,
DualsenseHID,
PlatformHIDProvider,
InputId,
} from "./hid";
import { Intensity } from "./math";
/** Settings for your Dualsense controller and each of its inputs */
export interface DualsenseParams extends InputParams {
/** Sets the source for HID events. Default: decide automatically */
hid?: DualsenseHID | null;
/** Settings for the Playstation button */
ps?: InputParams;
/** Settings for the mute button */
mute?: InputParams;
/** Settings for the options button */
options?: InputParams;
/** Settings for the create button */
create?: InputParams;
/** Settings for the triangle button */
triangle?: InputParams;
/** Settings for the circle button */
circle?: InputParams;
/** Settings for the cross button */
cross?: InputParams;
/** Settings for the square button */
square?: InputParams;
/** Settings for the dpad buttons */
dpad?: DpadParams;
/** Settings for inputs on the left half of the controller */
left?: UnisenseParams;
/** Settings for inputs on the right side of the controller */
right?: UnisenseParams;
/** Settings for the touchpad inputs */
touchpad?: InputParams;
/** Settings for the gyroscope */
gyroscope?: GyroscopeParams;
/** Settings for the accelerometer */
accelerometer?: AccelerometerParams;
}
/** Represents a Dualsense controller */
export class Dualsense extends Input<Dualsense> {
public readonly state: Dualsense = this;
/** The Playstation button */
public readonly ps: Momentary;
/** The mute button and status light */
public readonly mute: Mute;
/** The options button */
public readonly options: Momentary;
/** The create button */
public readonly create: Momentary;
/** The triangle button */
public readonly triangle: Momentary;
/** The circle button */
public readonly circle: Momentary;
/** The cross, or X button */
public readonly cross: Momentary;
/** The square button */
public readonly square: Momentary;
/** The up/down/left/right dpad buttons */
public readonly dpad: Dpad;
/** Inputs on the left half of the controller */
public readonly left: Unisense;
/** Inputs on the right half of the controller */
public readonly right: Unisense;
/** The touchpad; works like a pair of analog sticks */
public readonly touchpad: Touchpad;
/** Tracks the controller's angular velocity */
public readonly gyroscope: Gyroscope;
/** Tracks the controller's linear acceleration */
public readonly accelerometer: Accelerometer;
/** Represents the underlying HID device. Provides input events */
public readonly hid: DualsenseHID;
/** A virtual button representing whether or not a controller is connected */
public readonly connection: Momentary;
public get active(): boolean {
return Object.values(this).some(
(input) => input !== this && input instanceof Input && input.active
);
}
constructor(params: DualsenseParams = {}) {
super(params);
this.ps = new Momentary({
icon: "ใฐ",
name: "Home",
...(params.ps ?? {}),
});
this.mute = new Mute({
icon: "๐ฉ",
name: "Mute",
...(params.mute ?? {}),
});
this.options = new Momentary({
icon: "โฏ",
name: "Options",
...(params.options ?? {}),
});
this.create = new Momentary({
icon: "๐",
name: "Create",
...(params.create ?? {}),
});
this.triangle = new Momentary({
icon: "๐",
name: "Triangle",
...(params.triangle ?? {}),
});
this.circle = new Momentary({
icon: "โ",
name: "Circle",
...(params.circle ?? {}),
});
this.cross = new Momentary({
icon: "โฎฟ",
name: "Cross",
...(params.cross ?? {}),
});
this.square = new Momentary({
icon: "๐",
name: "Square",
...(params.square ?? {}),
});
this.dpad = new Dpad({
icon: "D",
name: "D-pad",
...(params.dpad ?? {}),
});
this.left = new Unisense({
icon: "L",
name: "Left",
...(params.left ?? {}),
});
this.right = new Unisense({
icon: "R",
name: "Right",
...(params.right ?? {}),
});
this.touchpad = new Touchpad({
icon: "โ",
name: "Touchpad",
...(params.touchpad ?? {}),
});
this.connection = new Momentary({
icon: "๐",
name: "Connected",
...(params.square ?? {}),
});
this.gyroscope = new Gyroscope({
icon: "โ",
name: "Gyroscope",
threshold: 0.01,
...(params.gyroscope ?? {}),
});
this.accelerometer = new Accelerometer({
icon: "โคฒ",
name: "Accelerometer",
threshold: 0.01,
...(params.accelerometer ?? {}),
});
this.connection[InputSet](false);
this.hid = params.hid ?? new DualsenseHID(new PlatformHIDProvider());
this.hid.register((state: DualsenseHIDState) => {
this.processHID(state);
});
/** Refresh connection state */
setInterval(() => {
const {
provider: { connected },
} = this.hid;
this.connection[InputSet](connected);
if (!connected) this.hid.provider.connect();
}, 200);
/** Refresh rumble state */
const rumbleMemo = { left: -1, right: -1 };
setInterval(() => {
const left = this.left.rumble();
const right = this.right.rumble();
if (
this.connection.active &&
(left !== rumbleMemo.left || right !== rumbleMemo.right)
) {
this.hid.setRumble(left * 255, right * 255);
rumbleMemo.left = left;
rumbleMemo.right = right;
}
}, 1000 / 30);
}
private get rumbleIntensity(): number {
return (this.left.rumble() + this.right.rumble()) / 2;
}
/** Check or adjust rumble intensity evenly across both sides of the controller */
public rumble(intensity?: Intensity): number {
this.left.rumble(intensity);
this.right.rumble(intensity);
return this.rumbleIntensity;
}
/** Distributes HID event values to the controller's inputs */
private processHID(state: DualsenseHIDState): void {
this.ps[InputSet](state[InputId.Playstation]);
this.options[InputSet](state[InputId.Options]);
this.create[InputSet](state[InputId.Create]);
this.mute[InputSet](state[InputId.Mute]);
this.mute.status[InputSet](state[InputId.Status]);
this.triangle[InputSet](state[InputId.Triangle]);
this.circle[InputSet](state[InputId.Circle]);
this.cross[InputSet](state[InputId.Cross]);
this.square[InputSet](state[InputId.Square]);
this.dpad.up[InputSet](state[InputId.Up]);
this.dpad.down[InputSet](state[InputId.Down]);
this.dpad.right[InputSet](state[InputId.Right]);
this.dpad.left[InputSet](state[InputId.Left]);
this.touchpad.button[InputSet](state[InputId.TouchButton]);
this.touchpad.left.x[InputSet](state[InputId.TouchX0]);
this.touchpad.left.y[InputSet](state[InputId.TouchY0]);
this.touchpad.left.contact[InputSet](state[InputId.TouchContact0]);
this.touchpad.left.tracker[InputSet](state[InputId.TouchId0]);
this.touchpad.right.x[InputSet](state[InputId.TouchX1]);
this.touchpad.right.y[InputSet](state[InputId.TouchY1]);
this.touchpad.right.contact[InputSet](state[InputId.TouchContact1]);
this.touchpad.right.tracker[InputSet](state[InputId.TouchId1]);
this.left.analog.x[InputSet](state[InputId.LeftAnalogX]);
this.left.analog.y[InputSet](state[InputId.LeftAnalogY]);
this.left.analog.button[InputSet](state[InputId.LeftAnalogButton]);
this.left.bumper[InputSet](state[InputId.LeftBumper]);
this.left.trigger[InputSet](state[InputId.LeftTrigger]);
this.left.trigger.button[InputSet](state[InputId.LeftTriggerButton]);
this.right.analog.x[InputSet](state[InputId.RightAnalogX]);
this.right.analog.y[InputSet](state[InputId.RightAnalogY]);
this.right.analog.button[InputSet](state[InputId.RightAnalogButton]);
this.right.bumper[InputSet](state[InputId.RightBumper]);
this.right.trigger[InputSet](state[InputId.RightTrigger]);
this.right.trigger.button[InputSet](state[InputId.RightTriggerButton]);
this.gyroscope.x[InputSet](state[InputId.GyroX]);
this.gyroscope.y[InputSet](state[InputId.GyroY]);
this.gyroscope.z[InputSet](state[InputId.GyroZ]);
this.accelerometer.x[InputSet](state[InputId.AccelX]);
this.accelerometer.y[InputSet](state[InputId.AccelY]);
this.accelerometer.z[InputSet](state[InputId.AccelZ]);
}
}