(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} */ 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