UNPKG

multyx-client

Version:

Framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling

357 lines (356 loc) 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Controller = void 0; const message_1 = require("./message"); class Controller { constructor(ws) { this.listening = new Set(); this.ws = ws; this.preventDefault = false; this.keys = {}; this.mouse = { x: NaN, y: NaN, down: false, centerX: 0, centerY: 0, scaleX: 1, scaleY: 1 }; document.addEventListener('keydown', e => { if (this.preventDefault) e.preventDefault; const key = e.key.toLowerCase(); // When holding down key if (this.keys[key] && this.listening.has('keyhold')) { this.relayInput('keyhold', { code: key }); } if (this.keys[e.code] && this.listening.has('keyhold')) { this.relayInput('keyhold', { code: e.code }); } // Change in key state if (this.listening.has(key) && !this.keys[key]) { this.relayInput('keydown', { code: e.key }); } if (this.listening.has(e.code) && !this.keys[e.code]) { this.relayInput('keydown', { code: e.code }); } this.keys[key] = true; this.keys[e.code] = true; }); document.addEventListener('keyup', e => { if (this.preventDefault) e.preventDefault; const key = e.key.toLowerCase(); delete this.keys[key]; delete this.keys[e.code]; if (this.listening.has(key)) this.relayInput('keyup', { code: key }); if (this.listening.has(e.code)) this.relayInput('keyup', { code: e.code }); }); // Mouse input events document.addEventListener('mousedown', e => { if (this.preventDefault) e.preventDefault; if (this.mouseGetter) { const mouse = this.mouseGetter(); this.mouse.x = mouse.x; this.mouse.y = mouse.y; } else { this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX; this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY; } this.mouse.down = true; if (this.listening.has('mousedown')) this.relayInput('mousedown', { x: this.mouse.x, y: this.mouse.y }); }); document.addEventListener('mouseup', e => { if (this.preventDefault) e.preventDefault; if (this.mouseGetter) { const mouse = this.mouseGetter(); this.mouse.x = mouse.x; this.mouse.y = mouse.y; } else { this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX; this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY; } this.mouse.down = false; if (this.listening.has('mouseup')) this.relayInput('mouseup', { x: this.mouse.x, y: this.mouse.y }); }); document.addEventListener('mousemove', e => { if (this.preventDefault) e.preventDefault; if (this.mouseGetter) { const mouse = this.mouseGetter(); this.mouse.x = mouse.x; this.mouse.y = mouse.y; } else { this.mouse.x = (e.clientX - this.mouse.centerX) / this.mouse.scaleX; this.mouse.y = (e.clientY - this.mouse.centerY) / this.mouse.scaleY; } if (this.listening.has('mousemove')) this.relayInput('mousemove', { x: this.mouse.x, y: this.mouse.y }); }); } /** * Map the canvas to specified top left and bottom right positions * @param canvas HTML canvas element * @param canvasContext 2D rendering context for canvas * @param top Canvas position to correspond to top of canvas * @param left Canvas position to correspond to left of canvas * @param bottom Canvas position to correspond to bottom of canvas * @param right Canvas position to correspond to right of canvas * @param anchor Anchor the origin at a specific spot on the canvas */ mapCanvasPosition(canvas, position) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; const t = 'top' in position; const b = 'bottom' in position; const l = 'left' in position; const r = 'right' in position; const a = position.anchor; const bounding = canvas.getBoundingClientRect(); const error = (included, ...pieces) => { const p1 = included ? "Cannot include value for " : "Must include value for "; const p2 = pieces.length == 1 ? pieces[0] : pieces.slice(0, -1).join(', ') + (included ? ' and ' : ' or ') + pieces.slice(-1)[0]; const p3 = a ? " if anchoring at " + a : " if not anchoring"; console.error(p1 + p2 + p3); }; const wToH = bounding.width / bounding.height; const hToW = bounding.height / bounding.width; if (Number.isNaN(wToH) || Number.isNaN(hToW)) { console.error("Canvas element bounding box is flat, canvas must be present on the screen"); } // mb bruh jus trust it works if (!a) { if (!t && !b) return error(false, 'top', 'bottom'); else if (!b) position.bottom = position.top + canvas.height; else if (!t) position.top = position.bottom - canvas.height; if (!l && !r) return error(false, 'left', 'right'); else if (!r) position.right = position.left + canvas.width; else if (!l) position.left = position.right - canvas.width; } else if (a == 'center') { if (t && b && position.top !== -position.bottom || l && r && position.left !== -position.right) return error(true, 'top', 'bottom', 'left', 'right'); if (t) { position.left = l ? position.left : r ? -position.right : -Math.abs(wToH * position.top); position.right = l ? -position.left : r ? position.right : Math.abs(wToH * position.top); position.bottom = -position.top; } else if (b) { position.left = l ? position.left : r ? -position.right : -Math.abs(wToH * position.bottom); position.right = l ? -position.left : r ? position.right : Math.abs(wToH * position.bottom); position.top = -position.bottom; } else if (l) { position.top = t ? position.top : b ? -position.bottom : -Math.abs(hToW * position.left); position.bottom = t ? -position.top : b ? position.bottom : Math.abs(hToW * position.left); position.right = -position.left; } else if (r) { position.top = t ? position.top : b ? -position.bottom : -Math.abs(hToW * position.right); position.bottom = t ? -position.top : b ? position.bottom : Math.abs(hToW * position.right); position.left = -position.right; } } else if (a == 'bottom') { if (!l && !r && !t) return error(false, 'left', 'right', 'top'); if (position.bottom) return error(true, 'bottom'); position.bottom = 0; if (l) { (_a = position.top) !== null && _a !== void 0 ? _a : (position.top = Math.abs(hToW * position.left * 2)); (_b = position.right) !== null && _b !== void 0 ? _b : (position.right = -position.left); } else if (r) { (_c = position.top) !== null && _c !== void 0 ? _c : (position.top = Math.abs(hToW * position.right * 2)); (_d = position.left) !== null && _d !== void 0 ? _d : (position.left = -position.right); } else { position.left = -Math.abs(wToH * position.top / 2); position.right = -position.left; } } else if (a == 'top') { if (!l && !r && !b) return error(false, 'left', 'right', 'bottom'); if (position.top) return error(true, 'top'); position.top = 0; if (l) { (_e = position.bottom) !== null && _e !== void 0 ? _e : (position.bottom = Math.abs(hToW * position.left * 2)); (_f = position.right) !== null && _f !== void 0 ? _f : (position.right = -position.left); } else if (r) { (_g = position.bottom) !== null && _g !== void 0 ? _g : (position.bottom = Math.abs(hToW * position.right * 2)); (_h = position.left) !== null && _h !== void 0 ? _h : (position.left = -position.right); } else { position.left = -Math.abs(wToH * position.bottom / 2); position.right = -position.left; } } else if (a == 'left') { if (!t && !b && !r) return error(false, 'top', 'bottom', 'right'); if (l) return error(true, 'left'); position.left = 0; if (t) { (_j = position.right) !== null && _j !== void 0 ? _j : (position.right = -Math.abs(wToH * position.top * 2)); (_k = position.bottom) !== null && _k !== void 0 ? _k : (position.bottom = -position.top); } else if (b) { (_l = position.right) !== null && _l !== void 0 ? _l : (position.right = Math.abs(wToH * position.bottom * 2)); (_m = position.top) !== null && _m !== void 0 ? _m : (position.top = -position.bottom); } else { position.top = -Math.abs(hToW * position.right / 2); position.bottom = -position.top; } } else if (a == 'right') { if (!t && !b && !l) return error(false, 'top', 'bottom', 'left'); if (r) return error(true, 'right'); position.right = 0; if (t) { (_o = position.left) !== null && _o !== void 0 ? _o : (position.left = -Math.abs(wToH * position.top * 2)); (_p = position.bottom) !== null && _p !== void 0 ? _p : (position.bottom = -position.top); } else if (b) { (_q = position.left) !== null && _q !== void 0 ? _q : (position.left = Math.abs(wToH * position.bottom * 2)); (_r = position.top) !== null && _r !== void 0 ? _r : (position.top = -position.bottom); } else { position.top = -Math.abs(hToW * position.right / 2); position.bottom = -position.top; } } else if (a == 'topleft') { if (!r && !b) return error(false, 'right', 'bottom'); if (l || t) return error(true, 'left', 'top'); position.left = position.top = 0; if (r) position.bottom = Math.abs(hToW * position.right); else position.right = Math.abs(wToH * position.bottom); } else if (a == 'topright') { if (!l && !b) return error(false, 'left', 'bottom'); if (r || t) return error(true, 'right', 'top'); position.right = position.top = 0; if (l) position.bottom = Math.abs(hToW * position.left); else position.left = Math.abs(wToH * position.bottom); } else if (a == 'bottomleft') { if (!r && !t) return error(false, 'right', 'top'); if (b || l) return error(true, 'bottom', 'left'); position.left = position.bottom = 0; if (r) position.top = Math.abs(hToW * position.right); else position.right = Math.abs(wToH * position.top); } else if (a == 'bottomright') { if (!t && !l) return error(false, 'top', 'left'); if (r || b) return error(true, 'bottom', 'right'); position.right = position.bottom = 0; if (l) position.top = Math.abs(hToW * position.left); else position.left = Math.abs(wToH * position.top); } const ctx = canvas.getContext("2d"); ctx.setTransform(1, 0, 0, 1, 0, 0); canvas.width = Math.floor(Math.abs(position.right - position.left)); canvas.height = Math.floor(Math.abs(position.bottom - position.top)); if (position.right < position.left) ctx.scale(-1, 1); if (position.top > position.bottom) ctx.scale(1, -1); ctx.translate(-position.left, -position.top); } /** * @param centerX Anchor x-value corresponding to mouse position x-value of 0 * @param centerY Anchor y-value corresponding to mouse position y-value of 0 * @param anchor HTML Element to read mouse position relative to * @param scaleX Number of anchor pixels corresponding to a mouse position x-value change of 1 * @param scaleY Number of anchor pixels corresponding to a mouse position y-value change of 1 */ mapMousePosition(centerX, centerY, anchor = document.body, scaleX = 1, scaleY = scaleX) { const ratioX = window.innerWidth / (anchor instanceof HTMLCanvasElement ? anchor.width : anchor.clientWidth); const ratioY = window.innerHeight / (anchor instanceof HTMLCanvasElement ? anchor.height : anchor.clientHeight); const bounding = anchor.getBoundingClientRect(); this.mouse.centerX = bounding.left + centerX * ratioX; this.mouse.centerY = bounding.top + centerY * ratioY; this.mouse.scaleX = scaleX * ratioX; this.mouse.scaleY = scaleY * ratioY; } /** * Map mouse position to the corresponding canvas coordinates on screen * @param canvas Canvas element in DOM */ mapMouseToCanvas(canvas) { const ctx = canvas.getContext("2d"); const transform = ctx.getTransform(); const bounding = canvas.getBoundingClientRect(); // Ratio between canvas scale to unit pixels const canvasRatioX = bounding.width / canvas.width; const canvasRatioY = bounding.height / canvas.height; this.mouse.centerX = bounding.left + transform.e * canvasRatioX; this.mouse.centerY = bounding.top + transform.f * canvasRatioY; this.mouse.scaleX = canvasRatioX * transform.a; this.mouse.scaleY = canvasRatioY * transform.d; } /** * Utilize mouse coordinates of another object * @param mouseGetter Callback that returns the mouse coordinates at any given time */ setMouseAs(mouseGetter) { this.mouseGetter = mouseGetter; } relayInput(input, data) { if (this.ws.readyState !== 1) { throw new Error('Websocket connection is ' + (this.ws.readyState == 2 ? 'closing' : 'closed')); } this.ws.send(message_1.Message.Native(Object.assign({ instruction: 'input', input: input }, (data ? { data } : {})))); } } exports.Controller = Controller;