diff --git a/client/package.json b/client/package.json index bc6ec69..7278be0 100644 --- a/client/package.json +++ b/client/package.json @@ -13,9 +13,11 @@ "dependencies": { "@kickoff/shared": "*", "colyseus.js": "^0.16.5", - "pixi.js": "^8.14.3" + "pixi.js": "^8.14.3", + "qrcode": "^1.5.4" }, "devDependencies": { + "@types/qrcode": "^1.5.6", "tsx": "^4.20.6", "typescript": "^5.3.3", "vite": "^5.0.8", diff --git a/client/src/scenes/MultiplayerScene.ts b/client/src/scenes/MultiplayerScene.ts index c2a0fa8..bfea61f 100644 --- a/client/src/scenes/MultiplayerScene.ts +++ b/client/src/scenes/MultiplayerScene.ts @@ -10,6 +10,7 @@ import { AIManager } from '@/ai' import { sceneRouter } from '@/utils/SceneRouter' import type { Room } from 'colyseus.js' import { PixiSceneManager } from '@/utils/PixiSceneManager' +import { WaitingOverlay } from '@/utils/WaitingOverlay' /** * Multiplayer Game Scene (PixiJS) @@ -26,6 +27,7 @@ export class MultiplayerScene extends BaseGameScene { private aiEnabled: boolean = true private lastControlledPlayerId?: string private lastMovementWasNonZero: boolean = false + private waitingOverlay?: WaitingOverlay // Dead reckoning state — track last known server positions + velocities so we can // extrapolate between patches and eliminate visual stalls during network jitter private lastBallServerX: number = 0 @@ -191,7 +193,9 @@ export class MultiplayerScene extends BaseGameScene { this.colorInitialized = false this.positionInitialized = false this.returningToMenu = false - + + this.hideWaitingOverlay() + if (this.aiManager) { this.aiManager = undefined } @@ -388,6 +392,13 @@ export class MultiplayerScene extends BaseGameScene { this.setupNetworkListeners() this.networkManager.checkExistingPlayers() + + // Show QR code overlay while waiting for an opponent + const state = this.networkManager.getState() + if (roomId !== 'Unknown' && state?.phase === 'waiting') { + const joinUrl = `${window.location.origin}${window.location.pathname}#multiplayer?id=${roomId}` + this.waitingOverlay = new WaitingOverlay(joinUrl, this.cameraManager.getGameContainer()) + } } catch (error) { console.error('❌ Multiplayer connection failed:', error) this.isMultiplayer = false @@ -416,6 +427,10 @@ export class MultiplayerScene extends BaseGameScene { } }) + this.networkManager.on('matchStart', () => { + this.hideWaitingOverlay() + }) + this.networkManager.on('playerJoin', (player: any) => { try { console.log('👤 Remote player joined:', player.id, player.team) @@ -445,6 +460,11 @@ export class MultiplayerScene extends BaseGameScene { const state = this.networkManager?.getState() as any if (!state?.players) return + // Hide waiting overlay when match starts (fallback for matchStart event) + if (this.waitingOverlay && state.phase === 'playing') { + this.hideWaitingOverlay() + } + // Create sprites for any new players state.players.forEach((player: any, playerId: string) => { if (!this.players.has(playerId)) { @@ -531,6 +551,13 @@ export class MultiplayerScene extends BaseGameScene { } } + private hideWaitingOverlay(): void { + if (this.waitingOverlay) { + this.waitingOverlay.destroy() + this.waitingOverlay = undefined + } + } + private returnToMenu(message: string): void { if (this.returningToMenu) { return diff --git a/client/src/utils/WaitingOverlay.ts b/client/src/utils/WaitingOverlay.ts new file mode 100644 index 0000000..4e9a825 --- /dev/null +++ b/client/src/utils/WaitingOverlay.ts @@ -0,0 +1,129 @@ +import { Container, Graphics, Text } from 'pixi.js' +import { GAME_CONFIG } from '@shared/types' +import QRCode from 'qrcode' + +/** + * Overlay displayed on the field while waiting for a second player to join. + * Shows a QR code (rendered via PixiJS Graphics) encoding the join URL, + * plus "Waiting for opponent..." text with a subtle pulse animation. + */ +export class WaitingOverlay { + readonly container: Container + private titleText: Text + private animationFrame: number = 0 + private destroyed: boolean = false + + constructor(joinUrl: string, parent: Container) { + this.container = new Container() + this.container.zIndex = 500 + + const centerX = GAME_CONFIG.FIELD_WIDTH / 2 + const centerY = GAME_CONFIG.FIELD_HEIGHT / 2 + + // Semi-transparent backdrop + const backdrop = new Graphics() + backdrop.roundRect(centerX - 200, centerY - 210, 400, 420, 16) + backdrop.fill({ color: 0x000000, alpha: 0.75 }) + this.container.addChild(backdrop) + + // Title text: "Waiting for opponent..." + this.titleText = new Text({ + text: 'Waiting for opponent...', + style: { + fontSize: 26, + fill: '#ffffff', + fontFamily: 'Arial, sans-serif', + fontWeight: 'bold', + }, + }) + this.titleText.anchor.set(0.5) + this.titleText.position.set(centerX, centerY - 175) + this.container.addChild(this.titleText) + + // Generate and render QR code + this.renderQRCode(joinUrl, centerX, centerY) + + // Instruction text: "Scan to join" + const instructionText = new Text({ + text: 'Scan to join', + style: { + fontSize: 20, + fill: '#aaaaaa', + fontFamily: 'Arial, sans-serif', + }, + }) + instructionText.anchor.set(0.5) + instructionText.position.set(centerX, centerY + 175) + this.container.addChild(instructionText) + + parent.addChild(this.container) + + // Start pulse animation + this.animate() + } + + private renderQRCode(url: string, centerX: number, centerY: number): void { + try { + const qr = QRCode.create(url, { errorCorrectionLevel: 'M' }) + const modules = qr.modules + const moduleCount = modules.size + + // Target ~250 game units wide for the QR code + const qrSize = 250 + const cellSize = qrSize / moduleCount + const startX = centerX - qrSize / 2 + const startY = centerY - qrSize / 2 + + const graphics = new Graphics() + + // White background for QR code (with quiet zone) + const padding = cellSize * 2 + graphics.roundRect( + startX - padding, + startY - padding, + qrSize + padding * 2, + qrSize + padding * 2, + 8, + ) + graphics.fill(0xffffff) + + // Draw dark modules + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount; col++) { + if (modules.get(row, col)) { + graphics.rect( + startX + col * cellSize, + startY + row * cellSize, + cellSize, + cellSize, + ) + graphics.fill(0x000000) + } + } + } + + this.container.addChild(graphics) + } catch (error) { + console.error('[WaitingOverlay] Failed to generate QR code:', error) + } + } + + private animate = (): void => { + if (this.destroyed) return + + this.animationFrame++ + // Subtle alpha pulse: oscillate between 0.6 and 1.0 + const alpha = 0.8 + Math.sin(this.animationFrame * 0.05) * 0.2 + this.titleText.alpha = alpha + + requestAnimationFrame(this.animate) + } + + destroy(): void { + this.destroyed = true + if (this.container.parent) { + this.container.parent.removeChild(this.container) + } + this.container.destroy({ children: true }) + } +} diff --git a/package-lock.json b/package-lock.json index 2099d92..f1166f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kickoff", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kickoff", - "version": "0.3.1", + "version": "0.4.0", "workspaces": [ "client", "server", @@ -35,13 +35,15 @@ }, "client": { "name": "@kickoff/client", - "version": "0.3.1", + "version": "0.4.0", "dependencies": { "@kickoff/shared": "*", "colyseus.js": "^0.16.5", - "pixi.js": "^8.14.3" + "pixi.js": "^8.14.3", + "qrcode": "^1.5.4" }, "devDependencies": { + "@types/qrcode": "^1.5.6", "tsx": "^4.20.6", "typescript": "^5.3.3", "vite": "^5.0.8", @@ -3189,6 +3191,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3321,7 +3333,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3331,7 +3342,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3722,6 +3732,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001765", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", @@ -3826,7 +3845,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3839,7 +3857,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colyseus": { @@ -4133,6 +4150,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -4217,6 +4243,12 @@ "node": ">=8" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4317,7 +4349,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -4783,6 +4814,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -4942,7 +4986,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5564,7 +5607,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6053,6 +6095,18 @@ "node": ">=6" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", @@ -6513,6 +6567,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -6535,6 +6625,15 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6686,6 +6785,15 @@ "node": ">=12" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6761,6 +6869,89 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -7016,7 +7207,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7047,6 +7237,12 @@ "node": ">=6" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -7342,6 +7538,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -7628,7 +7830,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -7761,7 +7962,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9309,6 +9509,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/which-typed-array": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", @@ -9749,7 +9955,7 @@ }, "server": { "name": "@kickoff/server", - "version": "0.3.1", + "version": "0.4.0", "dependencies": { "@colyseus/monitor": "^0.16.7", "@kickoff/shared": "*", @@ -10056,7 +10262,7 @@ }, "shared": { "name": "@kickoff/shared", - "version": "0.3.1", + "version": "0.4.0", "devDependencies": { "typescript": "^5.3.3" }