import {EventGamepad, EventGamepadAnalog, EventGamepadDigital} from '../index.js';
/**
* The ordered list of the axes event codes of the gamepad.
* @type {Array<Array<string>>}
* @constant
* @private
*/
const $GAMEPADAXES = [
['StickLeftLeft', 'StickLeftRight'],
['StickLeftUp', 'StickLeftDown'],
['StickRightLeft', 'StickRightRight'],
['StickRightUp', 'StickRightDown']
];
/**
* The ordered list of the buttons event codes of the gamepad.
* @type {Array<string>}
* @constant
* @private
*/
const $GAMEPADBUTTONS = [
'ClusterRightButtonBottom',
'ClusterRightButtonRight',
'ClusterRightButtonLeft',
'ClusterRightButtonTop',
'ClusterFrontButtonTopLeft',
'ClusterFrontButtonTopRight',
'ClusterFrontButtonBottomLeft',
'ClusterFrontButtonBottomRight',
'ClusterCenterButtonLeft',
'ClusterCenterButtonRight',
'StickLeftButton',
'StickRightButton',
'ClusterLeftButtonTop',
'ClusterLeftButtonBottom',
'ClusterLeftButtonLeft',
'ClusterLeftButtonRight',
'ClusterCenterButtonCenter'
];
/**
* The threshold of the gampead axes.
* @type {number}
* @constant
* @private
*/
const $THRESHOLDGAMEPADAXES = 0.5;
/**
* Creates gamepad extensions.
*
* @example
*
* ExtensionGamepad.activate();
*/
class ExtensionGamepad {
/**
* Stores the activated status.
* @type {boolean}
* @private
* @static
*/
static $activated = false;
/**
* Stores the index of the last connected gamepad.
* @type {number}
* @private
*/
$indexLastConnected;
/**
* Stores the gamepad state.
* @type {Object<string, boolean>}
* @private
*/
$stateGamepad;
/**
* Stores the unloaded status.
* @type {boolean}
* @private
*/
$unloaded;
/**
* Creates a new gamepad extension.
*/
constructor() {
this.$stateGamepad = {};
this.$unloaded = false;
[...$GAMEPADBUTTONS, ...$GAMEPADAXES.flat()].forEach(($code) => {
this.$stateGamepad[$code] = false;
});
window.addEventListener('beforeunload', this.$onBeforeUnload.bind(this));
window.addEventListener('gamepadconnected', this.$onConnect.bind(this));
window.addEventListener('gamepaddisconnected', this.$onDisconnect.bind(this));
window.addEventListener('gamepadvibrate', this.$onVibrate.bind(this));
window.requestAnimationFrame(this.$update.bind(this));
}
/**
* Activates the extension.
* @public
* @static
*/
static activate() {
if (ExtensionGamepad.$activated === true) {
return;
}
new ExtensionGamepad();
ExtensionGamepad.$activated = true;
}
/**
* Called when the scope is about to be unloaded.
* @private
*/
$onBeforeUnload() {
this.$unloaded = true;
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.$indexLastConnected];
if (typeof gamepad === 'undefined') {
return;
}
if (typeof gamepad.vibrationActuator === 'undefined') {
return;
}
gamepad.vibrationActuator.reset();
}
/**
* Called when the gamepad is connected.
* @param {GamepadEvent} $event The native gamepad connected event.
* @private
*/
$onConnect($event) {
if ($event.gamepad.mapping !== 'standard') {
return;
}
Object.entries(this.$stateGamepad).forEach(([$code, $activated]) => {
if ($activated === true) {
this.$stateGamepad[$code] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', $code));
}
});
this.$indexLastConnected = $event.gamepad.index;
window.dispatchEvent(new EventGamepadDigital('gamepadconnect', 'Connected'));
}
/**
* Called when the gamepad is disconnected.
* @param {GamepadEvent} $event The native gamepad disconnected event.
* @private
*/
$onDisconnect($event) {
if ($event.gamepad.index !== this.$indexLastConnected) {
return;
}
Object.entries(this.$stateGamepad).forEach(([$code, $activated]) => {
if ($activated === true) {
this.$stateGamepad[$code] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', $code));
}
});
this.$indexLastConnected = undefined;
window.dispatchEvent(new EventGamepadDigital('gamepadconnect', 'Disconnected'));
}
/**
* Called when a gamepad vibration is needed.
* @param {Event} $event The gamepad vibrate event.
* @private
*/
$onVibrate($event) {
if (this.$unloaded === true) {
return;
}
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.$indexLastConnected];
if (typeof gamepad === 'undefined') {
return;
}
if (typeof gamepad.vibrationActuator === 'undefined') {
return;
}
if ($event instanceof EventGamepadDigital
&& $event.code === 'VibrateEnd') {
gamepad.vibrationActuator.reset();
return;
}
if ($event instanceof EventGamepad
&& $event.code === 'VibrateStart') {
gamepad.vibrationActuator.playEffect('dual-rumble', {
startDelay: 0,
duration: $event.vibration.duration,
strongMagnitude: $event.vibration.intensityFrequencyLow,
weakMagnitude: $event.vibration.intensityFrequencyHigh
});
return;
}
}
/**
* Updates the state of the gamepad.
* @private
*/
$update() {
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.$indexLastConnected];
if (gamepad instanceof Gamepad) {
$GAMEPADBUTTONS.forEach(($button, $index) => {
const button = gamepad.buttons[$index];
if (button.pressed === true) {
if (this.$stateGamepad[$button] === false) {
this.$stateGamepad[$button] = true;
}
window.dispatchEvent(new EventGamepadDigital('gamepaddown', $button));
window.dispatchEvent(new EventGamepadAnalog('gamepadanalog', $button, button.value));
}
else {
if (this.$stateGamepad[$button] === true) {
this.$stateGamepad[$button] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', $button));
}
}
});
gamepad.axes.forEach(($direction, $index) => {
const [axeMinimum, axeMaximum] = $GAMEPADAXES[$index];
if ($direction <= - $THRESHOLDGAMEPADAXES) {
if (this.$stateGamepad[axeMaximum] === true) {
this.$stateGamepad[axeMaximum] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', axeMaximum));
}
this.$stateGamepad[axeMinimum] = true;
window.dispatchEvent(new EventGamepadDigital('gamepaddown', axeMinimum));
window.dispatchEvent(new EventGamepadAnalog('gamepadanalog', axeMinimum, ($direction - (Math.sign($direction) * $THRESHOLDGAMEPADAXES)) / (1 - $THRESHOLDGAMEPADAXES)));
}
else if ($direction >= $THRESHOLDGAMEPADAXES) {
if (this.$stateGamepad[axeMinimum] === true) {
this.$stateGamepad[axeMinimum] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', axeMinimum));
}
this.$stateGamepad[axeMaximum] = true;
window.dispatchEvent(new EventGamepadDigital('gamepaddown', axeMaximum));
window.dispatchEvent(new EventGamepadAnalog('gamepadanalog', axeMaximum, ($direction - (Math.sign($direction) * $THRESHOLDGAMEPADAXES)) / (1 - $THRESHOLDGAMEPADAXES)));
}
else {
if (this.$stateGamepad[axeMinimum] === true) {
this.$stateGamepad[axeMinimum] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', axeMinimum));
}
if (this.$stateGamepad[axeMaximum] === true) {
this.$stateGamepad[axeMaximum] = false;
window.dispatchEvent(new EventGamepadDigital('gamepadup', axeMaximum));
}
}
});
}
window.requestAnimationFrame(this.$update.bind(this));
}
}
export {
ExtensionGamepad
};
export default ExtensionGamepad;