waves/public/assets/g/turbowarp/main/js/addon-entry-gamepad.js
2025-04-09 17:11:14 -05:00

1911 lines
65 KiB
JavaScript

(window["webpackJsonpGUI"] = window["webpackJsonpGUI"] || []).push([["addon-entry-gamepad"],{
/***/ "./node_modules/css-loader/index.js!./src/addons/addons/gamepad/gamepadlib.css":
/*!****************************************************************************!*\
!*** ./node_modules/css-loader!./src/addons/addons/gamepad/gamepadlib.css ***!
\****************************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
var escape = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/url/escape.js */ "./node_modules/css-loader/lib/url/escape.js");
exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
// imports
// module
exports.push([module.i, ".gamepadlib-selector {\n width: 100%;\n margin-bottom: 3px;\n}\n\n.gamepadlib-content {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n}\n.gamepadlib-content-buttons {\n padding-right: 10px;\n}\n\n.gamepadlib-mapping {\n display: flex;\n align-items: center;\n margin-bottom: 3px;\n}\n.gamepadlib-mapping-label {\n width: 100px;\n text-align: center;\n}\n.gamepadlib-keyinput {\n text-align: center;\n width: 75px;\n height: 25px;\n border-radius: 0;\n border: 1px solid black;\n background: white;\n color: black;\n box-sizing: border-box;\n padding: 0;\n margin: 0;\n}\n[theme=\"dark\"] .gamepadlib-keyinput {\n background-color: var(--ui-tertiary);\n border-color: var(--ui-black-transparent);\n color: var(--text-primary);\n}\n.gamepadlib-mapping[data-value=\"1\"] .gamepadlib-keyinput {\n background: yellow;\n}\n[theme=\"dark\"] .gamepadlib-mapping[data-value=\"1\"] .gamepadlib-keyinput {\n background: hsl(60, 100%, 20%)\n}\n.gamepadlib-keyinput[data-accepting-input=\"true\"] {\n background: #d6fff9;\n}\n[theme=\"dark\"] .gamepadlib-keyinput[data-accepting-input=\"true\"] {\n background: hsl(171, 100%, 20%);\n}\n.gamepadlib-keyinput[data-empty=\"true\"]:not([data-accepting-input=\"true\"]) {\n color: #aaa;\n font-style: italic;\n}\n\n.gamepadlib-axis {\n margin-bottom: 8px;\n text-align: center;\n}\n.gamepadlib-axis-circle {\n position: relative;\n width: 150px;\n height: 150px;\n border: 1px solid black;\n overflow: hidden;\n}\n[theme=\"dark\"] .gamepadlib-axis-circle {\n border-color: var(--ui-black-transparent);\n}\n.gamepadlib-axis-dot {\n position: absolute;\n top: 50%;\n left: 50%;\n background-image: url(" + escape(__webpack_require__(/*! ./dot.svg */ "./src/addons/addons/gamepad/dot.svg")) + ");\n width: 8px;\n height: 8px;\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n.gamepadlib-axis-mapping {\n width: 100%;\n}\n\n.gamepadlib-axis-circle-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n.gamepadlib-axis-circle-overlay > .gamepadlib-axis-mapper {\n position: absolute;\n}\n.gamepadlib-axis-circle-overlay > .gamepadlib-axis-mapper:nth-of-type(1) {\n left: 50%;\n top: 0;\n transform: translateX(-50%);\n}\n.gamepadlib-axis-circle-overlay > .gamepadlib-axis-mapper:nth-of-type(2) {\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n}\n.gamepadlib-axis-circle-overlay > .gamepadlib-axis-mapper:nth-of-type(3) {\n right: 0;\n top: 50%;\n transform: translateY(-50%);\n}\n.gamepadlib-axis-circle-overlay > .gamepadlib-axis-mapper:nth-of-type(4) {\n left: 50%;\n bottom: 0;\n transform: translateX(-50%);\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/css-loader/index.js!./src/addons/addons/gamepad/style.css":
/*!***********************************************************************!*\
!*** ./node_modules/css-loader!./src/addons/addons/gamepad/style.css ***!
\***********************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
// imports
// module
exports.push([module.i, "[dir=\"ltr\"] .sa-gamepad-container {\n margin-right: 0.2rem;\n}\n[dir=\"rtl\"] .sa-gamepad-container {\n margin-left: 0.2rem;\n}\n\n.sa-gamepad-popup-outer {\n /* above fullscreen */\n z-index: 99999;\n}\n.sa-gamepad-popup {\n box-sizing: border-box;\n width: 700px;\n max-height: min(800px, 85vh);\n height: 100%;\n max-width: 85%;\n margin: 50px auto;\n display: flex;\n flex-direction: column;\n}\n.sa-gamepad-popup-content {\n padding: 1.5rem 2.25rem;\n height: 100%;\n overflow-y: auto;\n}\n\n.sa-gamepad-popup [class*=\"modal_header-item-title\"] {\n margin: 0 -20rem 0 0;\n}\n\n.sa-gamepad-cursor {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 9999;\n user-select: none;\n pointer-events: none;\n will-change: transform;\n image-rendering: optimizeSpeed;\n image-rendering: crisp-edges;\n image-rendering: pixelated;\n}\n.sa-gamepad-cursor-down {\n filter: invert(100%);\n}\n\n.sa-gamepad-small .sa-gamepad-container[data-editor-mode=\"editor\"] {\n display: none !important;\n}\n\n.sa-gamepad-hide-cursor [class^=\"stage_stage_\"] {\n cursor: none;\n}\n\n.sa-gamepad-browser-support-warning {\n font-weight: bold;\n margin-bottom: 10px;\n}\n\n.sa-gamepad-extra-options {\n display: none;\n}\n.sa-gamepad-has-controller .sa-gamepad-extra-options {\n display: block;\n}\n\n.sa-gamepad-store-settings {\n display: block;\n}\n.sa-gamepad-store-settings > input {\n margin-right: 4px;\n}\n\n.sa-gamepad-reset-button {\n margin: 8px 8px 8px 0;\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/active.png":
/*!************************************************************************************!*\
!*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/active.png ***!
\************************************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGAQMAAADaAn0LAAAABlBMVEX///8AAABVwtN+AAAADklEQVQI12MAAQMgBAIAAkwAYUis6mUAAAAASUVORK5CYII=");
/***/ }),
/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/close.svg":
/*!***********************************************************************************!*\
!*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/close.svg ***!
\***********************************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyBkYXRhLW5hbWU9IkxheWVyIDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDcuNDggNy40OCI+PHBhdGggZD0iTTMuNzQgNi40OFYxTTEgMy43NGg1LjQ4IiBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MnB4Ii8+PC9zdmc+");
/***/ }),
/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/cursor.png":
/*!************************************************************************************!*\
!*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/cursor.png ***!
\************************************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGAQMAAADaAn0LAAAABlBMVEUAAAD///+l2Z/dAAAADklEQVQI12MAAQMgBAIAAkwAYUis6mUAAAAASUVORK5CYII=");
/***/ }),
/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/dot.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/dot.svg ***!
\*********************************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4IiBoZWlnaHQ9IjgiIHZpZXdCb3g9IjAgMCAyLjExNyAyLjExNyI+PGNpcmNsZSBjeD0iMS4wNTgiIGN5PSIxLjA1OCIgcj0iMS4wNTgiIGZpbGw9InJlZCIvPjwvc3ZnPg==");
/***/ }),
/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/gamepad.svg":
/*!*************************************************************************************!*\
!*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/gamepad.svg ***!
\*************************************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtaGlkZGVuPSJ0cnVlIiB3aWR0aD0iMWVtIiBoZWlnaHQ9IjFlbSIgc3R5bGU9Ii1tcy10cmFuc2Zvcm06cm90YXRlKDM2MGRlZyk7LXdlYmtpdC10cmFuc2Zvcm06cm90YXRlKDM2MGRlZyk7dHJhbnNmb3JtOnJvdGF0ZSgzNjBkZWcpIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHBhdGggZmlsbD0iIzU3NWU3NSIgZD0iTTM2OCAxNjhoLTk2di02NGgxNzZhMjQuMDI3IDI0LjAyNyAwIDAgMCAyNC0yNFYxNmgtMzJ2NTZIMjY0YTI0LjAyNyAyNC4wMjcgMCAwIDAtMjQgMjR2NzJoLTk2QTEyOC4xNDUgMTI4LjE0NSAwIDAgMCAxNiAyOTZ2MTAwLjk1M0E5MS4xNSA5MS4xNSAwIDAgMCAxMDcuMDQ3IDQ4OGgxLjhhOTAuODA3IDkwLjgwNyAwIDAgMCA2OS45NTMtMzIuNzZMMjMxLjUgMzkyaDQ4LjYyOGw1Mi42NjYgNjguNDY1QTkxLjA0NiA5MS4wNDYgMCAwIDAgNDk2IDQwNC45NTNWMjk2YTEyOC4xNDUgMTI4LjE0NSAwIDAgMC0xMjgtMTI4em05NiAyMzYuOTUzYTU5LjA0NyA1OS4wNDcgMCAwIDEtMTA1Ljg0OSAzNkwyOTUuODc4IDM2MGgtNzkuMzcybC02Mi4yOTQgNzQuNzU0QTU4Ljg5MyA1OC44OTMgMCAwIDEgMTA4Ljg1IDQ1NmgtMS44QTU5LjExMyA1OS4xMTMgMCAwIDEgNDggMzk2Ljk1M1YyOTZhOTYuMTA4IDk2LjEwOCAwIDAgMSA5Ni05NmgyMjRhOTYuMTA4IDk2LjEwOCAwIDAgMSA5NiA5NnoiLz48cGF0aCBmaWxsPSIjNTc1ZTc1IiBkPSJNMzYwIDI0OGgzMnYzMmgtMzJ6bTAgODBoMzJ2MzJoLTMyem0tNDAtNDBoMzJ2MzJoLTMyem04MCAwaDMydjMyaC0zMnptLTI0OC00MGgtMzJ2NDBIODB2MzJoNDB2NDBoMzJ2LTQwaDQwdi0zMmgtNDB2LTQweiIvPjwvc3ZnPg==");
/***/ }),
/***/ "./src/addons/addons/gamepad/_runtime_entry.js":
/*!*****************************************************!*\
!*** ./src/addons/addons/gamepad/_runtime_entry.js ***!
\*****************************************************/
/*! exports provided: resources */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/gamepad/userscript.js");
/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./style.css */ "./node_modules/css-loader/index.js!./src/addons/addons/gamepad/style.css");
/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _css_loader_gamepadlib_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! css-loader!./gamepadlib.css */ "./node_modules/css-loader/index.js!./src/addons/addons/gamepad/gamepadlib.css");
/* harmony import */ var _css_loader_gamepadlib_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_css_loader_gamepadlib_css__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _url_loader_active_png__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! url-loader!./active.png */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/active.png");
/* harmony import */ var _url_loader_close_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! url-loader!./close.svg */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/close.svg");
/* harmony import */ var _url_loader_cursor_png__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! url-loader!./cursor.png */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/cursor.png");
/* harmony import */ var _url_loader_dot_svg__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! url-loader!./dot.svg */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/dot.svg");
/* harmony import */ var _url_loader_gamepad_svg__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! url-loader!./gamepad.svg */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/gamepad/gamepad.svg");
/* generated by pull.js */
const resources = {
"userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
"style.css": _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default.a,
"gamepadlib.css": _css_loader_gamepadlib_css__WEBPACK_IMPORTED_MODULE_2___default.a,
"active.png": _url_loader_active_png__WEBPACK_IMPORTED_MODULE_3__["default"],
"close.svg": _url_loader_close_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
"cursor.png": _url_loader_cursor_png__WEBPACK_IMPORTED_MODULE_5__["default"],
"dot.svg": _url_loader_dot_svg__WEBPACK_IMPORTED_MODULE_6__["default"],
"gamepad.svg": _url_loader_gamepad_svg__WEBPACK_IMPORTED_MODULE_7__["default"]
};
/***/ }),
/***/ "./src/addons/addons/gamepad/dot.svg":
/*!*******************************************!*\
!*** ./src/addons/addons/gamepad/dot.svg ***!
\*******************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__.p + "static/assets/88a77444f0bb453209bf1c62becbd37c.svg";
/***/ }),
/***/ "./src/addons/addons/gamepad/gamepadlib.js":
/*!*************************************************!*\
!*** ./src/addons/addons/gamepad/gamepadlib.js ***!
\*************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _event_target_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../event-target.js */ "./src/addons/event-target.js");
/* inserted by pull.js */
let console = window.console;
/*
Mapping types:
type: "key" maps a button to a keyboard key
All key names will be interpreted as a KeyboardEvent.key value (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)
high: "KeyName" is the name of the key to dispatch when a button reads a HIGH value
low: "KeyName" is the name of the key to dispatch when a button reads a LOW value
deadZone: 0.5 controls the minimum value necessary to be read in either + or - to trigger either high or low
The high/low distinction is necessary for axes. Buttons will only use high
type: "mousedown" maps a button to control whether the mouse is down or not
deadZone: 0.5 controls the minimum value to trigger a mousedown
button: 0, 1, 2, etc. controls which button to press
type: "virtual_cursor" maps a button to control the "virtual cursor"
deadZone: 0.5 again controls the minimum value to trigger a movement
sensitivity: 10 controls the speed
high: "+y"/"-y"/"+x"/"-x" defines what happens when an axis reads high
low: "+y"/"-y"/"+x"/"-x" defines what happens when an axis reads low
+y increases y, -y decreases y, +x increases x, -x decreases x.
*/
const defaultAxesMappings = {
arrows: [{
type: "key",
high: "ArrowRight",
low: "ArrowLeft",
deadZone: 0.5
}, {
type: "key",
high: "ArrowDown",
low: "ArrowUp",
deadZone: 0.5
}],
wasd: [{
type: "key",
high: "d",
low: "a",
deadZone: 0.5
}, {
type: "key",
high: "s",
low: "w",
deadZone: 0.5
}],
cursor: [{
type: "virtual_cursor",
high: "+x",
low: "-x",
sensitivity: 0.6,
deadZone: 0.2
}, {
type: "virtual_cursor",
high: "-y",
low: "+y",
sensitivity: 0.6,
deadZone: 0.2
}]
};
const emptyMapping = () => ({
type: "key",
high: null,
low: null
});
const transformAndCopyMapping = mapping => {
if (typeof mapping !== "object" || !mapping) {
console.warn("invalid mapping", mapping);
return emptyMapping();
}
const copy = Object.assign({}, mapping);
if (copy.type === "key") {
if (typeof copy.deadZone === "undefined") {
copy.deadZone = 0.5;
}
if (typeof copy.high === "undefined") {
copy.high = "";
}
if (typeof copy.low === "undefined") {
copy.low = "";
}
} else if (copy.type === "mousedown") {
if (typeof copy.deadZone === "undefined") {
copy.deadZone = 0.5;
}
if (typeof copy.button === "undefined") {
copy.button = 0;
}
} else if (copy.type === "virtual_cursor") {
if (typeof copy.high === "undefined") {
copy.high = "";
}
if (typeof copy.low === "undefined") {
copy.low = "";
}
if (typeof copy.sensitivity === "undefined") {
copy.sensitivity = 10;
}
if (typeof copy.deadZone === "undefined") {
copy.deadZone = 0.5;
}
} else {
console.warn("unknown mapping type", copy.type);
return emptyMapping();
}
return copy;
};
const prepareMappingForExport = mapping => Object.assign({}, mapping);
const prepareAxisMappingForExport = prepareMappingForExport;
const prepareButtonMappingForExport = mapping => {
const copy = prepareMappingForExport(mapping);
delete copy.deadZone;
delete copy.low;
return copy;
};
const padWithEmptyMappings = (array, length) => {
// Keep adding empty mappings until the list is full
while (array.length < length) {
array.push(emptyMapping());
} // In case the input array is longer than the desired length
array.length = length;
return array;
};
const createEmptyMappingList = length => padWithEmptyMappings([], length);
const getMovementConfiguration = usedKeys => ({
usesArrows: usedKeys.has("ArrowUp") || usedKeys.has("ArrowDown") || usedKeys.has("ArrowRight") || usedKeys.has("ArrowLeft"),
usesWASD: usedKeys.has("w") && usedKeys.has("s") || usedKeys.has("a") && usedKeys.has("d")
});
const getGamepadId = gamepad => "".concat(gamepad.id, " (").concat(gamepad.index, ")");
class GamepadData {
/**
* @param {Gamepad} gamepad Source Gamepad
* @param {GamepadLib} gamepadLib Parent GamepadLib
*/
constructor(gamepad, gamepadLib) {
this.gamepad = gamepad;
this.gamepadLib = gamepadLib;
this.resetMappings();
}
resetMappings() {
this.hints = this.gamepadLib.getHints();
this.buttonMappings = this.getDefaultButtonMappings().map(transformAndCopyMapping);
this.axesMappings = this.getDefaultAxisMappings().map(transformAndCopyMapping);
}
clearMappings() {
this.buttonMappings = createEmptyMappingList(this.gamepad.buttons.length);
this.axesMappings = createEmptyMappingList(this.gamepad.axes.length);
}
getDefaultButtonMappings() {
let buttons;
if (this.hints.importedSettings) {
buttons = this.hints.importedSettings.buttons;
} else {
const usedKeys = this.hints.usedKeys;
const alreadyUsedKeys = new Set();
const {
usesArrows,
usesWASD
} = getMovementConfiguration(usedKeys);
if (usesWASD) {
alreadyUsedKeys.add("w");
alreadyUsedKeys.add("a");
alreadyUsedKeys.add("s");
alreadyUsedKeys.add("d");
}
const possiblePauseKeys = [// Restart keys, pause keys, other potentially dangerous keys
"p", "q", "r"];
const possibleActionKeys = [" ", "Enter", "e", "f", "z", "x", "c", ...Array.from(usedKeys).filter(i => i.length === 1 && !possiblePauseKeys.includes(i))];
const findKey = keys => {
for (const key of keys) {
if (usedKeys.has(key) && !alreadyUsedKeys.has(key)) {
alreadyUsedKeys.add(key);
return key;
}
}
return null;
};
const getPrimaryAction = () => {
if (usesArrows && usedKeys.has("ArrowUp")) {
return "ArrowUp";
}
if (usesWASD && usedKeys.has("w")) {
return "w";
}
return findKey(possibleActionKeys);
};
const getSecondaryAction = () => findKey(possibleActionKeys);
const getPauseKey = () => findKey(possiblePauseKeys);
const getUp = () => {
if (usesArrows || !usesWASD) return "ArrowUp";
return "w";
};
const getDown = () => {
if (usesArrows || !usesWASD) return "ArrowDown";
return "s";
};
const getRight = () => {
if (usesArrows || !usesWASD) return "ArrowRight";
return "d";
};
const getLeft = () => {
if (usesArrows || !usesWASD) return "ArrowLeft";
return "a";
};
const action1 = getPrimaryAction();
let action2 = getSecondaryAction();
let action3 = getSecondaryAction();
let action4 = getSecondaryAction(); // When only 1 or 2 action keys are detected, bind the other buttons to the same things.
if (action1 && !action2 && !action3 && !action4) {
action2 = action1;
action3 = action1;
action4 = action1;
}
if (action1 && action2 && !action3 && !action4) {
action3 = action1;
action4 = action2;
} // Set indices "manually" because we don't evaluate them in order.
buttons = [];
buttons[0] = {
/*
Xbox: A
SNES-like: B
*/
type: "key",
high: action1
};
buttons[1] = {
/*
Xbox: B
SNES-like: A
*/
type: "key",
high: action2
};
buttons[2] = {
/*
Xbox: X
SNES-like: Y
*/
type: "key",
high: action3
};
buttons[3] = {
/*
Xbox: Y
SNES-like: X
*/
type: "key",
high: action4
};
buttons[4] = {
/*
Xbox: LB
SNES-like: Left trigger
*/
type: "mousedown"
};
buttons[5] = {
/*
Xbox: RB
*/
type: "mousedown"
};
buttons[6] = {
/*
Xbox: LT
*/
type: "mousedown"
};
buttons[7] = {
/*
Xbox: RT
SNES-like: Right trigger
*/
type: "mousedown"
};
buttons[9] = {
/*
Xbox: Menu
SNES-like: Start
*/
type: "key",
high: getPauseKey()
};
buttons[8] = {
/*
Xbox: Change view
SNES-like: Select
*/
type: "key",
high: getPauseKey()
}; // Xbox: Left analog press
buttons[10] = emptyMapping(); // Xbox: Right analog press
buttons[11] = emptyMapping();
buttons[12] = {
/*
Xbox: D-pad up
*/
type: "key",
high: getUp()
};
buttons[13] = {
/*
Xbox: D-pad down
*/
type: "key",
high: getDown()
};
buttons[14] = {
/*
Xbox: D-pad left
*/
type: "key",
high: getLeft()
};
buttons[15] = {
/*
Xbox: D-pad right
*/
type: "key",
high: getRight()
};
}
return padWithEmptyMappings(buttons, this.gamepad.buttons.length);
}
getDefaultAxisMappings() {
let axes = [];
if (this.hints.importedSettings) {
axes = this.hints.importedSettings.axes;
} else {
// Only return default axis mappings when there are 4 axes, like an xbox controller
// If there isn't exactly 4, we can't really predict what the axes mean
// Some controllers map the dpad to *both* buttons and axes at the same time, which would cause conflicts.
if (this.gamepad.axes.length === 4) {
const usedKeys = this.hints.usedKeys;
const {
usesArrows,
usesWASD
} = getMovementConfiguration(usedKeys);
if (usesWASD) {
axes.push(defaultAxesMappings.wasd[0]);
axes.push(defaultAxesMappings.wasd[1]);
} else if (usesArrows) {
axes.push(defaultAxesMappings.arrows[0]);
axes.push(defaultAxesMappings.arrows[1]);
} else {
axes.push(defaultAxesMappings.cursor[0]);
axes.push(defaultAxesMappings.cursor[1]);
}
axes.push(defaultAxesMappings.cursor[0]);
axes.push(defaultAxesMappings.cursor[1]);
}
}
return padWithEmptyMappings(axes, this.gamepad.axes.length);
}
}
const defaultHints = () => ({
usedKeys: new Set(),
importedSettings: null,
generated: false
});
class GamepadLib extends _event_target_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor() {
super();
/** @type {Map<string, GamepadData>} */
this.gamepads = new Map();
this.handleConnect = this.handleConnect.bind(this);
this.handleDisconnect = this.handleDisconnect.bind(this);
this.update = this.update.bind(this);
this.animationFrame = null;
this.currentTime = null;
this.deltaTime = 0;
this.virtualCursor = {
x: 0,
y: 0,
maxX: Infinity,
minX: -Infinity,
maxY: Infinity,
minY: -Infinity,
modified: false
};
this._editor = null;
this.connectCallbacks = [];
this.keysPressedThisFrame = new Set();
this.oldKeysPressed = new Set();
this.mouseButtonsPressedThisFrame = new Set();
this.oldMouseDown = new Set();
this.addEventHandlers();
}
addEventHandlers() {
window.addEventListener("gamepadconnected", this.handleConnect);
window.addEventListener("gamepaddisconnected", this.handleDisconnect);
}
removeEventHandlers() {
window.removeEventListener("gamepadconnected", this.handleConnect);
window.removeEventListener("gamepaddisconnected", this.handleDisconnect);
}
gamepadConnected() {
if (this.gamepads.size > 0) {
return Promise.resolve();
}
return new Promise(resolve => {
this.connectCallbacks.push(resolve);
});
}
getHints() {
return Object.assign(defaultHints(), this.getUserHints());
}
getUserHints() {
// to be overridden by users
return {};
}
resetControls() {
for (const gamepad of this.gamepads.values()) {
gamepad.resetMappings();
}
}
clearControls() {
for (const gamepad of this.gamepads.values()) {
gamepad.clearMappings();
}
}
handleConnect(e) {
for (const callback of this.connectCallbacks) {
callback();
}
this.connectCallbacks = [];
const gamepad = e.gamepad;
const id = getGamepadId(gamepad);
console.log("connected", gamepad);
const gamepadData = new GamepadData(gamepad, this);
this.gamepads.set(id, gamepadData);
if (this.animationFrame === null) {
this.animationFrame = requestAnimationFrame(this.update);
}
this.dispatchEvent(new CustomEvent("gamepadconnected", {
detail: gamepadData
}));
}
handleDisconnect(e) {
const gamepad = e.gamepad;
const id = getGamepadId(gamepad);
console.log("disconnected", gamepad);
const gamepadData = this.gamepads.get(id);
this.gamepads.delete(id);
this.dispatchEvent(new CustomEvent("gamepaddisconnected", {
detail: gamepadData
}));
if (this.gamepads.size === 0) {
cancelAnimationFrame(this.animationFrame);
this.animationFrame = null;
this.currentTime = null;
}
}
dispatchKey(key, pressed) {
if (pressed) {
this.dispatchEvent(new CustomEvent("keydown", {
detail: key
}));
} else {
this.dispatchEvent(new CustomEvent("keyup", {
detail: key
}));
}
}
dispatchMouse(button, down) {
if (down) {
this.dispatchEvent(new CustomEvent("mousedown", {
detail: button
}));
} else {
this.dispatchEvent(new CustomEvent("mouseup", {
detail: button
}));
}
}
dispatchMouseMove(x, y) {
this.dispatchEvent(new CustomEvent("mousemove", {
detail: {
x,
y
}
}));
}
updateButton(value, mapping) {
if (mapping.type === "key") {
if (value >= mapping.deadZone) {
if (mapping.high) {
this.keysPressedThisFrame.add(mapping.high);
}
} else if (value <= -mapping.deadZone) {
if (mapping.low) {
this.keysPressedThisFrame.add(mapping.low);
}
}
} else if (mapping.type === "mousedown") {
const isDown = Math.abs(value) >= mapping.deadZone;
if (isDown) {
this.mouseButtonsPressedThisFrame.add(mapping.button);
}
} else if (mapping.type === "virtual_cursor") {
const deadZone = mapping.deadZone;
let action;
if (value >= deadZone) action = mapping.high;
if (value <= -deadZone) action = mapping.low;
if (action) {
// an axis value just beyond the deadzone should have a multiplier near 0, a high value should have a multiplier of 1
const multiplier = (Math.abs(value) - deadZone) / (1 - deadZone);
const speed = multiplier * multiplier * mapping.sensitivity * this.deltaTime;
if (action === "+x") {
this.virtualCursor.x += speed;
} else if (action === "-x") {
this.virtualCursor.x -= speed;
} else if (action === "+y") {
this.virtualCursor.y += speed;
} else if (action === "-y") {
this.virtualCursor.y -= speed;
}
this.virtualCursor.modified = true;
}
}
}
update(time) {
this.oldKeysPressed = this.keysPressedThisFrame;
this.oldMouseButtonsPressed = this.mouseButtonsPressedThisFrame;
this.keysPressedThisFrame = new Set();
this.mouseButtonsPressedThisFrame = new Set();
if (this.currentTime === null) {
this.deltaTime = 0; // doesn't matter what this is, it's just the first frame
} else {
this.deltaTime = time - this.currentTime;
}
this.deltaTime = Math.max(Math.min(this.deltaTime, 1000), 0);
this.currentTime = time;
this.animationFrame = requestAnimationFrame(this.update);
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
if (gamepad === null) {
continue;
}
const id = getGamepadId(gamepad);
const data = this.gamepads.get(id);
for (let i = 0; i < gamepad.buttons.length; i++) {
const button = gamepad.buttons[i];
const value = button.value;
const mapping = data.buttonMappings[i];
this.updateButton(value, mapping);
}
for (let i = 0; i < gamepad.axes.length; i++) {
const axis = gamepad.axes[i];
const mapping = data.axesMappings[i];
this.updateButton(axis, mapping);
}
}
if (this._editor) {
this._editor.update(gamepads);
}
for (const key of this.keysPressedThisFrame) {
if (!this.oldKeysPressed.has(key)) {
this.dispatchKey(key, true);
}
}
for (const key of this.oldKeysPressed) {
if (!this.keysPressedThisFrame.has(key)) {
this.dispatchKey(key, false);
}
}
for (const button of this.mouseButtonsPressedThisFrame) {
if (!this.oldMouseButtonsPressed.has(button)) {
this.dispatchMouse(button, true);
}
}
for (const button of this.oldMouseButtonsPressed) {
if (!this.mouseButtonsPressedThisFrame.has(button)) {
this.dispatchMouse(button, false);
}
}
if (this.virtualCursor.modified) {
this.virtualCursor.modified = false;
if (this.virtualCursor.x > this.virtualCursor.maxX) {
this.virtualCursor.x = this.virtualCursor.maxX;
}
if (this.virtualCursor.x < this.virtualCursor.minX) {
this.virtualCursor.x = this.virtualCursor.minX;
}
if (this.virtualCursor.y > this.virtualCursor.maxY) {
this.virtualCursor.y = this.virtualCursor.maxY;
}
if (this.virtualCursor.y < this.virtualCursor.minY) {
this.virtualCursor.y = this.virtualCursor.minY;
}
this.dispatchMouseMove(this.virtualCursor.x, this.virtualCursor.y);
}
}
editor() {
if (!this._editor) {
this._editor = new GamepadEditor(this);
}
return this._editor;
}
}
GamepadLib.browserHasBrokenGamepadAPI = () => {
// Check that the gamepad API is supported at all
if (!navigator.getGamepads) {
return true;
} // Firefox on Linux has a broken gamepad API implementation that results in strange and sometimes unusable mappings
// https://bugzilla.mozilla.org/show_bug.cgi?id=1643358
// https://bugzilla.mozilla.org/show_bug.cgi?id=1643835
if (navigator.userAgent.includes("Firefox") && navigator.userAgent.includes("Linux")) {
return true;
} // Firefox on macOS has other bugs that result in strange and unusable mappings
// eg. https://bugzilla.mozilla.org/show_bug.cgi?id=1434408
if (navigator.userAgent.includes("Firefox") && navigator.userAgent.includes("Mac OS")) {
return true;
}
return false;
};
GamepadLib.setConsole = n => console = n;
const removeAllChildren = el => {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
};
const buttonHtmlId = index => "gamepadlib-button-".concat(index);
const axisHtmlId = n => "gamepadlib-axis-".concat(n);
class GamepadEditor extends _event_target_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor(gamepadLib) {
super();
/** @type {GamepadLib} */
this.gamepadLib = gamepadLib;
this.root = Object.assign(document.createElement("div"), {
className: "gamepadlib-root"
});
this.selector = Object.assign(document.createElement("select"), {
className: "gamepadlib-selector"
});
this.content = Object.assign(document.createElement("div"), {
className: "gamepadlib-content"
});
this.root.appendChild(this.selector);
this.root.appendChild(this.content);
this.onSelectorChange = this.onSelectorChange.bind(this);
this.onGamepadsChange = this.onGamepadsChange.bind(this);
this.selector.addEventListener("change", this.onSelectorChange);
this.gamepadLib.addEventListener("gamepadconnected", this.onGamepadsChange);
this.gamepadLib.addEventListener("gamepaddisconnected", this.onGamepadsChange);
this.buttonIdToElement = new Map();
this.axisIdToElement = new Map();
this.hidden = false; // should be overridden later
this.msg = (id, opts) => id;
}
onSelectorChange() {
this.updateContent();
this.dispatchEvent(new CustomEvent("gamepad-changed"));
}
onGamepadsChange() {
this.updateAllContent();
this.dispatchEvent(new CustomEvent("gamepad-changed"));
}
updateAllContent() {
this.updateDropdown();
this.updateContent();
this.focus();
}
updateDropdown() {
removeAllChildren(this.selector);
const gamepads = Array.from(this.gamepadLib.gamepads.entries());
if (gamepads.length === 0) {
this.selector.hidden = true;
return;
}
this.selector.hidden = false;
for (const [id, _] of gamepads) {
const option = document.createElement("option");
option.textContent = id;
option.value = id;
this.selector.appendChild(option);
}
}
keyToString(key) {
if (key === " ") return this.msg("key-space");
if (key === "ArrowUp") return this.msg("key-up");
if (key === "ArrowDown") return this.msg("key-down");
if (key === "ArrowLeft") return this.msg("key-left");
if (key === "ArrowRight") return this.msg("key-right");
if (key === "Enter") return this.msg("key-enter");
if (key.length === 1) {
return key.toUpperCase();
} // Convert eg. "PageUp" -> "Page Up"
return key.replace(/[a-z]([A-Z])/, n => "".concat(n[0], " ").concat(n[1]));
}
createButtonMapping(mappingList, index, {
property = "high",
allowClick = true
} = {}) {
const input = document.createElement("input");
input.readOnly = true;
input.className = "gamepadlib-keyinput";
input.title = this.msg("keyinput-title");
input.dataset.index = index;
const update = () => {
const mapping = mappingList[index];
input.dataset.empty = false;
if (mapping.type === "key") {
if (mapping[property] === null) {
input.value = this.msg("key-none");
input.dataset.empty = true;
} else {
input.value = this.keyToString(mapping[property]);
}
} else if (mapping.type === "mousedown") {
let value = this.msg("key-click");
if (mapping.button !== 0) {
value += " (".concat(mapping.button, ")");
}
input.value = value;
} else {
// should never happen
input.value = "??? ".concat(mapping.type);
}
};
const changedMapping = () => {
mappingList[index] = transformAndCopyMapping(mappingList[index]);
isAcceptingInput = false;
input.blur();
update();
input.dispatchEvent(new CustomEvent("mapping-changed"));
this.changed();
};
let isAcceptingInput = false;
const handleClick = e => {
e.preventDefault();
if (isAcceptingInput) {
if (allowClick) {
const mapping = mappingList[index];
mapping.type = "mousedown";
mapping.button = e.button;
changedMapping();
} else {
handleBlur();
}
} else {
input.value = "...";
input.dataset.acceptingInput = true;
isAcceptingInput = true;
}
};
const handleKeyEvent = e => {
if (isAcceptingInput) {
e.preventDefault();
const key = e.key; // TW: We allow binding to control and shift
if (["Alt"].includes(key)) {
return;
}
const mapping = mappingList[index];
const KEYS = ["ArrowUp", "ArrowDown", "ArrowRight", "ArrowLeft", "Enter", // TW: We support more keys
// "Backspace",
// "Delete",
"Shift", "CapsLock", "ScrollLock", "Control", // "Escape",
"Insert", "Home", "End", "PageUp", "PageDown"];
if (key.length === 1 || KEYS.includes(key)) {
mapping.type = "key";
mapping[property] = key;
} else if (key !== "Escape") {
mapping.type = "key";
mapping[property] = null;
}
changedMapping();
} else if (e.key === "Enter") {
e.preventDefault();
e.target.click();
}
};
const MODIFIER_KEYS = ["Shift", "Control"];
const handleKeyDown = e => {
if (!MODIFIER_KEYS.includes(e.key)) handleKeyEvent(e);
};
const handleKeyUp = e => {
if (MODIFIER_KEYS.includes(e.key)) handleKeyEvent(e);
};
const handleBlur = () => {
input.dataset.acceptingInput = false;
if (isAcceptingInput) {
isAcceptingInput = false;
update();
}
};
input.addEventListener("contextmenu", e => {
e.preventDefault();
});
input.addEventListener("mouseup", handleClick);
input.addEventListener("keydown", handleKeyDown);
input.addEventListener("keyup", handleKeyUp);
input.addEventListener("blur", handleBlur);
update();
return input;
}
createAxisMapping(mappingList, index) {
const selector = document.createElement("select");
selector.className = "gamepadlib-axis-mapping";
selector.id = axisHtmlId(index);
selector.appendChild(Object.assign(document.createElement("option"), {
textContent: this.msg("axis-none"),
value: "none"
}));
selector.appendChild(Object.assign(document.createElement("option"), {
textContent: this.msg("axis-cursor"),
value: "cursor"
}));
selector.appendChild(Object.assign(document.createElement("option"), {
// doesn't really make sense to translate
textContent: "WASD",
value: "wasd"
}));
selector.appendChild(Object.assign(document.createElement("option"), {
textContent: this.msg("axis-arrows"),
value: "arrows"
}));
selector.appendChild(Object.assign(document.createElement("option"), {
textContent: this.msg("axis-custom"),
value: "custom"
}));
const updateDropdownValue = () => {
if (mappingList[index].type === "key" || mappingList[index].type === "mousedown") {
if (mappingList[index].high === null && mappingList[index].low === null && mappingList[index + 1].high === null && mappingList[index + 1].low === null) {
selector.value = "none";
} else if (mappingList[index].high === defaultAxesMappings.wasd[0].high && mappingList[index].low === defaultAxesMappings.wasd[0].low && mappingList[index + 1].high === defaultAxesMappings.wasd[1].high && mappingList[index + 1].low === defaultAxesMappings.wasd[1].low) {
selector.value = "wasd";
} else if (mappingList[index].high === defaultAxesMappings.arrows[0].high && mappingList[index].low === defaultAxesMappings.arrows[0].low && mappingList[index + 1].high === defaultAxesMappings.arrows[1].high && mappingList[index + 1].low === defaultAxesMappings.arrows[1].low) {
selector.value = "arrows";
} else {
selector.value = "custom";
}
} else if (mappingList[index].type === "virtual_cursor") {
selector.value = "cursor";
} else {
// should never happen
selector.value = "none";
}
};
updateDropdownValue();
const circleOverlay = document.createElement("div");
circleOverlay.className = "gamepadlib-axis-circle-overlay";
const updateOverlay = () => {
removeAllChildren(circleOverlay);
if (mappingList[index].type === "key") {
const buttons = [this.createButtonMapping(mappingList, index + 1, {
property: "low",
allowClick: false
}), this.createButtonMapping(mappingList, index, {
property: "low",
allowClick: false
}), this.createButtonMapping(mappingList, index, {
property: "high",
allowClick: false
}), this.createButtonMapping(mappingList, index + 1, {
property: "high",
allowClick: false
})];
for (const button of buttons) {
button.classList.add("gamepadlib-axis-mapper");
button.addEventListener("mapping-changed", updateDropdownValue);
circleOverlay.appendChild(button);
}
}
};
updateOverlay();
selector.addEventListener("change", () => {
if (selector.value === "custom") {
// If key mappings already exist, leave them as-is
if (mappingList[index].type !== "key") {
mappingList[index] = transformAndCopyMapping(defaultAxesMappings.arrows[0]);
mappingList[index + 1] = transformAndCopyMapping(defaultAxesMappings.arrows[1]);
}
} else if (selector.value === "arrows") {
mappingList[index] = transformAndCopyMapping(defaultAxesMappings.arrows[0]);
mappingList[index + 1] = transformAndCopyMapping(defaultAxesMappings.arrows[1]);
} else if (selector.value === "wasd") {
mappingList[index] = transformAndCopyMapping(defaultAxesMappings.wasd[0]);
mappingList[index + 1] = transformAndCopyMapping(defaultAxesMappings.wasd[1]);
} else if (selector.value === "cursor") {
mappingList[index] = transformAndCopyMapping(defaultAxesMappings.cursor[0]);
mappingList[index + 1] = transformAndCopyMapping(defaultAxesMappings.cursor[1]);
} else {
mappingList[index] = transformAndCopyMapping(emptyMapping());
mappingList[index + 1] = transformAndCopyMapping(emptyMapping());
}
updateOverlay();
this.changed();
});
return {
circleOverlay,
selector
};
}
hasControllerSelected() {
return !!this.selector.value;
}
updateContent() {
removeAllChildren(this.content);
if (this.hidden) {
return;
}
const selectedId = this.selector.value;
if (!selectedId) {
const message = document.createElement("div");
message.textContent = this.msg("no-controllers");
this.content.appendChild(message);
return;
}
const gamepadData = this.gamepadLib.gamepads.get(selectedId);
if (!gamepadData) {
// Users should never be able to see this
const message = document.createElement("div");
message.textContent = "Cannot find controller: ".concat(selectedId);
this.content.appendChild(message);
return;
}
this.buttonIdToElement.clear();
this.axisIdToElement.clear();
const mappingsContainer = document.createElement("div");
mappingsContainer.className = "gamepadlib-content-buttons";
const buttonMappings = gamepadData.buttonMappings;
for (let i = 0; i < buttonMappings.length; i++) {
const container = document.createElement("div");
container.className = "gamepadlib-mapping";
container.dataset.id = i;
const label = document.createElement("label");
label.className = "gamepadlib-mapping-label";
label.textContent = this.msg("button-n", {
n: i
});
const id = buttonHtmlId(i);
label.htmlFor = id;
const options = document.createElement("div");
options.className = "gamepadlib-mapping-options";
const mappingInput = this.createButtonMapping(buttonMappings, i);
mappingInput.id = id;
options.appendChild(mappingInput);
container.appendChild(label);
container.appendChild(options);
mappingsContainer.appendChild(container);
this.buttonIdToElement.set(i, container);
}
const axesContainer = document.createElement("div");
axesContainer.className = "gamepadlib-content-axes";
const axesMappings = gamepadData.axesMappings;
for (let i = 0; i < axesMappings.length; i += 2) {
const container = document.createElement("div");
container.className = "gamepadlib-axis";
const label = document.createElement("label");
label.textContent = this.msg("axes-a-b", {
a: i,
b: i + 1
});
label.htmlFor = axisHtmlId(i);
const circle = document.createElement("div");
circle.className = "gamepadlib-axis-circle";
const {
circleOverlay,
selector
} = this.createAxisMapping(axesMappings, i);
circle.appendChild(circleOverlay);
const dot = document.createElement("div");
dot.className = "gamepadlib-axis-dot";
circle.appendChild(dot);
container.appendChild(label);
container.appendChild(circle);
container.appendChild(selector);
axesContainer.appendChild(container);
this.axisIdToElement.set(i, dot);
}
this.content.appendChild(mappingsContainer);
this.content.appendChild(axesContainer);
}
update(gamepads) {
if (this.hidden) {
return;
}
const selectedId = this.selector.value;
if (!selectedId) {
return;
}
const gamepad = Array.from(gamepads).find(i => i && getGamepadId(i) === this.selector.value);
if (!gamepad) {
return;
}
for (let i = 0; i < gamepad.buttons.length; i++) {
const element = this.buttonIdToElement.get(i);
if (element) {
const button = gamepad.buttons[i];
const value = button.value.toString();
if (value !== element.dataset.value) {
element.dataset.value = value;
}
}
}
for (let i = 0; i < gamepad.axes.length; i += 2) {
const element = this.axisIdToElement.get(i);
if (element) {
const x = gamepad.axes[i];
const y = gamepad.axes[i + 1] || 0;
const size = 150 / 2;
element.style.transform = "translate(-50%, -50%) translate(".concat(x * size, "px, ").concat(y * size, "px)");
}
}
}
export() {
const selectedId = this.selector.value;
if (!selectedId) {
return null;
}
const gamepadData = this.gamepadLib.gamepads.get(selectedId);
if (!gamepadData) {
return null;
}
return {
axes: gamepadData.axesMappings.map(prepareAxisMappingForExport),
buttons: gamepadData.buttonMappings.map(prepareButtonMappingForExport)
};
}
changed() {
this.dispatchEvent(new CustomEvent("mapping-changed"));
}
hide() {
this.hidden = true;
this.updateContent();
}
focus() {
if (this.selector.value) {
this.selector.focus();
}
}
generateEditor() {
this.hidden = false;
this.updateAllContent();
return this.root;
}
}
/* harmony default export */ __webpack_exports__["default"] = (GamepadLib);
/***/ }),
/***/ "./src/addons/addons/gamepad/userscript.js":
/*!*************************************************!*\
!*** ./src/addons/addons/gamepad/userscript.js ***!
\*************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _gamepadlib_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./gamepadlib.js */ "./src/addons/addons/gamepad/gamepadlib.js");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/* harmony default export */ __webpack_exports__["default"] = (async function ({
addon,
console,
msg
}) {
const vm = addon.tab.traps.vm; // Wait for the project to finish loading. Renderer and scripts will not be fully available until this happens.
await new Promise(resolve => {
if (vm.editingTarget) return resolve();
vm.runtime.once("PROJECT_LOADED", resolve);
});
const vmStarted = () => vm.runtime._steppingInterval !== null;
const scratchKeyToKey = key => {
switch (key) {
case "right arrow":
return "ArrowRight";
case "up arrow":
return "ArrowUp";
case "left arrow":
return "ArrowLeft";
case "down arrow":
return "ArrowDown";
case "enter":
return "Enter";
case "space":
return " ";
}
return key.toLowerCase().charAt(0);
};
const getKeysUsedByProject = () => {
const allBlocks = [vm.runtime.getTargetForStage(), ...vm.runtime.targets].filter(i => i.isOriginal).map(i => i.blocks);
const result = new Set();
for (const blocks of allBlocks) {
for (const block of Object.values(blocks._blocks)) {
if (block.opcode === "event_whenkeypressed" || block.opcode === "sensing_keyoptions") {
// For blocks like "key (my variable) pressed?", the sensing_keyoptions still exists but has a null parent.
if (block.opcode === "sensing_keyoptions" && !block.parent) {
continue;
}
const key = block.fields.KEY_OPTION.value;
result.add(scratchKeyToKey(key));
}
}
}
return result;
};
const GAMEPAD_CONFIG_MAGIC = " // _gamepad_";
const findOptionsComment = () => {
const target = vm.runtime.getTargetForStage();
const comments = target.comments;
for (const comment of Object.values(comments)) {
if (comment.text.includes(GAMEPAD_CONFIG_MAGIC)) {
return comment;
}
}
return null;
};
const parseOptionsComment = () => {
const comment = findOptionsComment();
if (!comment) {
return null;
}
const lineWithMagic = comment.text.split("\n").find(i => i.endsWith(GAMEPAD_CONFIG_MAGIC));
if (!lineWithMagic) {
console.warn("Gamepad comment does not contain valid line");
return null;
}
const jsonText = lineWithMagic.substr(0, lineWithMagic.length - GAMEPAD_CONFIG_MAGIC.length);
let parsed;
try {
parsed = JSON.parse(jsonText);
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.buttons) || !Array.isArray(parsed.axes)) {
throw new Error("Invalid data");
}
} catch (e) {
console.warn("Gamepad comment has invalid JSON", e);
return null;
}
return parsed;
};
_gamepadlib_js__WEBPACK_IMPORTED_MODULE_0__["default"].setConsole(console);
const gamepad = new _gamepadlib_js__WEBPACK_IMPORTED_MODULE_0__["default"]();
gamepad.getUserHints = () => {
const parsedOptions = parseOptionsComment();
if (parsedOptions) {
return {
importedSettings: parsedOptions
};
}
return {
usedKeys: getKeysUsedByProject()
};
};
vm.runtime.on("PROJECT_LOADED", () => {
gamepad.resetControls();
});
if (addon.settings.get("hide")) {
await new Promise(resolve => {
const end = () => {
addon.settings.removeEventListener("change", listener);
resolve();
};
const listener = () => {
if (!addon.settings.get("hide")) {
end();
}
};
gamepad.gamepadConnected().then(end);
addon.settings.addEventListener("change", listener);
});
}
const renderer = vm.runtime.renderer;
const width = renderer._xRight - renderer._xLeft;
const height = renderer._yTop - renderer._yBottom;
const canvas = renderer.canvas;
const container = document.createElement("div");
container.className = "sa-gamepad-container";
addon.tab.displayNoneWhileDisabled(container, {
display: "flex"
});
const buttonContainer = document.createElement("span");
buttonContainer.className = addon.tab.scratchClass("button_outlined-button", "stage-header_stage-button");
const buttonContent = document.createElement("div");
buttonContent.className = addon.tab.scratchClass("button_content");
const buttonImage = document.createElement("img");
buttonImage.className = addon.tab.scratchClass("stage-header_stage-button-icon");
buttonImage.draggable = false;
buttonImage.src = addon.self.getResource("/gamepad.svg")
/* rewritten by pull.js */
;
buttonContent.appendChild(buttonImage);
buttonContainer.appendChild(buttonContent);
container.appendChild(buttonContainer);
let editor;
let shouldStoreSettingsInProject = false;
const didChangeProject = () => {
vm.runtime.emitProjectChanged();
if (vm.editingTarget === vm.runtime.getTargetForStage()) {
vm.emitWorkspaceUpdate();
}
};
const storeMappings = () => {
const exported = editor.export();
if (!exported) {
console.warn("Could not export gamepad settings");
return;
}
const text = "".concat(msg("config-header"), "\n").concat(JSON.stringify(exported)).concat(GAMEPAD_CONFIG_MAGIC);
const existingComment = findOptionsComment();
if (existingComment) {
existingComment.text = text;
} else {
const target = vm.runtime.getTargetForStage();
target.createComment( // comment ID, just has to be a random string
Math.random() + "", // block ID
null, // text
text, // x, y, width, height
50, 50, 350, 150, // minimized
false);
}
didChangeProject();
};
const removeStoredMappings = () => {
const comment = findOptionsComment();
if (comment) {
const target = vm.runtime.getTargetForStage();
delete target.comments[comment.id];
didChangeProject();
}
};
const handleGamepadMappingChanged = () => {
if (shouldStoreSettingsInProject) {
storeMappings();
}
};
const handleStoreSettingsCheckboxChanged = e => {
shouldStoreSettingsInProject = !!e.target.checked;
if (shouldStoreSettingsInProject) {
storeMappings();
} else {
removeStoredMappings();
}
};
const handleEditorControllerChanged = () => {
document.body.classList.toggle("sa-gamepad-has-controller", editor.hasControllerSelected());
handleGamepadMappingChanged();
};
buttonContainer.addEventListener("click", () => {
if (!editor) {
editor = gamepad.editor();
editor.msg = msg;
editor.addEventListener("mapping-changed", handleGamepadMappingChanged);
editor.addEventListener("gamepad-changed", handleEditorControllerChanged);
}
const editorEl = editor.generateEditor();
handleEditorControllerChanged();
const {
backdrop,
container,
content,
closeButton,
remove
} = addon.tab.createModal(msg("settings"), {
isOpen: true,
useEditorClasses: true
});
const handleKeyDown = e => {
if (e.key === "Escape" && !e.target.closest("[data-accepting-input]")) {
remove();
}
};
backdrop.addEventListener("click", remove);
window.addEventListener("keydown", handleKeyDown);
addon.self.addEventListener("disabled", remove);
backdrop.classList.add("sa-gamepad-popup-outer");
container.classList.add("sa-gamepad-popup");
closeButton.tabIndex = "0";
closeButton.setAttribute("role", "button");
closeButton.addEventListener("click", remove);
closeButton.addEventListener("keydown", e => {
if (e.key === "Enter" || e.key === " ") {
remove();
}
});
content.classList.add("sa-gamepad-popup-content");
if (_gamepadlib_js__WEBPACK_IMPORTED_MODULE_0__["default"].browserHasBrokenGamepadAPI()) {
const warning = document.createElement("div");
warning.textContent = msg("browser-support");
warning.className = "sa-gamepad-browser-support-warning";
content.appendChild(warning);
}
content.appendChild(editorEl);
const extraOptionsContainer = document.createElement("div");
extraOptionsContainer.className = "sa-gamepad-extra-options";
content.appendChild(extraOptionsContainer);
const mappingsWereResetOrCleared = () => {
editor.updateAllContent();
storeSettingsCheckbox.checked = false;
shouldStoreSettingsInProject = false;
};
const resetButton = document.createElement("button");
resetButton.className = "sa-gamepad-reset-button";
resetButton.textContent = msg("reset");
resetButton.addEventListener("click", () => {
gamepad.resetControls();
mappingsWereResetOrCleared();
});
extraOptionsContainer.appendChild(resetButton);
const clearButton = document.createElement("button");
clearButton.className = "sa-gamepad-reset-button";
clearButton.textContent = msg("clear");
clearButton.addEventListener("click", () => {
gamepad.clearControls();
mappingsWereResetOrCleared();
});
extraOptionsContainer.appendChild(clearButton);
const storeSettingsLabel = document.createElement("label");
storeSettingsLabel.className = "sa-gamepad-store-settings";
storeSettingsLabel.textContent = msg("store-in-project");
const storeSettingsCheckbox = document.createElement("input");
storeSettingsCheckbox.type = "checkbox";
storeSettingsCheckbox.checked = shouldStoreSettingsInProject;
storeSettingsCheckbox.addEventListener("change", handleStoreSettingsCheckboxChanged);
storeSettingsLabel.prepend(storeSettingsCheckbox);
extraOptionsContainer.appendChild(storeSettingsLabel);
editor.focus();
});
if (addon.tab.redux.state && addon.tab.redux.state.scratchGui.stageSize.stageSize === "small") {
document.body.classList.add("sa-gamepad-small");
}
document.addEventListener("click", e => {
if (e.target.closest("[class*='stage-header_stage-button-first']:not(.sa-hide-stage-button)")) {
document.body.classList.add("sa-gamepad-small");
} else if (e.target.closest("[class*='stage-header_stage-button-last']") || e.target.closest(".sa-hide-stage-button")) {
document.body.classList.remove("sa-gamepad-small");
}
}, {
capture: true
});
const virtualCursorElement = document.createElement("img");
virtualCursorElement.hidden = true;
virtualCursorElement.className = "sa-gamepad-cursor";
virtualCursorElement.src = addon.self.getResource("/cursor.png")
/* rewritten by pull.js */
;
addon.self.addEventListener("disabled", () => {
virtualCursorElement.hidden = true;
});
let hideCursorTimeout;
const hideRealCursor = () => {
document.body.classList.add("sa-gamepad-hide-cursor");
};
const showRealCursor = () => {
document.body.classList.remove("sa-gamepad-hide-cursor");
};
const virtualCursorSetVisible = visible => {
virtualCursorElement.hidden = !visible;
clearTimeout(hideCursorTimeout);
if (visible) {
hideRealCursor();
hideCursorTimeout = setTimeout(virtualCursorHide, 8000);
}
};
const virtualCursorHide = () => {
virtualCursorSetVisible(false);
};
const virtualCursorSetDown = down => {
virtualCursorSetVisible(true);
virtualCursorElement.classList.toggle("sa-gamepad-cursor-down", down);
};
const virtualCursorSetPosition = (x, y) => {
virtualCursorSetVisible(true);
const CURSOR_SIZE = 6;
const stageX = width / 2 + x - CURSOR_SIZE / 2;
const stageY = height / 2 - y - CURSOR_SIZE / 2;
virtualCursorElement.style.transform = "translate(".concat(stageX, "px, ").concat(stageY, "px)");
};
document.addEventListener("mousemove", () => {
virtualCursorSetVisible(false);
showRealCursor();
});
let getCanvasSize; // Support modern ResizeObserver and slow getBoundingClientRect version for improved browser support (matters for TurboWarp)
if (window.ResizeObserver) {
let canvasWidth = width;
let canvasHeight = height;
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
canvasWidth = entry.contentRect.width;
canvasHeight = entry.contentRect.height;
}
});
resizeObserver.observe(canvas);
getCanvasSize = () => [canvasWidth, canvasHeight];
} else {
getCanvasSize = () => {
const rect = canvas.getBoundingClientRect();
return [rect.width, rect.height];
};
} // Both in Scratch space
let virtualX = 0;
let virtualY = 0;
const postMouseData = data => {
if (addon.self.disabled || !vmStarted()) return;
const [rectWidth, rectHeight] = getCanvasSize();
vm.postIOData("mouse", _objectSpread(_objectSpread({}, data), {}, {
canvasWidth: rectWidth,
canvasHeight: rectHeight,
x: (virtualX + width / 2) * (rectWidth / width),
y: (height / 2 - virtualY) * (rectHeight / height)
}));
};
const postKeyboardData = (key, isDown) => {
if (addon.self.disabled || !vmStarted()) return;
vm.postIOData("keyboard", {
key,
isDown
});
};
const handleGamepadButtonDown = e => postKeyboardData(e.detail, true);
const handleGamepadButtonUp = e => postKeyboardData(e.detail, false);
const handleGamepadMouseDown = e => {
virtualCursorSetDown(true);
postMouseData({
isDown: true,
button: e.detail
});
};
const handleGamepadMouseUp = e => {
virtualCursorSetDown(false);
postMouseData({
isDown: false,
button: e.detail
});
};
const handleGamepadMouseMove = e => {
virtualX = e.detail.x;
virtualY = e.detail.y;
virtualCursorSetPosition(virtualX, virtualY);
postMouseData({});
};
gamepad.virtualCursor.maxX = renderer._xRight;
gamepad.virtualCursor.minX = renderer._xLeft;
gamepad.virtualCursor.maxY = renderer._yTop;
gamepad.virtualCursor.minY = renderer._yBottom;
gamepad.addEventListener("keydown", handleGamepadButtonDown);
gamepad.addEventListener("keyup", handleGamepadButtonUp);
gamepad.addEventListener("mousedown", handleGamepadMouseDown);
gamepad.addEventListener("mouseup", handleGamepadMouseUp);
gamepad.addEventListener("mousemove", handleGamepadMouseMove);
while (true) {
const target = await addon.tab.waitForElement('[class^="stage-header_embed-buttons_"], [class^="stage-header_stage-size-row"], [class^="stage-header_stage-menu-wrapper"] > [class^="button_outlined-button"]', {
markAsSeen: true,
reduxEvents: ["scratch-gui/mode/SET_PLAYER", "scratch-gui/mode/SET_FULL_SCREEN", "fontsLoaded/SET_FONTS_LOADED", "scratch-gui/locales/SELECT_LOCALE"]
});
container.dataset.editorMode = addon.tab.editorMode;
if (target.className.includes("stage-size-row")) {
addon.tab.appendToSharedSpace({
space: "stageHeader",
element: container,
order: 1
});
} else {
addon.tab.appendToSharedSpace({
space: "fullscreenStageHeader",
element: container,
order: 0
});
}
const monitorListScaler = document.querySelector("[class^='monitor-list_monitor-list-scaler']");
monitorListScaler.appendChild(virtualCursorElement);
}
});
/***/ })
}]);
//# sourceMappingURL=addon-entry-gamepad.js.map