multyx-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/multyx.js ADDED
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Multyx=e():t.Multyx=e()}(self,(()=>(()=>{"use strict";var t={376:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Controller=void 0;const i=s(210);e.Controller=class{constructor(t){this.listening=new Set,this.ws=t,this.preventDefault=!1,this.keys={},this.mouse={x:NaN,y:NaN,down:!1,centerX:0,centerY:0,scaleX:1,scaleY:1},document.addEventListener("keydown",(t=>{this.preventDefault&&t.preventDefault;const e=t.key.toLowerCase();this.keys[e]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:e}),this.keys[t.code]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:t.code}),this.listening.has(e)&&!this.keys[e]&&this.relayInput("keydown",{code:t.key}),this.listening.has(t.code)&&!this.keys[t.code]&&this.relayInput("keydown",{code:t.code}),this.keys[e]=!0,this.keys[t.code]=!0})),document.addEventListener("keyup",(t=>{this.preventDefault&&t.preventDefault;const e=t.key.toLowerCase();delete this.keys[e],delete this.keys[t.code],this.listening.has(e)&&this.relayInput("keyup",{code:e}),this.listening.has(t.code)&&this.relayInput("keyup",{code:t.code})})),document.addEventListener("mousedown",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!0,this.listening.has("mousedown")&&this.relayInput("mousedown",{x:this.mouse.x,y:this.mouse.y})})),document.addEventListener("mouseup",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!1,this.listening.has("mouseup")&&this.relayInput("mouseup",{x:this.mouse.x,y:this.mouse.y})})),document.addEventListener("mousemove",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.listening.has("mousemove")&&this.relayInput("mousemove",{x:this.mouse.x,y:this.mouse.y})}))}mapCanvasPosition(t,e){var s,i,o,n,r,h,l,a,u,c,f,p,d,m,g,v;const b="top"in e,y="bottom"in e,w="left"in e,M="right"in e,x=e.anchor,j=t.getBoundingClientRect(),S=(t,...e)=>{const s=t?"Cannot include value for ":"Must include value for ",i=1==e.length?e[0]:e.slice(0,-1).join(", ")+(t?" and ":" or ")+e.slice(-1)[0],o=x?" if anchoring at "+x:" if not anchoring";console.error(s+i+o)},k=j.width/j.height,E=j.height/j.width;if((Number.isNaN(k)||Number.isNaN(E))&&console.error("Canvas element bounding box is flat, canvas must be present on the screen"),x){if("center"==x){if(b&&y&&e.top!==-e.bottom||w&&M&&e.left!==-e.right)return S(!0,"top","bottom","left","right");b?(e.left=w?e.left:M?-e.right:-Math.abs(k*e.top),e.right=w?-e.left:M?e.right:Math.abs(k*e.top),e.bottom=-e.top):y?(e.left=w?e.left:M?-e.right:-Math.abs(k*e.bottom),e.right=w?-e.left:M?e.right:Math.abs(k*e.bottom),e.top=-e.bottom):w?(e.top=b?e.top:y?-e.bottom:-Math.abs(E*e.left),e.bottom=b?-e.top:y?e.bottom:Math.abs(E*e.left),e.right=-e.left):M&&(e.top=b?e.top:y?-e.bottom:-Math.abs(E*e.right),e.bottom=b?-e.top:y?e.bottom:Math.abs(E*e.right),e.left=-e.right)}else if("bottom"==x){if(!w&&!M&&!b)return S(!1,"left","right","top");if(e.bottom)return S(!0,"bottom");e.bottom=0,w?(null!==(s=e.top)&&void 0!==s||(e.top=Math.abs(E*e.left*2)),null!==(i=e.right)&&void 0!==i||(e.right=-e.left)):M?(null!==(o=e.top)&&void 0!==o||(e.top=Math.abs(E*e.right*2)),null!==(n=e.left)&&void 0!==n||(e.left=-e.right)):(e.left=-Math.abs(k*e.top/2),e.right=-e.left)}else if("top"==x){if(!w&&!M&&!y)return S(!1,"left","right","bottom");if(e.top)return S(!0,"top");e.top=0,w?(null!==(r=e.bottom)&&void 0!==r||(e.bottom=Math.abs(E*e.left*2)),null!==(h=e.right)&&void 0!==h||(e.right=-e.left)):M?(null!==(l=e.bottom)&&void 0!==l||(e.bottom=Math.abs(E*e.right*2)),null!==(a=e.left)&&void 0!==a||(e.left=-e.right)):(e.left=-Math.abs(k*e.bottom/2),e.right=-e.left)}else if("left"==x){if(!b&&!y&&!M)return S(!1,"top","bottom","right");if(w)return S(!0,"left");e.left=0,b?(null!==(u=e.right)&&void 0!==u||(e.right=-Math.abs(k*e.top*2)),null!==(c=e.bottom)&&void 0!==c||(e.bottom=-e.top)):y?(null!==(f=e.right)&&void 0!==f||(e.right=Math.abs(k*e.bottom*2)),null!==(p=e.top)&&void 0!==p||(e.top=-e.bottom)):(e.top=-Math.abs(E*e.right/2),e.bottom=-e.top)}else if("right"==x){if(!b&&!y&&!w)return S(!1,"top","bottom","left");if(M)return S(!0,"right");e.right=0,b?(null!==(d=e.left)&&void 0!==d||(e.left=-Math.abs(k*e.top*2)),null!==(m=e.bottom)&&void 0!==m||(e.bottom=-e.top)):y?(null!==(g=e.left)&&void 0!==g||(e.left=Math.abs(k*e.bottom*2)),null!==(v=e.top)&&void 0!==v||(e.top=-e.bottom)):(e.top=-Math.abs(E*e.right/2),e.bottom=-e.top)}else if("topleft"==x){if(!M&&!y)return S(!1,"right","bottom");if(w||b)return S(!0,"left","top");e.left=e.top=0,M?e.bottom=Math.abs(E*e.right):e.right=Math.abs(k*e.bottom)}else if("topright"==x){if(!w&&!y)return S(!1,"left","bottom");if(M||b)return S(!0,"right","top");e.right=e.top=0,w?e.bottom=Math.abs(E*e.left):e.left=Math.abs(k*e.bottom)}else if("bottomleft"==x){if(!M&&!b)return S(!1,"right","top");if(y||w)return S(!0,"bottom","left");e.left=e.bottom=0,M?e.top=Math.abs(E*e.right):e.right=Math.abs(k*e.top)}else if("bottomright"==x){if(!b&&!w)return S(!1,"top","left");if(M||y)return S(!0,"bottom","right");e.right=e.bottom=0,w?e.top=Math.abs(E*e.left):e.left=Math.abs(k*e.top)}}else{if(!b&&!y)return S(!1,"top","bottom");if(y?b||(e.top=e.bottom-t.height):e.bottom=e.top+t.height,!w&&!M)return S(!1,"left","right");M?w||(e.left=e.right-t.width):e.right=e.left+t.width}const O=t.getContext("2d");O.setTransform(1,0,0,1,0,0),t.width=Math.floor(Math.abs(e.right-e.left)),t.height=Math.floor(Math.abs(e.bottom-e.top)),e.right<e.left&&O.scale(-1,1),e.top>e.bottom&&O.scale(1,-1),O.translate(-e.left,-e.top)}mapMousePosition(t,e,s=document.body,i=1,o=i){const n=window.innerWidth/(s instanceof HTMLCanvasElement?s.width:s.clientWidth),r=window.innerHeight/(s instanceof HTMLCanvasElement?s.height:s.clientHeight),h=s.getBoundingClientRect();this.mouse.centerX=h.left+t*n,this.mouse.centerY=h.top+e*r,this.mouse.scaleX=i*n,this.mouse.scaleY=o*r}mapMouseToCanvas(t){const e=t.getContext("2d").getTransform(),s=t.getBoundingClientRect(),i=s.width/t.width,o=s.height/t.height;this.mouse.centerX=s.left+e.e*i,this.mouse.centerY=s.top+e.f*o,this.mouse.scaleX=i*e.a,this.mouse.scaleY=o*e.d}setMouseAs(t){this.mouseGetter=t}relayInput(t,e){if(1!==this.ws.readyState)throw new Error("Websocket connection is "+(2==this.ws.readyState?"closing":"closed"));this.ws.send(i.Message.Native(Object.assign({instruction:"input",input:t},e?{data:e}:{})))}}},34:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.MultyxClientValue=e.MultyxClientObject=e.MultyxClientList=void 0;const i=s(70);e.MultyxClientList=i.default;const o=s(614);e.MultyxClientObject=o.default;const n=s(501);e.MultyxClientValue=n.default},70:(t,e,s)=>{var i;Object.defineProperty(e,"__esModule",{value:!0});const o=s(787),n=s(614);class r extends n.default{get value(){const t=[];for(let e=0;e<this.length;e++)t[e]=this.get(e).value;return t}constructor(t,e,s=[],n){return super(t,{},s,n),this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[i]=()=>this.value,this.length=0,this.push(...e instanceof o.EditWrapper?e.value.map((t=>new o.EditWrapper(t))):e),new Proxy(this,{has:(t,e)=>e in t||t.has(e),get:(t,e)=>e in t?t[e]:t.get(e),set:(t,e,s)=>e in t?(t[e]=s,!0):t.set(e,s),deleteProperty:(t,e)=>t.delete(e,!1)})}set(t,e){if("string"==typeof t&&(t=parseInt(t)),void 0===e)return this.delete(t,!1);if(e instanceof o.EditWrapper&&void 0===e.value)return this.delete(t,!0);const s=super.set(t,e);return s&&t>=this.length&&(this.length=t+1),s}delete(t,e=!1){"string"==typeof t&&(t=parseInt(t));const s=super.delete(t,e);return s&&(this.length=this.reduce(((t,e,s)=>void 0!==e?s+1:t),0)),s}forAll(t){for(let e=0;e<this.length;e++)t(this.get(e),e);super.forAll(((e,s)=>t(s,e)))}push(...t){for(const e of t)this.set(this.length,e);return this.length}pop(){if(0===this.length)return null;const t=this.get(this.length);return this.delete(this.length),t}unshift(...t){for(let e=this.length-1;e>=0;e--)e>=t.length?this.set(e,this.get(e-t.length)):this.set(e,t[e]);return this.length}shift(){if(0==this.length)return;this.length--;const t=this.get(0);for(let t=0;t<this.length;t++)this.set(t,this.get(t+1));return t}splice(t,e,...s){void 0===e&&(e=this.length-t);let i=s.length-e;if(i>0)for(let s=this.length-1;s>=t+e;s--)this.set(s+i,this.get(s));else if(i<0){for(let s=t+e;s<this.length;s++)this.set(s+i,this.get(s));const s=this.length;for(let t=s+i;t<s;t++)this.set(t,void 0)}for(let e=t;e<s.length;e++)this.set(e,s[e])}filter(t){const e=[];for(let s=0;s<this.length;s++)e.push(t(this.get(s),s,this));let s=0;for(let t=0;t<e.length;t++)e[t]&&s&&this.set(t-s,this.get(t)),e[t]||s--}map(t){for(let e=0;e<this.length;e++)this.set(e,t(this.get(e),e,this))}flat(){for(let t=0;t<this.length;t++){const e=this.get(t);if(e instanceof r)for(let s=0;s<e.length;s++)t++,this.set(t,e[s])}}reduce(t,e){for(let s=0;s<this.length;s++)e=t(e,this.get(s),s,this);return e}reduceRight(t,e){for(let s=this.length-1;s>=0;s--)e=t(e,this.get(s),s,this);return e}reverse(){let t=this.length-1;for(let e=0;e<t;e++){const s=this.get(e),i=this.get(t);this.set(e,i),this.set(t,s)}return this}forEach(t){for(let e=0;e<this.length;e++)t(this.get(e),e,this)}every(t){for(let e=0;e<this.length;e++)if(!t(this.get(e),e,this))return!1;return!0}some(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return!0;return!1}find(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return this.get(e)}findIndex(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return e;return-1}deorder(){const t=[];for(const e in this.object)t.push(this.get(e));return t}deorderEntries(){const t=[];for(const e in this.object)t.push([parseInt(e),this.get(e)]);return t}entries(){const t=[];for(let e=0;e<this.length;e++)t.push([this.get(e),e]);return t}keys(){return Array(this.length).fill(0).map(((t,e)=>e))}[Symbol.iterator](){const t=[];for(let e=0;e<this.length;e++)t[e]=this.get(e);return t[Symbol.iterator]()}}i=Symbol.toPrimitive,e.default=r},614:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0});const i=s(210),o=s(787),n=s(735),r=s(501);class h{get value(){const t={};for(const e in this.object)t[e]=this.object[e];return t}constructor(t,e,s=[],i){this.object={},this.propertyPath=s,this.multyx=t,this.editable=i,this.setterListeners=[],e instanceof h&&(e=e.value);for(const t in e instanceof o.EditWrapper?e.value:e)this.set(t,e instanceof o.EditWrapper?new o.EditWrapper(e.value[t]):e[t]);if(this.constructor===h)return new Proxy(this,{has:(t,e)=>t.has(e),get:(t,e)=>e in t?t[e]:t.get(e),set:(t,e,s)=>e in t?(t[e]=s,!0):t.set(e,s),deleteProperty:(t,e)=>t.delete(e,!1)})}has(t){return t in this.object}get(t){return this.object[t]}set(t,e){if(void 0===e)return this.delete(t);if(this.object[t]instanceof r.default)return this.object[t].set(e);if(e instanceof o.EditWrapper&&void 0===e.value)return this.delete(t,!0);if(!(e instanceof o.EditWrapper||this.editable))return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")+"."+t}' to ${e}`),!1;this.object[t]=new((0,n.default)(e instanceof o.EditWrapper?e.value:e))(this.multyx,e,[...this.propertyPath,t],this.editable);for(const e of this.setterListeners)this.multyx[o.Add]((()=>{this.has(t)&&e(t,this.get(t))}));return e instanceof o.EditWrapper||this.multyx.ws.send(i.Message.Native({instruction:"edit",path:this.propertyPath,value:this.object[t].value})),!0}delete(t,e=!1){return this.editable||e?(delete this.object[t],e||this.multyx.ws.send(i.Message.Native({instruction:"edit",path:[...this.propertyPath,t],value:void 0})),!0):(this.multyx.options.verbose&&console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join(".")+"."+t}'`),!1)}forAll(t){for(let e in this.object)t(e,this.get(e));this.setterListeners.push(t)}keys(){return Object.keys(this.object)}values(){return Object.values(this.object)}entries(){const t=[];for(let e in this.object)t.push([e,this.get(e)]);return t}[o.Unpack](t){for(const e in t)this.object[e][o.Unpack](t[e])}}e.default=h},735:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return Array.isArray(t)?s(70).default:"object"==typeof t?s(614).default:s(501).default}},501:(t,e,s)=>{var i;Object.defineProperty(e,"__esModule",{value:!0});const o=s(210),n=s(787);class r{get value(){return this.interpolator?this.interpolator.get():this._value}set value(t){this._value=t,this.interpolator&&this.interpolator.set()}constructor(t,e,s=[],o){this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[i]=()=>this.value,this.propertyPath=s,this.editable=o,this.multyx=t,this.constraints={},this.set(e)}set(t){if(t instanceof n.EditWrapper)return this.value=t.value,!0;if(!this.editable)return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")}' to ${t}`),!1;let e=t;for(const s in this.constraints)if(e=(0,this.constraints[s])(e),null===e)return this.multyx.options.verbose&&console.error(`Attempting to set property that failed on constraint. Setting '${this.propertyPath.join(".")}' to ${t}, stopped by constraint '${s}'`),!1;return this.value===e?(this.value=e,!0):(this.value=e,this.multyx.ws.send(o.Message.Native({instruction:"edit",path:this.propertyPath,value:e})),!0)}[n.Unpack](t){for(const[e,s]of Object.entries(t)){const t=(0,n.BuildConstraint)(e,s);t&&(this.constraints[e]=t)}}Lerp(){this.interpolator={history:[{value:this._value,time:Date.now()},{value:this._value,time:Date.now()}],get:()=>{const[t,e]=this.interpolator.history,s=Math.min(1,(Date.now()-t.time)/Math.min(250,t.time-e.time));return Number.isNaN(s)||"number"!=typeof t.value||"number"!=typeof e.value?t.value:t.value*s+e.value*(1-s)},set:()=>{this.interpolator.history.pop(),this.interpolator.history.unshift({value:this._value,time:Date.now()})}}}PredictiveLerp(){this.interpolator={history:[{value:this._value,time:Date.now()},{value:this._value,time:Date.now()},{value:this._value,time:Date.now()}],get:()=>{const[t,e,s]=this.interpolator.history,i=Math.min(1,(Date.now()-t.time)/(t.time-e.time));return Number.isNaN(i)||"number"!=typeof s.value||"number"!=typeof t.value||"number"!=typeof e.value||Math.abs((t.value-e.value)/(e.value-s.value)-1)>.2?t.value:t.value*(1+i)-e.value*i},set:()=>{this.interpolator.history.pop(),this.interpolator.history.unshift({value:this._value,time:Date.now()})}}}}i=Symbol.toPrimitive,e.default=r},210:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Message=void 0;class s{constructor(t,e,s=!1){this.name=t,this.data=e,this.time=Date.now(),this.native=s}static BundleOperations(t,e){return Array.isArray(e)||(e=[e]),JSON.stringify(new s("_",{operations:e,deltaTime:t}))}static Native(t){return JSON.stringify(new s("_",t,!0))}static Parse(t){const e=JSON.parse(t);return"_"==e.name[0]&&(e.name=e.name.slice(1)),new s(e.name,e.data,""==e.name)}static Create(t,e){if(0==t.length)throw new Error("Multyx message cannot have empty name");if("_"==t[0]&&(t="_"+t),"function"==typeof e)throw new Error("Multyx data must be JSON storable");return JSON.stringify(new s(t,e))}}e.Message=s},944:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.DefaultOptions=void 0,e.DefaultOptions={port:443,secure:!1,uri:"localhost",verbose:!1,logUpdateFrame:!1}},787:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.EditWrapper=e.Add=e.Done=e.Unpack=void 0,e.Interpolate=function(t,e,s){let i={value:t[e],time:Date.now()},o={value:t[e],time:Date.now()};Object.defineProperty(t,e,{get:()=>{const t=o.time-i.time;let e=s[0],n=s[0];for(const i of s)t>i.time&&i.time>e.time&&(e=i),t<i.time&&i.time<n.time&&(n=i);const r=(t-e.time)/(n.time-e.time),h=e.progress+r*(n.progress-e.progress);return Number.isNaN(h)?i.value:o.value*h+i.value*(1-h)},set:t=>Date.now()-o.time<10?(o.value=t,!0):(i=Object.assign({},o),o={value:t,time:Date.now()},!0)})},e.BuildConstraint=function(t,e){return"min"==t?t=>t>=e[0]?t:e[0]:"max"==t?t=>t<=e[0]?t:e[0]:"int"==t?t=>Math.floor(t):"ban"==t?t=>e.includes(t)?null:t:"disabled"==t?t=>e[0]?null:t:t=>t},e.Unpack=Symbol("unpack"),e.Done=Symbol("done"),e.Add=Symbol("add"),e.EditWrapper=class{constructor(t){this.value=t}}}},e={};function s(i){var o=e[i];if(void 0!==o)return o.exports;var n=e[i]={exports:{}};return t[i](n,n.exports,s),n.exports}var i={};return(()=>{var t=i;const e=s(210),o=s(787),n=s(376),r=s(34),h=s(944);class l{constructor(t={},s){this.options=Object.assign(Object.assign({},h.DefaultOptions),t);const i=`ws${this.options.secure?"s":""}://${this.options.uri}:${this.options.port}/`;this.ws=new WebSocket(i),this.ping=0,this.events=new Map,this.self={},this.all={},this.teams={},this.clients={},this.controller=new n.Controller(this.ws),this.listenerQueue=[],null==s||s(),this.ws.onmessage=t=>{var s,i,o;const n=e.Message.Parse(t.data);this.ping=2*(Date.now()-n.time),n.native?(this.parseNativeEvent(n),null===(s=this.events.get(l.Native))||void 0===s||s.forEach((t=>t(n)))):n.name in this.events&&(this.events[n.name](n.data),null===(i=this.events.get(l.Custom))||void 0===i||i.forEach((t=>t(n)))),null===(o=this.events.get(l.Any))||void 0===o||o.forEach((t=>t(n)))}}on(t,e){var s;const i=null!==(s=this.events.get(t))&&void 0!==s?s:[];i.push(e),this.events.set(t,i)}send(t,s,i=!1){if("_"===t[0]&&(t="_"+t),this.ws.send(e.Message.Create(t,s)),i)return new Promise((e=>this.events.set(Symbol.for("_"+t),[e])))}loop(t,e){if(e)this.on(l.Start,(()=>setInterval(t,Math.round(1e3/e))));else{const e=()=>{t(),requestAnimationFrame(e)};this.on(l.Start,(()=>requestAnimationFrame(e)))}}forAll(t){this.on(l.Start,(()=>{this.teams.all.clients.forAll((e=>t(this.clients[e])))})),this.on(l.Connection,t)}parseNativeEvent(t){var e,s,i,o;this.options.logUpdateFrame&&console.log(t);for(const n of t.data)switch(n.instruction){case"init":this.initialize(n);for(const t of null!==(e=this.events.get(l.Start))&&void 0!==e?e:[])this.listenerQueue.push((()=>t(n)));this.events.has(l.Start)&&(this.events.get(l.Start).length=0);break;case"edit":this.parseEdit(n);for(const t of null!==(s=this.events.get(l.Edit))&&void 0!==s?s:[])this.listenerQueue.push((()=>t(n)));break;case"self":this.parseSelf(n);break;case"conn":this.clients[n.uuid]=new r.MultyxClientObject(this,n.data,[n.uuid],!1);for(const t of null!==(i=this.events.get(l.Connection))&&void 0!==i?i:[])this.listenerQueue.push((()=>t(this.clients[n.uuid])));break;case"dcon":for(const t of null!==(o=this.events.get(l.Disconnect))&&void 0!==o?o:[]){const e=this.clients[n.client].value;this.listenerQueue.push((()=>t(e)))}delete this.clients[n.client];break;case"resp":(0,this.events.get(Symbol.for("_"+n.name))[0])(n.response);break;default:this.options.verbose&&console.error("Server error: Unknown native Multyx instruction")}this.listenerQueue.forEach((t=>t())),this.listenerQueue.length=0}initialize(t){this.uuid=t.client.uuid,this.joinTime=t.client.joinTime,this.controller.listening=new Set(t.client.controller),this.teams=new r.MultyxClientObject(this,{},[],!0);for(const e of Object.keys(t.teams))this.teams[e]=new o.EditWrapper(t.teams[e]);this.all=this.teams.all,this.clients={};for(const[e,s]of Object.entries(t.clients))e!=this.uuid&&(this.clients[e]=new r.MultyxClientObject(this,new o.EditWrapper(s),[e],!1));const e=new r.MultyxClientObject(this,new o.EditWrapper(t.client.self),[this.uuid],!0);this.self=e,this.clients[this.uuid]=e;for(const[e,s]of Object.entries(t.constraintTable))(this.uuid==e?this.self:this.teams[e])[o.Unpack](s)}parseEdit(t){let e=t.team?this.teams:this.clients;if(e){for(const s of t.path.slice(0,-1))s in e||(e[s]=new o.EditWrapper({})),e=e[s];e[t.path.slice(-1)[0]]=new o.EditWrapper(t.value)}}parseSelf(t){if("controller"==t.prop)this.controller.listening=new Set(t.data);else if("uuid"==t.prop)this.uuid=t.data;else if("constraint"==t.prop){let e=this.uuid==t.data.path[0]?this.self:this.teams[t.data.path[0]];for(const s of t.data.path.slice(1))e=null==e?void 0:e[s];if(void 0===e)return;e[o.Unpack]({[t.data.name]:t.data.args})}}[o.Add](t){this.listenerQueue.push(t)}}l.Start=Symbol("start"),l.Connection=Symbol("connection"),l.Disconnect=Symbol("disconnect"),l.Edit=Symbol("edit"),l.Native=Symbol("native"),l.Custom=Symbol("custom"),l.Any=Symbol("any"),t.default=l})(),i.default})()));
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "multyx-client",
3
+ "version": "0.1.0",
4
+ "description": "Framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "tsc",
9
+ "webpack": "tsc && npx webpack"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/seanlnge/multyx.git"
14
+ },
15
+ "keywords": [
16
+ "multiplayer",
17
+ "javascript",
18
+ "websocket",
19
+ "server",
20
+ "browser-game",
21
+ "typescript"
22
+ ],
23
+ "author": "Sean Lange",
24
+ "license": "ISC",
25
+ "types": "dist/index.d.ts",
26
+ "bugs": {
27
+ "url": "https://github.com/seanlnge/multyx/issues"
28
+ },
29
+ "homepage": "https://github.com/seanlnge/multyx#readme",
30
+ "devDependencies": {
31
+ "webpack": "^5.88.2",
32
+ "webpack-cli": "^5.1.4"
33
+ }
34
+ }
@@ -0,0 +1,353 @@
1
+ import { Message } from "./message";
2
+ import { RawObject } from "./types";
3
+
4
+ export class Controller {
5
+ private mouseGetter: () => { x: number, y: number };
6
+ listening: Set<string>;
7
+ ws: WebSocket;
8
+
9
+ preventDefault: boolean;
10
+
11
+ keys: RawObject<boolean>;
12
+ mouse: {
13
+ x: number, y: number, down: boolean, // Mouse state
14
+ centerX: number, centerY: number, // Translation in reference to top left of document body, scaled by unit pixels
15
+ scaleX: number, scaleY: number // Scaling in reference to unit pixels
16
+ };
17
+
18
+ constructor(ws: WebSocket) {
19
+ this.listening = new Set();
20
+ this.ws = ws;
21
+ this.preventDefault = false;
22
+
23
+ this.keys = {};
24
+ this.mouse = {
25
+ x: NaN,
26
+ y: NaN,
27
+ down: false,
28
+ centerX: 0,
29
+ centerY: 0,
30
+ scaleX: 1,
31
+ scaleY: 1
32
+ };
33
+
34
+ document.addEventListener('keydown', e => {
35
+ if(this.preventDefault) e.preventDefault;
36
+
37
+ const key = e.key.toLowerCase();
38
+
39
+ // When holding down key
40
+ if(this.keys[key] && this.listening.has('keyhold')) {
41
+ this.relayInput('keyhold', { code: key });
42
+ }
43
+ if(this.keys[e.code] && this.listening.has('keyhold')) {
44
+ this.relayInput('keyhold', { code: e.code });
45
+ }
46
+
47
+ // Change in key state
48
+ if(this.listening.has(key) && !this.keys[key]) {
49
+ this.relayInput('keydown', { code: e.key });
50
+ }
51
+ if(this.listening.has(e.code) && !this.keys[e.code]) {
52
+ this.relayInput('keydown', { code: e.code });
53
+ }
54
+
55
+ this.keys[key] = true;
56
+ this.keys[e.code] = true;
57
+
58
+ });
59
+ document.addEventListener('keyup', e => {
60
+ if(this.preventDefault) e.preventDefault;
61
+
62
+ const key = e.key.toLowerCase();
63
+
64
+ delete this.keys[key];
65
+ delete this.keys[e.code];
66
+ if(this.listening.has(key)) this.relayInput('keyup', { code: key });
67
+ if(this.listening.has(e.code)) this.relayInput('keyup', { code: e.code });
68
+ });
69
+
70
+ // Mouse input events
71
+ document.addEventListener('mousedown', e => {
72
+ if(this.preventDefault) e.preventDefault;
73
+
74
+ if(this.mouseGetter) {
75
+ const mouse = this.mouseGetter();
76
+ this.mouse.x = mouse.x;
77
+ this.mouse.y = mouse.y;
78
+ } else {
79
+ this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX;
80
+ this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY;
81
+ }
82
+ this.mouse.down = true;
83
+ if(this.listening.has('mousedown'))
84
+ this.relayInput('mousedown', {
85
+ x: this.mouse.x, y: this.mouse.y
86
+ });
87
+ });
88
+ document.addEventListener('mouseup', e => {
89
+ if(this.preventDefault) e.preventDefault;
90
+
91
+ if(this.mouseGetter) {
92
+ const mouse = this.mouseGetter();
93
+ this.mouse.x = mouse.x;
94
+ this.mouse.y = mouse.y;
95
+ } else {
96
+ this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX;
97
+ this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY;
98
+ }
99
+ this.mouse.down = false;
100
+ if(this.listening.has('mouseup'))
101
+ this.relayInput('mouseup', {
102
+ x: this.mouse.x, y: this.mouse.y
103
+ });
104
+ });
105
+ document.addEventListener('mousemove', e => {
106
+ if(this.preventDefault) e.preventDefault;
107
+
108
+ if(this.mouseGetter) {
109
+ const mouse = this.mouseGetter();
110
+ this.mouse.x = mouse.x;
111
+ this.mouse.y = mouse.y;
112
+ } else {
113
+ this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX;
114
+ this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY;
115
+ }
116
+ if(this.listening.has('mousemove'))
117
+ this.relayInput('mousemove', {
118
+ x: this.mouse.x, y: this.mouse.y
119
+ });
120
+ })
121
+ }
122
+
123
+ /**
124
+ * Map the canvas to specified top left and bottom right positions
125
+ * @param canvas HTML canvas element
126
+ * @param canvasContext 2D rendering context for canvas
127
+ * @param top Canvas position to correspond to top of canvas
128
+ * @param left Canvas position to correspond to left of canvas
129
+ * @param bottom Canvas position to correspond to bottom of canvas
130
+ * @param right Canvas position to correspond to right of canvas
131
+ * @param anchor Anchor the origin at a specific spot on the canvas
132
+ */
133
+ mapCanvasPosition(canvas: HTMLCanvasElement, position: {
134
+ top?: number, bottom?: number, left?: number, right?: number,
135
+ anchor?: 'center' | 'left' | 'right' | 'top' | 'bottom' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright'
136
+ }) {
137
+ const t = 'top' in position;
138
+ const b = 'bottom' in position;
139
+ const l = 'left' in position;
140
+ const r = 'right' in position;
141
+ const a = position.anchor;
142
+
143
+ const bounding = canvas.getBoundingClientRect();
144
+
145
+ const error = (included: boolean, ...pieces: string[]) => {
146
+ const p1 = included ? "Cannot include value for " : "Must include value for ";
147
+ const p2 = pieces.length == 1 ? pieces[0] : pieces.slice(0, -1).join(', ') + (included ? ' and ' : ' or ') + pieces.slice(-1)[0];
148
+ const p3 = a ? " if anchoring at " + a : " if not anchoring";
149
+ console.error(p1 + p2 + p3);
150
+ }
151
+
152
+ const wToH = bounding.width / bounding.height;
153
+ const hToW = bounding.height / bounding.width;
154
+
155
+ if(Number.isNaN(wToH) || Number.isNaN(hToW)) {
156
+ console.error("Canvas element bounding box is flat, canvas must be present on the screen");
157
+ }
158
+
159
+ // mb bruh jus trust it works
160
+ if(!a) {
161
+ if(!t && !b) return error(false, 'top', 'bottom');
162
+ else if(!b) position.bottom = position.top + canvas.height;
163
+ else if(!t) position.top = position.bottom - canvas.height;
164
+
165
+ if(!l && !r) return error(false, 'left', 'right');
166
+ else if(!r) position.right = position.left + canvas.width;
167
+ else if(!l) position.left = position.right - canvas.width;
168
+ } else if(a == 'center') {
169
+ if(t && b && position.top !== -position.bottom
170
+ || l && r && position.left !== -position.right) return error(true, 'top', 'bottom', 'left', 'right');
171
+
172
+ if(t) {
173
+ position.left = l ? position.left : r ? -position.right : -Math.abs(wToH * position.top);
174
+ position.right = l ? -position.left : r ? position.right : Math.abs(wToH * position.top);
175
+ position.bottom = -position.top;
176
+ } else if(b) {
177
+ position.left = l ? position.left : r ? -position.right : -Math.abs(wToH * position.bottom);
178
+ position.right = l ? -position.left : r ? position.right : Math.abs(wToH * position.bottom);
179
+ position.top = -position.bottom;
180
+ } else if(l) {
181
+ position.top = t ? position.top : b ? -position.bottom : -Math.abs(hToW * position.left);
182
+ position.bottom = t ? -position.top : b ? position.bottom : Math.abs(hToW * position.left);
183
+ position.right = -position.left;
184
+ } else if(r) {
185
+ position.top = t ? position.top : b ? -position.bottom : -Math.abs(hToW * position.right);
186
+ position.bottom = t ? -position.top : b ? position.bottom : Math.abs(hToW * position.right);
187
+ position.left = -position.right;
188
+ }
189
+ } else if(a == 'bottom') {
190
+ if(!l && !r && !t) return error(false, 'left', 'right', 'top');
191
+ if(position.bottom) return error(true, 'bottom');
192
+ position.bottom = 0;
193
+
194
+ if(l) {
195
+ position.top ??= Math.abs(hToW * position.left * 2);
196
+ position.right ??= -position.left;
197
+ } else if(r) {
198
+ position.top ??= Math.abs(hToW * position.right * 2);
199
+ position.left ??= -position.right;
200
+ } else {
201
+ position.left = -Math.abs(wToH * position.top / 2);
202
+ position.right = -position.left;
203
+ }
204
+ } else if(a == 'top') {
205
+ if(!l && !r && !b) return error(false, 'left', 'right', 'bottom');
206
+ if(position.top) return error(true, 'top');
207
+ position.top = 0;
208
+
209
+ if(l) {
210
+ position.bottom ??= Math.abs(hToW * position.left * 2);
211
+ position.right ??= -position.left;
212
+ } else if(r) {
213
+ position.bottom ??= Math.abs(hToW * position.right * 2);
214
+ position.left ??= -position.right;
215
+ } else {
216
+ position.left = -Math.abs(wToH * position.bottom / 2);
217
+ position.right = -position.left;
218
+ }
219
+ } else if(a == 'left') {
220
+ if(!t && !b && !r) return error(false, 'top', 'bottom', 'right');
221
+ if(l) return error(true, 'left');
222
+ position.left = 0;
223
+
224
+ if(t) {
225
+ position.right ??= -Math.abs(wToH * position.top * 2);
226
+ position.bottom ??= -position.top;
227
+ } else if(b) {
228
+ position.right ??= Math.abs(wToH * position.bottom * 2);
229
+ position.top ??= -position.bottom;
230
+ } else {
231
+ position.top = -Math.abs(hToW * position.right / 2);
232
+ position.bottom = -position.top;
233
+ }
234
+ } else if(a == 'right') {
235
+ if(!t && !b && !l) return error(false, 'top', 'bottom', 'left');
236
+ if(r) return error(true, 'right');
237
+ position.right = 0;
238
+
239
+ if(t) {
240
+ position.left ??= -Math.abs(wToH * position.top * 2);
241
+ position.bottom ??= -position.top;
242
+ } else if(b) {
243
+ position.left ??= Math.abs(wToH * position.bottom * 2);
244
+ position.top ??= -position.bottom;
245
+ } else {
246
+ position.top = -Math.abs(hToW * position.right / 2);
247
+ position.bottom = -position.top;
248
+ }
249
+ } else if(a == 'topleft') {
250
+ if(!r && !b) return error(false, 'right', 'bottom');
251
+ if(l || t) return error(true, 'left', 'top');
252
+ position.left = position.top = 0;
253
+
254
+ if(r) position.bottom = Math.abs(hToW * position.right);
255
+ else position.right = Math.abs(wToH * position.bottom);
256
+ } else if(a == 'topright') {
257
+ if(!l && !b) return error(false, 'left', 'bottom');
258
+ if(r || t) return error(true, 'right', 'top');
259
+ position.right = position.top = 0;
260
+
261
+ if(l) position.bottom = Math.abs(hToW * position.left);
262
+ else position.left = Math.abs(wToH * position.bottom);
263
+ } else if(a == 'bottomleft') {
264
+ if(!r && !t) return error(false, 'right', 'top');
265
+ if(b || l) return error(true, 'bottom', 'left');
266
+ position.left = position.bottom = 0;
267
+
268
+ if(r) position.top = Math.abs(hToW * position.right);
269
+ else position.right = Math.abs(wToH * position.top);
270
+ } else if(a == 'bottomright') {
271
+ if(!t && !l) return error(false, 'top', 'left');
272
+ if(r || b) return error(true, 'bottom', 'right');
273
+ position.right = position.bottom = 0;
274
+
275
+ if(l) position.top = Math.abs(hToW * position.left);
276
+ else position.left = Math.abs(wToH * position.top);
277
+ }
278
+
279
+ const ctx = canvas.getContext("2d");
280
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
281
+
282
+ canvas.width = Math.floor(Math.abs(position.right-position.left));
283
+ canvas.height = Math.floor(Math.abs(position.bottom-position.top));
284
+ if(position.right < position.left) ctx.scale(-1, 1);
285
+ if(position.top > position.bottom) ctx.scale(1, -1);
286
+
287
+ ctx.translate(-position.left, -position.top);
288
+ }
289
+
290
+ /**
291
+ * @param centerX Anchor x-value corresponding to mouse position x-value of 0
292
+ * @param centerY Anchor y-value corresponding to mouse position y-value of 0
293
+ * @param anchor HTML Element to read mouse position relative to
294
+ * @param scaleX Number of anchor pixels corresponding to a mouse position x-value change of 1
295
+ * @param scaleY Number of anchor pixels corresponding to a mouse position y-value change of 1
296
+ */
297
+ mapMousePosition(centerX: number, centerY: number, anchor: HTMLElement = document.body, scaleX: number = 1, scaleY: number = scaleX) {
298
+ const ratioX = window.innerWidth / (anchor instanceof HTMLCanvasElement
299
+ ? anchor.width
300
+ : anchor.clientWidth
301
+ );
302
+ const ratioY = window.innerHeight / (anchor instanceof HTMLCanvasElement
303
+ ? anchor.height
304
+ : anchor.clientHeight
305
+ );
306
+
307
+ const bounding = anchor.getBoundingClientRect();
308
+ this.mouse.centerX = bounding.left + centerX * ratioX;
309
+ this.mouse.centerY = bounding.top + centerY * ratioY;
310
+ this.mouse.scaleX = scaleX * ratioX;
311
+ this.mouse.scaleY = scaleY * ratioY;
312
+ }
313
+
314
+ /**
315
+ * Map mouse position to the corresponding canvas coordinates on screen
316
+ * @param canvas Canvas element in DOM
317
+ */
318
+ mapMouseToCanvas(canvas: HTMLCanvasElement) {
319
+ const ctx = canvas.getContext("2d");
320
+ const transform = ctx.getTransform();
321
+ const bounding = canvas.getBoundingClientRect();
322
+
323
+ // Ratio between canvas scale to unit pixels
324
+ const canvasRatioX = bounding.width / canvas.width;
325
+ const canvasRatioY = bounding.height / canvas.height;
326
+
327
+ this.mouse.centerX = bounding.left + transform.e * canvasRatioX;
328
+ this.mouse.centerY = bounding.top + transform.f * canvasRatioY;
329
+
330
+ this.mouse.scaleX = canvasRatioX * transform.a;
331
+ this.mouse.scaleY = canvasRatioY * transform.d;
332
+ }
333
+
334
+ /**
335
+ * Utilize mouse coordinates of another object
336
+ * @param mouseGetter Callback that returns the mouse coordinates at any given time
337
+ */
338
+ setMouseAs(mouseGetter: () => { x: number, y: number }) {
339
+ this.mouseGetter = mouseGetter;
340
+ }
341
+
342
+ private relayInput(input: string, data?: RawObject) {
343
+ if(this.ws.readyState !== 1) {
344
+ throw new Error('Websocket connection is ' + (this.ws.readyState == 2 ? 'closing' : 'closed'));
345
+ }
346
+
347
+ this.ws.send(Message.Native({
348
+ instruction: 'input',
349
+ input: input,
350
+ ...(data ? { data } : {})
351
+ }));
352
+ }
353
+ }