UNPKG

virtual-gamepad-lib

Version:

Emulate and display virtual and real gamepads on the web

3 lines (2 loc) 14.1 kB
"use strict";var L=Object.defineProperty;var T=(G,e,a)=>e in G?L(G,e,{enumerable:!0,configurable:!0,writable:!0,value:a}):G[e]=a;var f=(G,e,a)=>T(G,typeof e!="symbol"?e+"":e,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("./enums.js"),x=require("./utilities.js"),y=18,P=4,w=class w{constructor(e){f(this,"getNativeGamepads");f(this,"buttonPressThreshold",.1);f(this,"realGpadToPatchedIndexMap",[]);f(this,"patchedGpadToRealIndexMap",[]);f(this,"emulatedGamepads",[]);f(this,"emulatedGamepadsMetadata",[]);f(this,"undoEventPatch",()=>{});f(this,"AddDisplayButtonEventListeners",this.AddButtonTouchEventListeners);f(this,"AddDisplayJoystickEventListeners",this.AddJoystickTouchEventListeners);f(this,"ClearDisplayButtonEventListeners",this.ClearButtonTouchEventListeners);f(this,"ClearDisplayJoystickEventListeners",this.ClearJoystickTouchEventListeners);if(this.buttonPressThreshold=e||this.buttonPressThreshold,w.instanceRunning)throw new Error("Only one GamepadEmulator instance may exist at a time!");w.instanceRunning=!0,this.undoEventPatch=this.monkeyPatchGamepadEvents(),this.monkeyPatchGetGamepads()}gamepadApiNativelySupported(){return!!this.getNativeGamepads&&!!this.getNativeGamepads.apply(navigator)}AddEmulatedGamepad(e,a,i=y,o=P){if((e===-1||!e&&e!==0)&&(e=this.nextEmptyEGpadIndex(a)),this.emulatedGamepads[e])return!1;const n={emulation:h.gamepadEmulationState.emulated,connected:!0,timestamp:performance.now(),displayId:"Emulated Gamepad "+e,id:"Emulated Gamepad "+e+" (Xinput STANDARD GAMEPAD)",mapping:"standard",index:e,buttons:new Array(i).fill({pressed:!1,value:0,touched:!1},0,i),axes:new Array(o).fill(0,0,o),hapticActuators:[]};this.emulatedGamepads[e]=n,this.emulatedGamepadsMetadata[e]={overlayMode:a};const d=new Event("gamepadconnected");return d.gamepad=n,window.dispatchEvent(d),n}RemoveEmulatedGamepad(e){this.ClearButtonTouchEventListeners(e),this.ClearJoystickTouchEventListeners(e);var a=this.emulatedGamepads[e];if(a){delete this.emulatedGamepads[e],delete this.emulatedGamepadsMetadata[e];const i={...a,connected:!1,timestamp:performance.now()},o=new Event("gamepaddisconnected");o.gamepad=i,window.dispatchEvent(o)}else console.warn("GamepadEmulator Error: Cannot remove emulated gamepad. No emulated gamepad exists at index "+e)}PressButton(e,a,i,o){var s,r,c;if(this.emulatedGamepads[e]==null)throw new Error("Error: PressButton() - no emulated gamepad at index "+e+", pass a valid index, or call AddEmulatedGamepad() first to create an emulated gamepad at that index");const n=[...((s=this.emulatedGamepads[e])==null?void 0:s.buttons)||[]],d=i>this.buttonPressThreshold;if(Array.isArray(a)){const u=d||(o??((r=n[a[0]])==null?void 0:r.touched))||!1;for(var t=0;t<a.length;t++){const l=a[t];if(l<0||l>=this.emulatedGamepads[e].buttons.length){console.error("Error: PressButton() - button index "+l+" out of range, pass a valid index between 0 and "+(this.emulatedGamepads[e].buttons.length-1));continue}n[l]={pressed:d,value:i||0,touched:u}}}else{const u=d||(o??((c=n[a])==null?void 0:c.touched))||!1;if(a<0||a>=this.emulatedGamepads[e].buttons.length){console.error("Error: PressButton() - button index "+a+" out of range, pass a valid index between 0 and "+(this.emulatedGamepads[e].buttons.length-1));return}n[a]={pressed:d,value:i||0,touched:u}}Object.defineProperty(this.emulatedGamepads[e],"buttons",{value:n,enumerable:!0,configurable:!0})}MoveAxis(e,a,i){var n;if(this.emulatedGamepads[e]==null)throw new Error("Error: MoveAxis() - no emulated gamepad at index "+e+", pass a valid index, or call AddEmulatedGamepad() first to create an emulated gamepad at that index");const o=[...((n=this.emulatedGamepads[e])==null?void 0:n.axes)||[]];o[a]=i,Object.defineProperty(this.emulatedGamepads[e],"axes",{value:o,enumerable:!0,configurable:!0})}AddButtonTouchEventListeners(e,a){if(!this.emulatedGamepads[e])throw new Error("Error: AddJoystickTouchEventListeners() - no emulated gamepad at index "+e+", pass a valid index, or call AddEmulatedGamepad() first to create an emulated gamepad at that index");let i=[];for(var o=0;o<a.length;o++){const n=a[o];if(!n)continue;const d=n.buttonIndexes??n.buttonIndex,t=n.tapTarget;if(!t){console.warn("GamepadEmulator: No tap target in gamepad "+e+" display config for button "+d+", skipping...");continue}const s=l=>{const m=l.changedTouches[0].target;(m==t||m.parentElement==t)&&l.preventDefault()};window.addEventListener("touchstart",s,{passive:!1});const r=l=>{const m=l.buttons==1?1:0;(!n.lockTargetWhilePressed||m==0)&&this.PressButton(e,d,m,!0)};t.addEventListener("pointerenter",r);const c=l=>{const m=l.buttons==1?1:0;(!n.lockTargetWhilePressed||m==0)&&this.PressButton(e,d,0,!1)};t.addEventListener("pointerleave",c);const u=l=>{this.PressButton(e,d,0,!1)};if(t.addEventListener("pointercancel",u),n.type==h.gamepadButtonType.onOff){const l=p=>{p.preventDefault(),this.PressButton(e,d,1,!0),n.lockTargetWhilePressed?t.setPointerCapture(p.pointerId):t.releasePointerCapture(p.pointerId)};t.addEventListener("pointerdown",l);const m=()=>{this.PressButton(e,d,0)};t.addEventListener("pointerup",m),i.push(function(){window.removeEventListener("touchstart",s),t.removeEventListener("pointerenter",r),t.removeEventListener("pointerleave",c),t.removeEventListener("pointerdown",l),t.removeEventListener("pointerup",m),t.removeEventListener("pointercancel",u)})}else if(n.type==h.gamepadButtonType.variable){const l=this.AddDragControlListener(n,(m,p,g)=>{let v=m?this.buttonPressThreshold+1e-5:0;v+=n.directions[h.gamepadDirection.left]||n.directions[h.gamepadDirection.right]?Math.abs(p):0,v+=n.directions[h.gamepadDirection.up]||n.directions[h.gamepadDirection.down]?Math.abs(g):0,this.PressButton(e,d,Math.min(v,1))});i.push(function(){window.removeEventListener("touchstart",s),t.removeEventListener("pointerenter",r),t.removeEventListener("pointerleave",c),t.removeEventListener("pointercancel",u),l()})}}this.emulatedGamepadsMetadata[e].removeButtonListenersFunc=()=>{i.forEach(n=>n())}}AddJoystickTouchEventListeners(e,a){if(!this.emulatedGamepads[e])throw new Error("Error: AddJoystickTouchEventListeners() - no emulated gamepad at index "+e+", pass a valid index, or call AddEmulatedGamepad() first to create an emulated gamepad at that index");let i=[];for(let o=0;o<a.length;o++){const n=a[o];if(!n)continue;if(n.tapTarget==null){console.warn("GamepadEmulator: No tap target in gamepad "+e+" display config for joystick "+o+", skipping...");continue}const d=this.AddDragControlListener(n,(t,s,r)=>{n.xAxisIndex!==void 0&&this.MoveAxis(e,n.xAxisIndex,s),n.yAxisIndex!==void 0&&this.MoveAxis(e,n.yAxisIndex,r)});i.push(d)}this.emulatedGamepadsMetadata[e].removeJoystickListenersFunc=()=>{i.forEach(o=>o())}}ClearButtonTouchEventListeners(e){var a;this.emulatedGamepadsMetadata[e]&&((a=this.emulatedGamepadsMetadata[e])!=null&&a.removeButtonListenersFunc)&&this.emulatedGamepadsMetadata[e].removeButtonListenersFunc()}ClearJoystickTouchEventListeners(e){var a;this.emulatedGamepadsMetadata[e]&&((a=this.emulatedGamepadsMetadata[e])!=null&&a.removeJoystickListenersFunc)&&this.emulatedGamepadsMetadata[e].removeJoystickListenersFunc()}AddDragControlListener(e,a){let i={startX:0,startY:0},o=-1;const n=s=>{var r=s.pointerId;if(o===r){const c=e.directions[h.gamepadDirection.left]?-1:0,u=e.directions[h.gamepadDirection.right]?1:0,l=e.directions[h.gamepadDirection.up]?-1:0,m=e.directions[h.gamepadDirection.down]?1:0,p=s.clientX-i.startX,g=s.clientY-i.startY;let{x:v,y:E}=x.NormalizeClampVector(p,g,e.dragDistance);v=Math.max(Math.min(v,u),c),E=Math.max(Math.min(E,m),l),a(!0,v,E)}},d=s=>{o==s.pointerId&&(document.removeEventListener("pointermove",n,!1),document.removeEventListener("pointerup",d,!1),o=-1,a(!1,0,0))};e.tapTarget.addEventListener("pointerdown",s=>{s.preventDefault(),i.startX=s.clientX,i.startY=s.clientY,o=s.pointerId,e.lockTargetWhilePressed?e.tapTarget.setPointerCapture(s.pointerId):e.tapTarget.releasePointerCapture(s.pointerId),a(!0,0,0),document.addEventListener("pointermove",n,!1),document.addEventListener("pointerup",d,!1)});const t=s=>{s.changedTouches[0].target==e.tapTarget&&s.preventDefault()};return window.addEventListener("touchstart",t,{passive:!1}),function(){window.removeEventListener("touchstart",t),e.tapTarget.removeEventListener("pointerdown",n)}}cloneGamepad(e){if(!e)return e;const a=e.axes?e.axes.length:0,i=e.buttons?e.buttons.length:0,o={};for(let n in e)if(n==="axes"){const d=new Array(a);for(let t=0;t<a;t++)d[t]=Number(e.axes[t]);Object.defineProperty(o,"axes",{value:d,enumerable:!0,configurable:!0})}else if(n==="buttons"){const d=new Array(i);for(let t=0;t<i;t++){const s=e.buttons[t];if(s==null)d[t]=s;else{const r=s.pressed,c=s.value,u=s.touched||!1;d[t]={pressed:r,value:c,touched:u}}}Object.defineProperty(o,"buttons",{value:d,enumerable:!0,configurable:!0})}else Object.defineProperty(o,n,{get:()=>e[n],configurable:!0,enumerable:!0});return o.emulation||Object.defineProperty(o,"emulation",{value:h.gamepadEmulationState.real,configurable:!0,enumerable:!0}),o}nextEmptyEGpadIndex(e){let a=0;if(e)do{if(!this.emulatedGamepads[a])break;a++}while(a<this.emulatedGamepads.length);else{const i=Math.max(this.emulatedGamepads.length,this.patchedGpadToRealIndexMap.length);do{if(!this.emulatedGamepads[a]&&this.patchedGpadToRealIndexMap[a]==null)break;a++}while(a<i)}return a}nextEmptyRealGpadIndex(e){let a=e;const i=Math.max(this.emulatedGamepads.length,this.patchedGpadToRealIndexMap.length);do{const o=this.emulatedGamepadsMetadata[a],n=this.realGpadToPatchedIndexMap[a]==null&&this.patchedGpadToRealIndexMap[a]==null;if(o&&o.overlayMode||!o&&n)break;a++}while(a<i);return a}monkeyPatchGamepadEvents(){let e,a,i,o;window.hasOwnProperty("ongamepadconnected")&&(e=Object.getOwnPropertyDescriptor(window,"ongamepadconnected"),e.configurable=!0,i=window.ongamepadconnected,window.ongamepadconnected=null,Object.defineProperty(window,"ongamepadconnected",{get:()=>function(t){},set:t=>{i=t},configurable:!0})),window.hasOwnProperty("ongamepaddisconnected")&&(a=Object.getOwnPropertyDescriptor(window,"ongamepaddisconnected"),a.configurable=!0,o=window.ongamepaddisconnected,window.ongamepaddisconnected=null,Object.defineProperty(window,"ongamepaddisconnected",{get:()=>function(t){},set:t=>{i=t},configurable:!0}));const n=t=>{const s=t.gamepad;if(s&&s.emulation===void 0){t.stopImmediatePropagation(),t.preventDefault();const r=this.cloneGamepad(t.gamepad),c=r.index,u=this.nextEmptyRealGpadIndex(c);this.realGpadToPatchedIndexMap[c]=u,this.patchedGpadToRealIndexMap[u]=c,Object.defineProperty(r,"index",{get:()=>u}),Object.defineProperty(r,"emulation",{get:()=>h.gamepadEmulationState.real});const l=new Event(t.type||"gamepadconnected");l.gamepad=r,window.dispatchEvent(l)}i&&i.call(window,t)};window.addEventListener("gamepadconnected",n);const d=t=>{const s=t.gamepad;if(s&&s.emulation===void 0){t.stopImmediatePropagation(),t.preventDefault();const r=this.cloneGamepad(t.gamepad),c=this.realGpadToPatchedIndexMap[r.index]||r.index;Object.defineProperty(r,"index",{get:()=>c}),Object.defineProperty(r,"emulation",{get:()=>h.gamepadEmulationState.real}),delete this.realGpadToPatchedIndexMap[r.index],delete this.patchedGpadToRealIndexMap[c];const u=new Event(t.type||"gamepaddisconnected");u.gamepad=r,window.dispatchEvent(u)}o&&o.call(window,t)};return window.addEventListener("gamepaddisconnected",d),function(){window.removeEventListener("gamepadconnected",n),window.hasOwnProperty("ongamepadconnected")&&(Object.defineProperty(window,"ongamepadconnected",e),window.ongamepadconnected=i),window.removeEventListener("gamepaddisconnected",d),window.hasOwnProperty("ongamepaddisconnected")&&(Object.defineProperty(window,"ongamepaddisconnected",a),window.ongamepaddisconnected=o)}}monkeyPatchGetGamepads(){const e=this;let a=navigator.getGamepads||navigator.webkitGetGamepads||navigator.mozGetGamepads||navigator.msGetGamepads;this.getNativeGamepads=a,navigator.getNativeGamepads=a||function(){return[]},Object.defineProperty(navigator,"getGamepads",{configurable:!0,value:function(){var d;let i=e.emulatedGamepads,o=a!=null?a.apply(navigator)||[]:[],n=new Array(Math.max(o.length,i.length)).fill(null);for(let t=0;t<o.length;t++){const s=o[t];if(!s)continue;let r=e.cloneGamepad(s),c=e.realGpadToPatchedIndexMap[r.index]||r.index;Object.defineProperty(r,"index",{get:()=>c}),n[c]=r}for(let t=0;t<i.length;t++){let s=n[t],r=i[t];if(r&&s){Object.defineProperty(n[t],"emulation",{value:h.gamepadEmulationState.overlay,configurable:!0});let c=Math.max(((d=s==null?void 0:s.buttons)==null?void 0:d.length)??0,r.buttons.length),u=new Array(c);for(let p=0;p<c;p++){const g=(r==null?void 0:r.buttons[p])||{touched:!1,pressed:!1,value:0},v=(s==null?void 0:s.buttons[p])||{touched:!1,pressed:!1,value:0};u[p]={touched:g.touched||v.touched||!1,pressed:g.pressed||v.pressed||!1,value:Math.max(g.value,v.value)||0}}Object.defineProperty(n[t],"buttons",{value:u,enumerable:!0,configurable:!0});let l=Math.max(r.axes.length,s.axes.length),m=new Array(c);for(let p=0;p<l;p++){const g=r.axes[p]??0,v=s.axes[p]??0;m[p]=Math.abs(g)>Math.abs(v)?g:v}Object.defineProperty(n[t],"axes",{value:m,enumerable:!0,configurable:!0})}else r&&(Object.defineProperty(r,"emulation",{value:h.gamepadEmulationState.emulated,enumerable:!0,configurable:!0}),Object.defineProperty(r,"timestamp",{value:performance.now(),enumerable:!0,configurable:!0}),n[t]=e.cloneGamepad(r))}return n}})}cleanup(){for(let e=0;e<this.emulatedGamepads.length;e++)this.ClearButtonTouchEventListeners(e),this.ClearJoystickTouchEventListeners(e);this.emulatedGamepads=[],this.undoEventPatch(),this.getNativeGamepads?Object.defineProperty(navigator,"getGamepads",{value:this.getNativeGamepads,configurable:!0}):Object.defineProperty(navigator,"getGamepads",{value:void 0,configurable:!0}),w.instanceRunning=!1,delete navigator.getNativeGamepads}};f(w,"instanceRunning",!1);let b=w;exports.DEFAULT_GPAD_AXIS_COUNT=P;exports.DEFAULT_GPAD_BUTTON_COUNT=y;exports.GamepadEmulator=b; //# sourceMappingURL=GamepadEmulator.js.map