From 87b06564f38a8379c7be45e420cf6462adb41382 Mon Sep 17 00:00:00 2001 From: Benoit Coudour Date: Mon, 4 Nov 2024 22:24:52 +0100 Subject: [PATCH 01/25] map-metadata integration --- package-lock.json | 1154 +---------------- package.json | 28 +- src/main/content/game/game-content.ts | 28 +- src/main/content/game/game-version.ts | 4 + src/main/content/maps/map-content.ts | 114 +- src/main/content/maps/map-data.ts | 46 +- src/main/content/maps/map-image-worker.ts | 16 + src/main/content/maps/map-image.ts | 34 + src/main/content/maps/online-map.ts | 44 + src/main/content/maps/parse-map-worker.ts | 11 - src/main/content/maps/parse-map.ts | 63 - src/main/content/pr-downloader.ts | 3 +- .../content/replays/parse-replay-worker.ts | 54 +- src/main/content/replays/parse-replay.ts | 63 +- src/main/content/replays/replay-content.ts | 62 +- src/main/content/replays/replay.ts | 4 +- src/main/game/battle/battle-types.ts | 22 +- src/main/game/game.ts | 22 +- src/main/json/file-store.ts | 8 +- src/main/main.ts | 3 +- src/main/model/start-script.ts | 2 +- src/main/model/user.ts | 2 +- src/main/services/game.service.ts | 1 - src/main/services/maps.service.ts | 30 +- src/main/services/news.service.ts | 2 +- src/main/utils/start-script-converter.ts | 35 +- src/preload/preload.ts | 20 +- src/renderer/App.vue | 7 +- src/renderer/api/notifications.ts | 9 +- .../assets/images/factions/armada_faction.png | Bin 0 -> 4136 bytes .../assets/images/factions/cortex_faction.png | Bin 0 -> 4532 bytes .../images/factions/unknown_faction.png | Bin 0 -> 803 bytes src/renderer/assets/images/icons/player.png | Bin 0 -> 3415 bytes .../assets/images/icons/spectator.png | Bin 0 -> 4759 bytes .../components/battle/AddBotModal.vue | 29 +- .../components/battle/BattlePreview.vue | 20 +- .../components/battle/GameModeComponent.vue | 115 ++ .../components/battle/LuaOptionsModal.vue | 155 ++- .../components/battle/MapListModal.vue | 10 +- .../components/battle/MapOptionsModal.vue | 201 ++- .../battle/OfflineBattleComponent.vue | 108 +- .../battle/OnlineBattleComponent.vue | 320 ----- src/renderer/components/battle/Playerlist.vue | 8 +- .../components/battle/ReplayPreview.vue | 30 +- .../components/battle/TeamComponent.vue | 30 +- src/renderer/components/common/Modal.vue | 8 +- src/renderer/components/common/Panel.vue | 8 +- .../controls/DownloadContentButton.vue | 23 + .../components/maps/MapBattlePreview.vue | 237 ++++ .../components/maps/MapListComponent.vue | 28 +- .../components/maps/MapOverviewCard.vue | 30 +- src/renderer/components/maps/MapPreview.vue | 198 --- .../components/maps/MapSimplePreview.vue | 64 + .../components/maps/ReplayPreviewMap.vue | 164 +++ src/renderer/components/misc/DebugSidebar.vue | 5 - src/renderer/components/misc/NewsTile.vue | 4 +- src/renderer/components/misc/Preloader.vue | 27 +- src/renderer/components/navbar/Downloads.vue | 9 +- src/renderer/composables/prompt.ts | 0 .../composables/useImageBlobUrlCache.ts | 13 +- src/renderer/directives/vSetPlayerColor.ts | 26 + src/renderer/directives/vStartBox.ts | 47 + src/renderer/directives/vStartPos.ts | 64 + src/renderer/index.ts | 9 +- src/renderer/shims.d.ts | 15 - src/renderer/store/battle.store.ts | 82 +- src/renderer/store/db.ts | 47 +- src/renderer/store/downloads.store.ts | 34 +- src/renderer/store/game.store.ts | 25 +- src/renderer/store/maps.store.ts | 50 +- src/renderer/store/stores.ts | 7 +- src/renderer/styles/_utils.scss | 6 + src/renderer/typed-router.d.ts | 1 - src/renderer/utils/start-boxes.ts | 11 +- src/renderer/utils/temp.ts | 24 - src/renderer/views/debug/controls.vue | 6 +- src/renderer/views/debug/pixi-map.vue | 75 -- src/renderer/views/library/maps/[id].vue | 66 +- src/renderer/views/library/maps/index.vue | 2 +- src/renderer/views/library/replays.vue | 40 +- src/renderer/views/login.vue | 5 +- src/renderer/views/multiplayer/battle.vue | 4 +- src/renderer/views/singleplayer/custom.vue | 4 +- src/renderer/views/singleplayer/scenarios.vue | 7 +- vendor/jaz-ts-utils/math.ts | 7 - vendor/jaz-ts-utils/object.ts | 103 -- vendor/jaz-ts-utils/types.ts | 43 - vendor/map-parser/map-model.ts | 91 +- vendor/map-parser/parse-dxt.ts | 147 --- vendor/map-parser/spring-map-parser.ts | 601 --------- vendor/map-parser/ultrasimple-map-parser.ts | 47 +- vendor/map-parser/utex.dds.js | 782 ----------- vite.main.config.mts | 2 +- 93 files changed, 1712 insertions(+), 4503 deletions(-) create mode 100644 src/main/content/maps/map-image-worker.ts create mode 100644 src/main/content/maps/map-image.ts create mode 100644 src/main/content/maps/online-map.ts delete mode 100644 src/main/content/maps/parse-map-worker.ts delete mode 100644 src/main/content/maps/parse-map.ts create mode 100644 src/renderer/assets/images/factions/armada_faction.png create mode 100644 src/renderer/assets/images/factions/cortex_faction.png create mode 100644 src/renderer/assets/images/factions/unknown_faction.png create mode 100644 src/renderer/assets/images/icons/player.png create mode 100644 src/renderer/assets/images/icons/spectator.png create mode 100644 src/renderer/components/battle/GameModeComponent.vue delete mode 100644 src/renderer/components/battle/OnlineBattleComponent.vue create mode 100644 src/renderer/components/controls/DownloadContentButton.vue create mode 100644 src/renderer/components/maps/MapBattlePreview.vue delete mode 100644 src/renderer/components/maps/MapPreview.vue create mode 100644 src/renderer/components/maps/MapSimplePreview.vue create mode 100644 src/renderer/components/maps/ReplayPreviewMap.vue delete mode 100644 src/renderer/composables/prompt.ts create mode 100644 src/renderer/directives/vSetPlayerColor.ts create mode 100644 src/renderer/directives/vStartBox.ts create mode 100644 src/renderer/directives/vStartPos.ts delete mode 100644 src/renderer/shims.d.ts delete mode 100644 src/renderer/utils/temp.ts delete mode 100644 src/renderer/views/debug/pixi-map.vue delete mode 100644 vendor/jaz-ts-utils/math.ts delete mode 100644 vendor/jaz-ts-utils/types.ts delete mode 100644 vendor/map-parser/parse-dxt.ts delete mode 100644 vendor/map-parser/spring-map-parser.ts delete mode 100644 vendor/map-parser/utex.dds.js diff --git a/package-lock.json b/package-lock.json index c17abe85..f4c62f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,11 @@ "dependencies": { "@extractus/feed-extractor": "^7.1.3", "@iconify-icons/mdi": "^1.2.48", - "@iconify/json": "^2.2.264", + "@iconify/json": "^2.2.266", "@iconify/vue": "^4.1.2", "@octokit/rest": "^21.0.2", - "@pixi/unsafe-eval": "^7.4.2", "@sinclair/typebox": "^0.33.17", - "@vueuse/core": "^11.1.0", + "@vueuse/core": "^11.2.0", "7zip-min": "^1.4.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -29,18 +28,12 @@ "flag-icons": "^7.2.3", "glob-promise": "^6.0.7", "howler": "^2.2.4", - "jimp": "^1.6.0", "luaparse": "^0.3.1", "marked": "^14.1.3", - "node-stream-zip": "^1.15.0", "pino": "^9.5.0", "pino-pretty": "^11.3.0", - "pixi.js": "^8.5.2", "primeicons": "^6.0.1", "primevue": "3.23.0", - "remove": "^0.1.5", - "tga": "^1.0.7", - "uuid": "^10.0.0", "vue-i18n": "^10.0.4", "vue-router": "^4.4.5" }, @@ -61,27 +54,26 @@ "@types/howler": "^2.2.12", "@types/luaparse": "^0.2.12", "@types/marked": "^6.0.0", - "@types/node": "^20.17.1", - "@types/uuid": "^10.0.0", + "@types/node": "^20.17.4", "@vitejs/plugin-vue": "^5.1.4", "cross-env": "^7.0.3", - "electron": "^33.0.1", + "electron": "33.0.1", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-unused-imports": "^4.1.4", - "eslint-plugin-vue": "^9.29.1", + "eslint-plugin-vue": "^9.30.0", "globals": "^15.11.0", "prettier": "^3.3.3", - "sass": "^1.80.4", + "sass": "^1.80.5", "ts-node": "^10.9.2", "type-fest": "^4.26.1", "typescript": "^5.6.3", - "typescript-eslint": "^8.11.0", + "typescript-eslint": "^8.12.2", "unplugin-vue-router": "^0.10.8", "vite": "^5.4.10", "vite-plugin-static-copy": "^2.0.0", - "vitest": "^2.1.3", - "vue-tsc": "^2.1.6" + "vitest": "^2.1.4", + "vue-tsc": "^2.1.10" }, "engines": { "node": "20.18.0" @@ -707,7 +699,7 @@ "node_modules/@electron/node-gyp": { "version": "10.2.0-electron.1", "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", - "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", + "integrity": "sha512-CrYo6TntjpoMO1SHjl5Pa/JoUsECNqNdB7Kx49WLQpWzPw53eEITJ2Hs9fh/ryUYDn4pxZz11StaBYBrLFJdqg==", "dev": true, "license": "MIT", "dependencies": { @@ -1692,418 +1684,6 @@ "url": "https://github.com/sponsors/kazupon" } }, - "node_modules/@jimp/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", - "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", - "license": "MIT", - "dependencies": { - "@jimp/file-ops": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "await-to-js": "^3.0.0", - "exif-parser": "^0.1.12", - "file-type": "^16.0.0", - "mime": "3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/diff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", - "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", - "license": "MIT", - "dependencies": { - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "pixelmatch": "^5.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/file-ops": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", - "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-bmp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", - "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "bmp-ts": "^1.0.9" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-gif": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", - "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "gifwrap": "^0.10.1", - "omggif": "^1.0.10" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-jpeg": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", - "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "jpeg-js": "^0.4.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-png": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", - "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "pngjs": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-tiff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", - "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "utif2": "^4.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", - "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", - "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", - "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", - "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "tinycolor2": "^1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", - "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", - "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", - "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", - "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", - "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", - "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", - "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-hash": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", - "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "any-base": "^1.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", - "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", - "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/types": "1.6.0", - "parse-bmfont-ascii": "^1.0.6", - "parse-bmfont-binary": "^1.0.6", - "parse-bmfont-xml": "^1.1.6", - "simple-xml-to-json": "^1.2.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-quantize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", - "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", - "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", - "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", - "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", - "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", - "license": "MIT", - "dependencies": { - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2563,6 +2143,7 @@ "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -2849,6 +2430,7 @@ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, "license": "Apache-2.0", + "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -2856,120 +2438,6 @@ "node": ">=0.10" } }, - "node_modules/@pixi/color": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.2.tgz", - "integrity": "sha512-av1LOvhHsiaW8+T4n/FgnOKHby55/w7VcA1HzPIHRBtEcsmxvSCDanT1HU2LslNhrxLPzyVx18nlmalOyt5OBg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pixi/colord": "^2.9.6" - } - }, - "node_modules/@pixi/colord": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", - "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", - "license": "MIT" - }, - "node_modules/@pixi/constants": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.2.tgz", - "integrity": "sha512-N9vn6Wpz5WIQg7ugUg2+SdqD2u2+NM0QthE8YzLJ4tLH2Iz+/TrnPKUJzeyIqbg3sxJG5ZpGGPiacqIBpy1KyA==", - "license": "MIT", - "peer": true - }, - "node_modules/@pixi/core": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.2.tgz", - "integrity": "sha512-UbMtgSEnyCOFPzbE6ThB9qopXxbZ5GCof2ArB4FXOC5Xi/83MOIIYg5kf5M8689C5HJMhg2SrJu3xLKppF+CMg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pixi/color": "7.4.2", - "@pixi/constants": "7.4.2", - "@pixi/extensions": "7.4.2", - "@pixi/math": "7.4.2", - "@pixi/runner": "7.4.2", - "@pixi/settings": "7.4.2", - "@pixi/ticker": "7.4.2", - "@pixi/utils": "7.4.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/pixijs" - } - }, - "node_modules/@pixi/extensions": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.2.tgz", - "integrity": "sha512-Hmx2+O0yZ8XIvgomHM9GZEGcy9S9Dd8flmtOK5Aa3fXs/8v7xD08+ANQpN9ZqWU2Xs+C6UBlpqlt2BWALvKKKA==", - "license": "MIT", - "peer": true - }, - "node_modules/@pixi/math": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.2.tgz", - "integrity": "sha512-7jHmCQoYk6e0rfSKjdNFOPl0wCcdgoraxgteXJTTHv3r0bMNx2pHD9FJ0VvocEUG7XHfj55O3+u7yItOAx0JaQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@pixi/runner": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.2.tgz", - "integrity": "sha512-LPBpwym4vdyyDY5ucF4INQccaGyxztERyLTY1YN6aqJyyMmnc7iqXlIKt+a0euMBtNoLoxy6MWMvIuZj0JfFPA==", - "license": "MIT", - "peer": true - }, - "node_modules/@pixi/settings": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.2.tgz", - "integrity": "sha512-pMN+L6aWgvUbwhFIL/BTHKe2ShYGPZ8h9wlVBnFHMtUcJcFLMF1B3lzuvCayZRepOphs6RY0TqvnDvVb585JhQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pixi/constants": "7.4.2", - "@types/css-font-loading-module": "^0.0.12", - "ismobilejs": "^1.1.0" - } - }, - "node_modules/@pixi/ticker": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.2.tgz", - "integrity": "sha512-cAvxCh/KI6IW4m3tp2b+GQIf+DoSj9NNmPJmsOeEJ7LzvruG8Ps7SKI6CdjQob5WbceL1apBTDbqZ/f77hFDiQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pixi/extensions": "7.4.2", - "@pixi/settings": "7.4.2", - "@pixi/utils": "7.4.2" - } - }, - "node_modules/@pixi/unsafe-eval": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/unsafe-eval/-/unsafe-eval-7.4.2.tgz", - "integrity": "sha512-45LM2mpqziNTeIORjgJl042CyssfZ17gfHHWcPZIZIGtiXSBPBy+mKvtHh5PraG0wBxAk/Bcr+nCYtAl8yuwgw==", - "license": "MIT", - "peerDependencies": { - "@pixi/core": "7.4.2" - } - }, - "node_modules/@pixi/utils": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.2.tgz", - "integrity": "sha512-aU/itcyMC4TxFbmdngmak6ey4kC5c16Y5ntIYob9QnjNAfD/7GTsYIBnP6FqEAyO1eq0MjkAALxdONuay1BG3g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pixi/color": "7.4.2", - "@pixi/constants": "7.4.2", - "@pixi/settings": "7.4.2", - "@types/earcut": "^2.1.0", - "earcut": "^2.2.4", - "eventemitter3": "^4.0.0", - "url": "^0.11.0" - } - }, "node_modules/@rollup/pluginutils": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", @@ -3290,12 +2758,6 @@ "node": ">=10" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3364,12 +2826,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", - "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", - "license": "MIT" - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -3380,12 +2836,6 @@ "@types/trusted-types": "*" } }, - "node_modules/@types/earcut": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", - "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -3500,13 +2950,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -4205,16 +3648,11 @@ } } }, - "node_modules/@webgpu/types": { - "version": "0.1.49", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.49.tgz", - "integrity": "sha512-NMmS8/DofhH/IFeW+876XrHVWel+J/vdcFCHLDqeJgkH9x0DeiwjVd8LcBdaxdG/T7Rf8VUAYsA8X1efMzLjRQ==", - "license": "BSD-3-Clause" - }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4442,12 +3880,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "license": "MIT" - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4614,15 +4046,6 @@ "node": ">=0.8" } }, - "node_modules/await-to-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", - "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/axios": { "version": "1.7.7", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", @@ -4707,12 +4130,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bmp-ts": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", - "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -4920,6 +4337,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -4962,18 +4380,6 @@ "node": ">=12" } }, - "node_modules/chainsaw": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.0.9.tgz", - "integrity": "sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA==", - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5517,6 +4923,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -5630,12 +5037,6 @@ "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", "license": "(MPL-2.0 OR Apache-2.0)" }, - "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "license": "ISC" - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6375,6 +5776,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" @@ -6387,6 +5789,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6755,11 +6158,11 @@ } }, "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT", - "peer": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", @@ -6875,11 +6278,6 @@ "which": "bin/which" } }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" - }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -7139,23 +6537,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "license": "MIT", - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", @@ -7398,6 +6779,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7466,6 +6848,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7530,16 +6913,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gifwrap": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", - "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -7709,6 +7082,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" @@ -7771,6 +7145,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -7783,6 +7158,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7795,6 +7171,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7803,22 +7180,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hashish": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz", - "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.2.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8005,21 +7371,6 @@ "node": ">= 4" } }, - "node_modules/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "license": "MIT", - "dependencies": { - "@types/node": "16.9.1" - } - }, - "node_modules/image-q/node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "license": "MIT" - }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -8281,50 +7632,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ismobilejs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", - "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", - "license": "MIT" - }, - "node_modules/jimp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", - "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/diff": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-gif": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-blur": "1.6.0", - "@jimp/plugin-circle": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-contain": "1.6.0", - "@jimp/plugin-cover": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-displace": "1.6.0", - "@jimp/plugin-dither": "1.6.0", - "@jimp/plugin-fisheye": "1.6.0", - "@jimp/plugin-flip": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/plugin-mask": "1.6.0", - "@jimp/plugin-print": "1.6.0", - "@jimp/plugin-quantize": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/plugin-rotate": "1.6.0", - "@jimp/plugin-threshold": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -8334,12 +7641,6 @@ "node": ">=10" } }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "license": "BSD-3-Clause" - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -8466,13 +7767,6 @@ "node": ">=16.0.0" } }, - "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, "node_modules/load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -8782,15 +8076,16 @@ } }, "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=10.0.0" + "node": ">=4" } }, "node_modules/mime-db": { @@ -9053,7 +8348,8 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/node-api-version": { "version": "0.2.0", @@ -9085,19 +8381,6 @@ } } }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, "node_modules/nopt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", @@ -9200,6 +8483,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9219,12 +8503,6 @@ "node": ">= 0.4" } }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "license": "MIT" - }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -9462,12 +8740,6 @@ "node": ">=6" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9494,28 +8766,6 @@ "node": ">=0.10.0" } }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "license": "MIT" - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "license": "MIT" - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", - "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", - "license": "MIT", - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.5.0" - } - }, "node_modules/parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -9539,12 +8789,6 @@ "node": ">=0.10.0" } }, - "node_modules/parse-svg-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", - "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", - "license": "MIT" - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -9650,19 +8894,6 @@ "url": "https://github.com/sponsors/jet2jet" } }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -9801,50 +9032,6 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "license": "ISC", - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/pixi.js": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.5.2.tgz", - "integrity": "sha512-TOt9g8ifOj4R9DN9ST1M8t2nvnuhr5oWL5YW9ywFLbnOVgFMDcEz+Xek5Mo8Xr64D+QU3qre3IFgreBlsHxTNw==", - "license": "MIT", - "dependencies": { - "@pixi/colord": "^2.9.6", - "@types/css-font-loading-module": "^0.0.12", - "@types/earcut": "^2.1.4", - "@webgpu/types": "^0.1.40", - "@xmldom/xmldom": "^0.8.10", - "earcut": "^2.2.4", - "eventemitter3": "^5.0.1", - "ismobilejs": "^1.1.1", - "parse-svg-path": "^0.1.2" - } - }, - "node_modules/pixi.js/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -9941,15 +9128,6 @@ "node": ">=10.4.0" } }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", @@ -10147,16 +9325,20 @@ } }, "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=6" + } }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -10353,6 +9535,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -10363,22 +9546,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/readdirp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", @@ -10414,15 +9581,6 @@ "node": ">= 10.13.0" } }, - "node_modules/remove": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/remove/-/remove-0.1.5.tgz", - "integrity": "sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==", - "license": "MIT", - "dependencies": { - "seq": ">= 0.3.5" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10553,12 +9711,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restructure": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", - "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==", - "license": "MIT" - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -10744,13 +9896,12 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.80.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.5.tgz", - "integrity": "sha512-TQd2aoQl/+zsxRMEDSxVdpPIqeq9UFc6pr7PzkugiTx3VYCFPUaa3P4RrBQsqok4PO200Vkz0vXQBNlg7W907g==", + "version": "1.80.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz", + "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", "dev": true, "license": "MIT", "dependencies": { - "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" @@ -10760,14 +9911,11 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" - }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", @@ -10854,32 +10002,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/seq": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/seq/-/seq-0.3.5.tgz", - "integrity": "sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g==", - "license": "MIT/X11", - "dependencies": { - "chainsaw": ">=0.0.7 <0.1", - "hashish": ">=0.0.2 <0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -10931,6 +10053,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -10978,6 +10101,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -11006,15 +10130,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-xml-to-json": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", - "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", - "license": "MIT", - "engines": { - "node": ">=20.12.2" - } - }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -11318,23 +10433,6 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "license": "MIT" }, - "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -11483,34 +10581,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tga": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", - "integrity": "sha512-GFVJwov5aJTMgh8U1QfaRheIELXo+dYc1qYIvQEIqZX4n+S6Fj/SDWsdbelHt7WP08xOR6W1z5aJQ+Ilh5gIeA==", - "license": "MIT", - "dependencies": { - "debug": "^2.6.1", - "restructure": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/tga/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/tga/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -11535,12 +10605,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyexec": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", @@ -11623,38 +10687,12 @@ "node": ">=0.6" } }, - "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -11993,30 +11031,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", - "license": "MIT", - "peer": true, - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/username": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/username/-/username-5.1.0.tgz", @@ -12031,19 +11045,11 @@ "node": ">=8" } }, - "node_modules/utif2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", - "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", - "license": "MIT", - "dependencies": { - "pako": "^1.0.11" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -12056,19 +11062,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -12630,34 +11623,6 @@ "node": ">=12" } }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "license": "MIT" - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -12936,15 +11901,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index ae6feeb2..d9a5712b 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,14 @@ "node": "20.18.0" }, "dependencies": { + "7zip-min": "^1.4.5", "@extractus/feed-extractor": "^7.1.3", "@iconify-icons/mdi": "^1.2.48", - "@iconify/json": "^2.2.264", + "@iconify/json": "^2.2.266", "@iconify/vue": "^4.1.2", "@octokit/rest": "^21.0.2", - "@pixi/unsafe-eval": "^7.4.2", "@sinclair/typebox": "^0.33.17", - "@vueuse/core": "^11.1.0", - "7zip-min": "^1.4.5", + "@vueuse/core": "^11.2.0", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "axios": "^1.7.7", @@ -45,18 +44,12 @@ "flag-icons": "^7.2.3", "glob-promise": "^6.0.7", "howler": "^2.2.4", - "jimp": "^1.6.0", "luaparse": "^0.3.1", "marked": "^14.1.3", - "node-stream-zip": "^1.15.0", "pino": "^9.5.0", "pino-pretty": "^11.3.0", - "pixi.js": "^8.5.2", "primeicons": "^6.0.1", "primevue": "3.23.0", - "remove": "^0.1.5", - "tga": "^1.0.7", - "uuid": "^10.0.0", "vue-i18n": "^10.0.4", "vue-router": "^4.4.5" }, @@ -77,26 +70,25 @@ "@types/howler": "^2.2.12", "@types/luaparse": "^0.2.12", "@types/marked": "^6.0.0", - "@types/node": "^20.17.1", - "@types/uuid": "^10.0.0", + "@types/node": "^20.17.4", "@vitejs/plugin-vue": "^5.1.4", "cross-env": "^7.0.3", - "electron": "^33.0.1", + "electron": "33.0.1", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-unused-imports": "^4.1.4", - "eslint-plugin-vue": "^9.29.1", + "eslint-plugin-vue": "^9.30.0", "globals": "^15.11.0", "prettier": "^3.3.3", - "sass": "^1.80.4", + "sass": "^1.80.5", "ts-node": "^10.9.2", "type-fest": "^4.26.1", "typescript": "^5.6.3", - "typescript-eslint": "^8.11.0", + "typescript-eslint": "^8.12.2", "unplugin-vue-router": "^0.10.8", "vite": "^5.4.10", "vite-plugin-static-copy": "^2.0.0", - "vitest": "^2.1.3", - "vue-tsc": "^2.1.6" + "vitest": "^2.1.4", + "vue-tsc": "^2.1.10" } } diff --git a/src/main/content/game/game-content.ts b/src/main/content/game/game-content.ts index a4598cc3..37315d49 100644 --- a/src/main/content/game/game-content.ts +++ b/src/main/content/game/game-content.ts @@ -80,8 +80,10 @@ export class GameContentAPI extends PrDownloaderAPI { for (const packageFile of packages) { const packageMd5 = packageFile.replace(".sdp", ""); const gameVersion = this.packageGameVersionLookup[packageMd5]; + const luaOptionSections = await this.getGameOptions(packageMd5); + const ais = await this.getAis(packageMd5); if (gameVersion) { - this.installedVersions.push({ gameVersion, packageMd5 }); + this.installedVersions.push({ gameVersion, packageMd5, luaOptionSections, ais }); } } this.sortVersions(); @@ -117,13 +119,16 @@ export class GameContentAPI extends PrDownloaderAPI { }); } - public async getGameOptions(version: string): Promise { - const gameVersion = this.installedVersions.find((installedVersion) => installedVersion.gameVersion === version); - // TODO: cache per session - const gameFiles = await this.getGameFiles(gameVersion.packageMd5, "modoptions.lua", true); - const gameOptionsLua = gameFiles[0].data; - // TODO maybe send ais as well - return parseLuaOptions(gameOptionsLua); + protected async getGameOptions(packageMd5: string): Promise { + const gameFiles = await this.getGameFiles(packageMd5, "modoptions.lua", true); + const modoptions = gameFiles[0].data; + return parseLuaOptions(modoptions); + } + + protected async getAis(packageMd5: string): Promise { + const gameFiles = await this.getGameFiles(packageMd5, "luaai.lua", true); + const luaai = gameFiles[0].data; + return this.parseAis(luaai); } public async getScenarios(): Promise { @@ -259,14 +264,13 @@ export class GameContentAPI extends PrDownloaderAPI { } protected async addGame(gameVersion: string) { - //TODO reimplement ais lookup now that its no longer in the GameVersion object - // const luaAiFile = (await this.getGameFiles({ md5: packageMd5 }, "luaai.lua", true))[0]; - // const ais = await this.parseAis(luaAiFile.data); if (gameVersion === "byar:test") { await this.scanPackagesDir(); } else { const packageMd5 = this.gameVersionPackageLookup[gameVersion]; - this.installedVersions.push({ gameVersion, packageMd5 }); + const luaOptionSections = await this.getGameOptions(packageMd5); + const ais = await this.getAis(packageMd5); + this.installedVersions.push({ gameVersion, packageMd5, luaOptionSections, ais }); this.sortVersions(); } } diff --git a/src/main/content/game/game-version.ts b/src/main/content/game/game-version.ts index e94c7530..d7832e55 100644 --- a/src/main/content/game/game-version.ts +++ b/src/main/content/game/game-version.ts @@ -1,6 +1,10 @@ +import { LuaOptionSection } from "@main/content/game/lua-options"; + export type GameVersion = { gameVersion: string; packageMd5: string; + luaOptionSections: LuaOptionSection[]; + ais: GameAI[]; }; export interface GameAI { diff --git a/src/main/content/maps/map-content.ts b/src/main/content/maps/map-content.ts index 752f4817..e88a960a 100644 --- a/src/main/content/maps/map-content.ts +++ b/src/main/content/maps/map-content.ts @@ -4,10 +4,8 @@ import * as path from "path"; import { MapData } from "@main/content/maps/map-data"; import { logger } from "@main/utils/logger"; import { Signal } from "$/jaz-ts-utils/signal"; -import { delay } from "$/jaz-ts-utils/delay"; import { PrDownloaderAPI } from "@main/content/pr-downloader"; import { CONTENT_PATH } from "@main/config/app"; -import { asyncParseMap } from "@main/content/maps/parse-map"; import chokidar from "chokidar"; import { UltraSimpleMapParser } from "$/map-parser/ultrasimple-map-parser"; @@ -17,11 +15,10 @@ const log = logger("map-content.ts"); * @todo replace queue method with syncMapCache function once prd returns map file name */ export class MapContentAPI extends PrDownloaderAPI { - public mapNameFileNameLookup: { [scriptName: string]: string } = {}; + public mapNameFileNameLookup: { [springName: string]: string } = {}; public fileNameMapNameLookup: { [fileName: string]: string } = {}; - public readonly onMapCachingStarted: Signal = new Signal(); - public readonly onMapCached: Signal = new Signal(); + public readonly onMapAdded: Signal = new Signal(); public readonly onMapDeleted: Signal = new Signal(); protected readonly mapsDir = path.join(CONTENT_PATH, "maps"); @@ -31,7 +28,6 @@ export class MapContentAPI extends PrDownloaderAPI { public override async init() { await fs.promises.mkdir(this.mapsDir, { recursive: true }); this.initLookupMaps(); - this.startCacheMapConsumer(); this.startWatchingMapFolder(); return super.init(); } @@ -41,10 +37,15 @@ export class MapContentAPI extends PrDownloaderAPI { const sd7filePaths = filePaths.filter((path) => path.endsWith(".sd7")); log.debug(`Found ${sd7filePaths.length} maps`); for (const filePath of sd7filePaths) { - const mapName = await this.getMapNameFromFile(filePath); - const fileName = path.basename(filePath); - this.mapNameFileNameLookup[mapName] = fileName; - this.fileNameMapNameLookup[fileName] = mapName; + try { + const mapName = await this.getMapNameFromFile(filePath); + const fileName = path.basename(filePath); + this.mapNameFileNameLookup[mapName] = fileName; + this.fileNameMapNameLookup[fileName] = mapName; + } catch (err) { + log.error(`File may be corrupted, removing ${filePath}: ${err}`); + fs.promises.rm(path.join(this.mapsDir, filePath)); + } } log.info(`Found ${Object.keys(this.mapNameFileNameLookup).length} maps`); } @@ -52,7 +53,7 @@ export class MapContentAPI extends PrDownloaderAPI { protected async getMapNameFromFile(file: string) { const ultraSimpleMapParser = new UltraSimpleMapParser(); const parsedMap = await ultraSimpleMapParser.parseMap(path.join(this.mapsDir, file)); - return parsedMap.scriptName; + return parsedMap.springName; } protected startWatchingMapFolder() { @@ -73,7 +74,7 @@ export class MapContentAPI extends PrDownloaderAPI { this.mapNameFileNameLookup[mapName] = filename; this.fileNameMapNameLookup[filename] = mapName; }); - this.queueMapsToCache([filename]); + this.onMapAdded.dispatch(filename); }) .on("unlink", (filepath) => { if (!filepath.endsWith("sd7")) { @@ -86,26 +87,26 @@ export class MapContentAPI extends PrDownloaderAPI { }); } - public isVersionInstalled(scriptName: string): boolean { - return this.mapNameFileNameLookup[scriptName] !== undefined; + public isVersionInstalled(springName: string): boolean { + return this.mapNameFileNameLookup[springName] !== undefined; } - public async downloadMaps(scriptNames: string[]) { - return Promise.all(scriptNames.map((scriptName) => this.downloadMap(scriptName))); + public async downloadMaps(springNames: string[]) { + return Promise.all(springNames.map((springName) => this.downloadMap(springName))); } - public async downloadMap(scriptName: string) { - if (this.isVersionInstalled(scriptName)) return; - if (this.currentDownloads.some((download) => download.name === scriptName)) { + public async downloadMap(springName: string) { + if (this.isVersionInstalled(springName)) return; + if (this.currentDownloads.some((download) => download.name === springName)) { return await new Promise((resolve) => { this.onDownloadComplete.addOnce((mapData) => { - if (mapData.name === scriptName) { + if (mapData.name === springName) { resolve(); } }); }); } - const downloadInfo = await this.downloadContent("map", scriptName); + const downloadInfo = await this.downloadContent("map", springName); this.onDownloadComplete.dispatch(downloadInfo); } @@ -113,83 +114,16 @@ export class MapContentAPI extends PrDownloaderAPI { throw new Error("Method not implemented."); } - //Method to sync the cache with the maps folder, if the folder doesnt have the map, download it. If the folder has a map that is not in the cache, cache it. - public async sync(maps: { scriptName: string; fileName: string }[]) { - const existingFiles = await this.scanFolderForMaps(); - const mapsToDownload = maps.filter((map) => !existingFiles.includes(map.fileName)); - mapsToDownload.forEach((map) => this.onMapDeleted.dispatch(map.fileName)); - this.downloadMaps(mapsToDownload.map((map) => map.scriptName)); - const mapFileNames = maps.map((map) => map.fileName); - const mapsToCache = existingFiles.filter((map) => !mapFileNames.includes(map)); - this.queueMapsToCache(mapsToCache); - } - public async scanFolderForMaps() { let mapFiles = await fs.promises.readdir(this.mapsDir); mapFiles = mapFiles.filter((mapFile) => mapFile.endsWith("sd7")); return mapFiles; } - protected async queueMapsToCache(filenames?: string[]) { - let mapFiles = filenames; - if (!filenames) { - mapFiles = await this.scanFolderForMaps(); - } - for (const mapFileToCache of mapFiles) { - this.mapCacheQueue.add(mapFileToCache); - this.onMapCachingStarted.dispatch(mapFileToCache); - } - } - public async uninstallVersion(version: MapData) { - const mapFile = path.join(this.mapsDir, version.fileName); + const mapFile = path.join(this.mapsDir, version.filename); await fs.promises.rm(mapFile, { force: true, recursive: true }); - log.debug(`Map removed: ${version.scriptName}`); - } - - protected async startCacheMapConsumer() { - if (this.cachingMaps) { - log.warn("Don't call cacheMaps more than once"); - return; - } - this.cachingMaps = true; - - while (true) { - const [mapToCache] = this.mapCacheQueue; - if (mapToCache) { - await this.cacheMap(mapToCache); - } else { - await delay(500); - } - } - } - - protected async cacheMap(mapFileName: string) { - try { - log.debug(`Caching: ${mapFileName}`); - console.time(`Cached: ${mapFileName}`); - const mapPath = path.join(this.mapsDir, mapFileName); - log.debug(`Parsing map asynchronously: ${mapFileName}`); - const mapData = await asyncParseMap(mapPath); - log.debug(`Parsed map: ${mapFileName}`); - this.onMapCached.dispatch(mapData); - console.timeEnd(`Cached: ${mapFileName}`); - } catch (err) { - log.error(`Error parsing map: ${mapFileName}`, err); - log.error(err); - //TODO emit error signal - } - this.mapCacheQueue.delete(mapFileName); - } - - protected mapCached(mapName: string) { - return new Promise((resolve) => { - this.onMapCached.addOnce((map) => { - if (map.scriptName === mapName) { - resolve(map); - } - }); - }); + log.debug(`Map removed: ${version.springName}`); } } diff --git a/src/main/content/maps/map-data.ts b/src/main/content/maps/map-data.ts index 79cd6b63..d7ba76a8 100644 --- a/src/main/content/maps/map-data.ts +++ b/src/main/content/maps/map-data.ts @@ -1,43 +1,9 @@ -import type { DeepPartial } from "$/jaz-ts-utils/types"; -import { MapInfo } from "$/map-parser/map-model"; +import { MapMetadata } from "@main/content/maps/online-map"; -export type MapData = { - scriptName: string; - fileName: string; - friendlyName: string; - description: string | null; - mapHardness: number; - gravity: number; - tidalStrength: number; - maxMetal: number; - extractorRadius: number; - minWind: number; - maxWind: number; - startPositions: Array<{ - x: number; - z: number; - }> | null; - width: number; - height: number; - minDepth: number; - maxDepth: number; - mapInfo: DeepPartial | null; - lastLaunched: Date; - images: { - texture: string; - height: string; - metal: string; - type: string; +export type MapData = MapMetadata & { + imagesBlob?: { + preview?: Blob; }; + isInstalled?: boolean; + isDownloading?: boolean; }; - -export function mapFileNameToFriendlyName(fileName: string) { - const friendlyName = fileName - .replace(/\.sd7$/, "") - .replaceAll("_", " ") - .toLowerCase() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.substring(1)) - .join(" "); - return friendlyName; -} diff --git a/src/main/content/maps/map-image-worker.ts b/src/main/content/maps/map-image-worker.ts new file mode 100644 index 00000000..ec375354 --- /dev/null +++ b/src/main/content/maps/map-image-worker.ts @@ -0,0 +1,16 @@ +import { isMainThread, parentPort } from "worker_threads"; + +if (isMainThread) { + throw new Error("This script should be run in worker thread."); +} else { + // listen to messages from the main thread + parentPort.on("message", async (imageSource: string) => { + try { + const response = await fetch(imageSource); + const arrayBuffer = await response.arrayBuffer(); + parentPort.postMessage({ imageSource, arrayBuffer }); + } catch (error) { + console.error(error); + } + }); +} diff --git a/src/main/content/maps/map-image.ts b/src/main/content/maps/map-image.ts new file mode 100644 index 00000000..3bc27072 --- /dev/null +++ b/src/main/content/maps/map-image.ts @@ -0,0 +1,34 @@ +import path from "path"; +import { Worker } from "worker_threads"; + +const worker = new Worker(path.join(__dirname, "map-image-worker.cjs")); + +const jobs = new Map< + string, + { + resolve: (value: ArrayBuffer) => void; + reject: (reason?: string) => void; + } +>(); +const promises = new Map>(); + +worker.on("message", ({ imageSource, arrayBuffer }) => { + const promiseHandles = jobs.get(imageSource); + promiseHandles.resolve(arrayBuffer); + jobs.delete(imageSource); + promises.delete(imageSource); +}); +// worker.on("error", reject); +// worker.on("exit", (code) => { +// if (code !== 0) reject(new Error(`map-image-worker stopped with exit code ${code}`)); +// }); + +export function fetchMapImages(imageSource: string): Promise { + if (promises.has(imageSource)) return promises.get(imageSource); + const promise = new Promise((resolve, reject) => { + jobs.set(imageSource, { resolve, reject }); + worker.postMessage(imageSource); + }); + promises.set(imageSource, promise); + return promise; +} diff --git a/src/main/content/maps/online-map.ts b/src/main/content/maps/online-map.ts new file mode 100644 index 00000000..eda5744f --- /dev/null +++ b/src/main/content/maps/online-map.ts @@ -0,0 +1,44 @@ +// model for https://maps-metadata.beyondallreason.dev/latest/lobby_maps.validated.json + +export interface MapMetadata { + author: string; + certified: boolean; + description: string; + displayName: string; + filename: string; + images: { + preview: string; + }; + mapHeight: number; + mapLists: string[]; + mapWidth: number; + playerCountMax: number; + playerCountMin: number; + springName: string; + startboxesSet: Startbox[]; + startPos?: { + positions: Record; + team: Team[]; + }; + tags: string[]; + terrain: string[]; + tidalStrength: number; + windMax: number; + windMin: number; +} + +export interface Startbox { + maxPlayersPerStartbox: number; + startboxes: { poly: { x: number; y: number }[] }[]; +} + +export interface Team { + playersPerTeam: number; + sides: { + starts: { + role: string; + spawnPoint: string; + }[]; + }[]; + teamCount: number; +} diff --git a/src/main/content/maps/parse-map-worker.ts b/src/main/content/maps/parse-map-worker.ts deleted file mode 100644 index 09914742..00000000 --- a/src/main/content/maps/parse-map-worker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { parseMap } from "@main/content/maps/parse-map"; -import { isMainThread, parentPort, workerData } from "worker_threads"; - -if (isMainThread) { - throw new Error("This script should be run in worker thread."); -} else { - const { mapPath } = workerData; - parseMap(mapPath).then((data) => { - parentPort.postMessage(data); - }); -} diff --git a/src/main/content/maps/parse-map.ts b/src/main/content/maps/parse-map.ts deleted file mode 100644 index cdb8348d..00000000 --- a/src/main/content/maps/parse-map.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { StartPos } from "$/map-parser/map-model"; -import { MapParser } from "$/map-parser/spring-map-parser"; -import path from "path"; -import { MapData } from "@main/content/maps/map-data"; -import { Worker } from "worker_threads"; -import { MIPMAP_SIZE } from "@main/config/map-parsing"; - -const mapParser = new MapParser({ - mipmapSize: MIPMAP_SIZE, -}); - -export const parseMap = async (mapPath: string) => { - const map = await mapParser.parseMap(mapPath); - - const texture = await map - .textureMap!.scaleToFit({ - h: 1024, - w: 1024, - }) - .getBase64("image/jpeg", { quality: 70 }); - const metal = await map.metalMap!.getBase64("image/jpeg", { quality: 70 }); - const height = await map.heightMap.getBase64("image/jpeg", { quality: 70 }); - const type = await map.typeMap.getBase64("image/jpeg", { quality: 70 }); - - return { - fileName: path.parse(mapPath).base, - scriptName: map.scriptName.trim(), - friendlyName: (map.mapInfo?.name || map.scriptName).trim().replace(/[_-]/g, " "), - description: map.mapInfo?.description || map.smd?.description || null, - mapHardness: map.mapInfo?.maphardness ?? map.smd?.mapHardness, - gravity: map.mapInfo?.gravity ?? map.smd?.gravity, - tidalStrength: map.mapInfo?.tidalStrength ?? map.smd?.tidalStrength, - maxMetal: map.mapInfo?.maxMetal ?? map.smd?.maxMetal, - extractorRadius: map.mapInfo?.extractorRadius ?? map.smd?.extractorRadius, - minWind: map.mapInfo?.atmosphere?.minWind ?? map.smd?.minWind, - maxWind: map.mapInfo?.atmosphere?.maxWind ?? map.smd?.maxWind, - startPositions: (map.mapInfo?.teams?.map((obj) => obj!.startPos) ?? map.smd?.startPositions) as Array, - width: map.smf!.mapWidthUnits * 2, - height: map.smf!.mapHeightUnits * 2, - minDepth: map.minHeight, - maxDepth: map.maxHeight, - mapInfo: map.mapInfo || null, - images: { - texture, - metal, - height, - type, - }, - } as MapData; -}; - -export function asyncParseMap(mapPath: string) { - return new Promise((resolve, reject) => { - const worker = new Worker(path.join(__dirname, "parse-map-worker.cjs"), { - workerData: { mapPath }, - }); - worker.on("message", resolve); - worker.on("error", reject); - worker.on("exit", (code) => { - if (code !== 0) reject(new Error(`parse-map-worker stopped with exit code ${code}`)); - }); - }); -} diff --git a/src/main/content/pr-downloader.ts b/src/main/content/pr-downloader.ts index 264a1faa..29b12c57 100644 --- a/src/main/content/pr-downloader.ts +++ b/src/main/content/pr-downloader.ts @@ -9,7 +9,6 @@ import { engineContentAPI } from "./engine/engine-content"; import { logger } from "@main/utils/logger"; import { CONTENT_PATH } from "@main/config/app"; import { DEFAULT_ENGINE_VERSION } from "@main/config/default-versions"; -import { mapFileNameToFriendlyName } from "@main/content/maps/map-data"; const log = logger("pr-downloader.ts"); @@ -67,7 +66,7 @@ export abstract class PrDownloaderAPI extends AbstractContentAPI { if (!downloadInfo) { downloadInfo = { type, - name: type === "map" ? mapFileNameToFriendlyName(name) : name, // ridiculous hack to somewhat have a match between a file name, a scriptname and a prd progress message + name, currentBytes: 0, totalBytes: progress.totalBytes, }; diff --git a/src/main/content/replays/parse-replay-worker.ts b/src/main/content/replays/parse-replay-worker.ts index 40aa6269..d8fad3f4 100644 --- a/src/main/content/replays/parse-replay-worker.ts +++ b/src/main/content/replays/parse-replay-worker.ts @@ -1,11 +1,55 @@ -import { parseReplay } from "@main/content/replays/parse-replay"; -import { isMainThread, parentPort, workerData } from "worker_threads"; +import { DemoParser } from "$/sdfz-demo-parser"; +import { Replay } from "@main/content/replays/replay"; +import path from "path"; +import { isMainThread, parentPort } from "worker_threads"; if (isMainThread) { throw new Error("This script should be run in worker thread."); } else { - const { replayFilePath } = workerData; - parseReplay(replayFilePath).then((data) => { - parentPort.postMessage(data); + const demoParser = new DemoParser(); + async function parseReplay(replayPath: string) { + const replayData = await demoParser.parseDemo(replayPath); + const numOfPlayers = replayData.info.players.length + replayData.info.ais.length; + let preset: "duel" | "team" | "ffa" | "teamffa" = "duel"; + if (replayData.info.allyTeams.length > 2 && replayData.info.players.some((player) => player.playerId !== player.allyTeamId)) { + preset = "teamffa"; + } else if (replayData.info.allyTeams.length > 2) { + preset = "ffa"; + } else if (numOfPlayers > 2) { + preset = "team"; + } + return { + gameId: replayData.header.gameId, + fileName: path.parse(replayPath).base, + filePath: replayPath, + engineVersion: replayData.info.meta.engine, + gameVersion: replayData.info.meta.game, + mapSpringName: replayData.info.meta.map, + startTime: replayData.info.meta.startTime, + gameDurationMs: replayData.info.meta.durationMs, + gameEndedNormally: replayData.info.meta.winningAllyTeamIds.length > 0 ? 1 : 0, + chatlog: replayData.chatlog || null, + hasBots: replayData.info.ais.length > 0 ? 1 : 0, + preset: preset, + winningTeamId: replayData.info.meta.winningAllyTeamIds[0], + teams: replayData.info.allyTeams, + contenders: [...replayData.info.players, ...replayData.info.ais], + spectators: replayData.info.spectators, + script: replayData.script, + battleSettings: replayData.info.hostSettings, + gameSettings: replayData.info.gameSettings, + mapSettings: replayData.info.mapSettings, + hostSettings: replayData.info.spadsSettings ?? {}, + } as Replay; + } + // listen to messages from the main thread + parentPort.on("message", async (replayFilePath: string) => { + try { + const replay = await parseReplay(replayFilePath); + parentPort.postMessage({ replayFilePath, replay }); + } catch (error) { + parentPort.postMessage({ replayFilePath, undefined, error }); + console.error(error); + } }); } diff --git a/src/main/content/replays/parse-replay.ts b/src/main/content/replays/parse-replay.ts index 9ca8a254..a4c5eaf8 100644 --- a/src/main/content/replays/parse-replay.ts +++ b/src/main/content/replays/parse-replay.ts @@ -1,57 +1,30 @@ -import { DemoParser } from "$/sdfz-demo-parser"; import { Replay } from "@main/content/replays/replay"; import path from "path"; import { Worker } from "worker_threads"; -const demoParser = new DemoParser(); +const worker = new Worker(path.join(__dirname, "parse-replay-worker.cjs")); -export async function parseReplay(replayPath: string) { - const replayData = await demoParser.parseDemo(replayPath); - - const numOfPlayers = replayData.info.players.length + replayData.info.ais.length; - let preset: "duel" | "team" | "ffa" | "teamffa" = "duel"; - if (replayData.info.allyTeams.length > 2 && replayData.info.players.some((player) => player.playerId !== player.allyTeamId)) { - preset = "teamffa"; - } else if (replayData.info.allyTeams.length > 2) { - preset = "ffa"; - } else if (numOfPlayers > 2) { - preset = "team"; +const jobs = new Map< + string, + { + resolve: (replay: Replay) => void; + reject: (reason?: string) => void; } +>(); - return { - gameId: replayData.header.gameId, - fileName: path.parse(replayPath).base, - filePath: replayPath, - engineVersion: replayData.info.meta.engine, - gameVersion: replayData.info.meta.game, - mapScriptName: replayData.info.meta.map, - startTime: replayData.info.meta.startTime, - gameDurationMs: replayData.info.meta.durationMs, - gameEndedNormally: replayData.info.meta.winningAllyTeamIds.length > 0 ? 1 : 0, - chatlog: replayData.chatlog || null, - hasBots: replayData.info.ais.length > 0 ? 1 : 0, - preset: preset, - winningTeamId: replayData.info.meta.winningAllyTeamIds[0], - teams: replayData.info.allyTeams, - contenders: [...replayData.info.players, ...replayData.info.ais], - spectators: replayData.info.spectators, - script: replayData.script, - battleSettings: replayData.info.hostSettings, - gameSettings: replayData.info.gameSettings, - mapSettings: replayData.info.mapSettings, - hostSettings: replayData.info.spadsSettings ?? {}, - } as Replay; -} +worker.on("message", ({ replayFilePath, replay, error }) => { + const promiseHandles = jobs.get(replayFilePath); + if (error) { + promiseHandles.reject(error); + } else { + promiseHandles.resolve(replay); + } + jobs.delete(replayFilePath); +}); export function asyncParseReplay(replayFilePath: string): Promise { return new Promise((resolve, reject) => { - const worker = new Worker(path.join(__dirname, "parse-replay-worker.cjs"), { - workerData: { replayFilePath }, - }); - worker.on("message", resolve); - worker.on("error", reject); - worker.on("exit", (code) => { - if (code !== 0) reject(new Error(`parse-replay-worker stopped with exit code ${code}`)); - }); + jobs.set(replayFilePath, { resolve, reject }); + worker.postMessage(replayFilePath); }); } diff --git a/src/main/content/replays/replay-content.ts b/src/main/content/replays/replay-content.ts index 50b8bd88..4fb074dd 100644 --- a/src/main/content/replays/replay-content.ts +++ b/src/main/content/replays/replay-content.ts @@ -1,17 +1,14 @@ -import { delay } from "$/jaz-ts-utils/delay"; import { Signal } from "$/jaz-ts-utils/signal"; import { REPLAYS_PATH } from "@main/config/app"; import { mapContentAPI } from "@main/content/maps/map-content"; import { asyncParseReplay } from "@main/content/replays/parse-replay"; import { Replay } from "@main/content/replays/replay"; import { gameAPI } from "@main/game/game"; -import { isFileInUse } from "@main/utils/file"; import { logger } from "@main/utils/logger"; import chokidar from "chokidar"; import fs from "fs"; import path from "path"; -import { reactive } from "vue"; const log = logger("replay-content.ts"); @@ -20,12 +17,11 @@ export class ReplayContentAPI { public readonly onReplayCached: Signal = new Signal(); public readonly onReplayDeleted: Signal = new Signal(); - protected readonly replayCacheQueue: Set = reactive(new Set()); + protected readonly replayCacheQueue: Set = new Set(); protected cachingReplays = false; public async init() { await fs.promises.mkdir(REPLAYS_PATH, { recursive: true }); - this.startCacheReplayConsumer(); this.startWatchingReplayFolder(); return this; } @@ -42,8 +38,7 @@ export class ReplayContentAPI { return; } log.debug(`Chokidar -=- Replay added: ${filepath}`); - const filename = path.basename(filepath); - this.queueReplaysToCache([filename]); + this.cacheReplay(filepath); }) .on("unlink", (filepath) => { if (!filepath.endsWith("sdfz")) { @@ -61,7 +56,7 @@ export class ReplayContentAPI { await fs.promises.copyFile(filePath, replayPath); } const replay = await asyncParseReplay(replayPath); - await mapContentAPI.downloadMap(replay.mapScriptName); + await mapContentAPI.downloadMap(replay.mapSpringName); gameAPI.launchReplay((await replay) as Replay); } @@ -69,8 +64,10 @@ export class ReplayContentAPI { const existingFiles = await this.scanFolderForReplays(); const replaysToDelete = replayFileNames.filter((fileName) => !existingFiles.includes(fileName)); replaysToDelete.forEach((fileName) => this.onReplayDeleted.dispatch(fileName)); - const replaysToCache = existingFiles.filter((fileName) => !replayFileNames.includes(fileName)); - this.queueReplaysToCache(replaysToCache); + existingFiles + .filter((fileName) => !replayFileNames.includes(fileName)) + .map((fileName) => path.join(REPLAYS_PATH, fileName)) + .forEach(this.cacheReplay); } protected async scanFolderForReplays() { @@ -79,17 +76,6 @@ export class ReplayContentAPI { return replayFiles; } - protected async queueReplaysToCache(filenames?: string[]) { - let replayFiles = filenames; - if (!filenames) { - replayFiles = await this.scanFolderForReplays(); - } - for (const mapFileToCache of replayFiles) { - this.replayCacheQueue.add(mapFileToCache); - this.onReplayCachingStarted.dispatch(mapFileToCache); - } - } - public async deleteReplay(fileName: string) { try { await fs.promises.rm(path.join(REPLAYS_PATH, fileName)); @@ -98,37 +84,12 @@ export class ReplayContentAPI { } } - protected async startCacheReplayConsumer() { - if (this.cachingReplays) { - log.warn("Don't call cacheReplays more than once"); - return; - } - this.cachingReplays = true; - - while (true) { - const [replayToCache] = this.replayCacheQueue; - if (replayToCache) { - const replayFilePath = path.join(REPLAYS_PATH, replayToCache); - const fileInUse = await isFileInUse(replayFilePath); - if (fileInUse) { - log.debug(`Cannot parse replay yet because it is still being written: ${replayToCache}`); - this.replayCacheQueue.delete(replayToCache); - } else { - await this.cacheReplay(replayFilePath); - } - } else { - await delay(500); - } - } - } - protected async cacheReplay(replayFilePath: string) { - const replayFileName = path.parse(replayFilePath).base; - log.debug(`Caching: ${replayFileName}`); + log.debug(`Caching: ${replayFilePath}`); try { const replayData = await asyncParseReplay(replayFilePath); if (replayData.gameId === "00000000000000000000000000000000") { - throw new Error(`invalid gameId for replay: ${replayFileName}`); + throw new Error(`invalid gameId for replay: ${replayFilePath}`); } // TODO handle this use case // const conflictingReplay = await this.getReplayByGameId(replayData.gameId); @@ -141,15 +102,14 @@ export class ReplayContentAPI { // await fs.promises.rm(filePath); // } // } - log.debug(`Cached replay: ${replayFileName}`); + log.debug(`Cached replay: ${replayFilePath}`); this.onReplayCached.dispatch(replayData); } catch (err) { - log.error(`Error caching replay: ${replayFileName}`, err); + log.error(`Error caching replay: ${replayFilePath}`, err); log.error(err); fs.promises.rename(replayFilePath, replayFilePath + ".error"); //TODO emit error signal } - this.replayCacheQueue.delete(replayFileName); } } diff --git a/src/main/content/replays/replay.ts b/src/main/content/replays/replay.ts index 1f90d4ea..30907397 100644 --- a/src/main/content/replays/replay.ts +++ b/src/main/content/replays/replay.ts @@ -6,7 +6,7 @@ export type Replay = { filePath: string; engineVersion: string; gameVersion: string; - mapScriptName: string; + mapSpringName: string; startTime: Date; gameDurationMs: number; gameEndedNormally: 0 | 1; @@ -29,7 +29,7 @@ export type OngoingBattle = { gameId: string; engineVersion: string; gameVersion: string; - mapScriptName: string; + mapSpringName: string; startTime: Date; hasBots: 0 | 1; preset: "duel" | "team" | "ffa" | "teamffa"; diff --git a/src/main/game/battle/battle-types.ts b/src/main/game/battle/battle-types.ts index fd56a476..710a9518 100644 --- a/src/main/game/battle/battle-types.ts +++ b/src/main/game/battle/battle-types.ts @@ -1,3 +1,4 @@ +import { MapData } from "@main/content/maps/map-data"; import { User } from "@main/model/user"; export interface Battle { @@ -5,7 +6,7 @@ export interface Battle { isOnline: boolean; battleOptions: BattleOptions; me: Player; - teams: Record>; + teams: Array>; spectators: Player[]; started: boolean; } @@ -15,14 +16,22 @@ export interface BattleWithMetadata extends Battle { participants: Array; } +export type GameMode = { + label: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options: Record; +}; + export type BattleOptions = { engineVersion?: string; gameVersion?: string; - mapScriptName?: string; - startPosType: StartPosType; - startBoxes: Record; - gameOptions: Record; - mapOptions: Record; + gameMode: GameMode; + map?: MapData; + mapOptions: { + startPosType: StartPosType; + startBoxesIndex?: number; + fixedPositionsIndex?: number; + }; restrictions: Restriction[]; }; @@ -80,6 +89,7 @@ export type Restriction = { export type Player = { id: number; + name: string; user: User; contentSyncState: { engine: number; diff --git a/src/main/game/game.ts b/src/main/game/game.ts index 9f7b5f05..eb9eb6d5 100644 --- a/src/main/game/game.ts +++ b/src/main/game/game.ts @@ -20,18 +20,18 @@ const engineLogger = logger("[RECOIL ENGINE]", { separator: "\n", level: "info" export class GameAPI { public onGameLaunched = new Signal(); public onGameClosed: Signal = new Signal(); - public readonly scriptName = "script.txt"; + public readonly springName = "script.txt"; protected gameProcess: ChildProcess | null = null; public async launchBattle(battle: BattleWithMetadata): Promise { const script = startScriptConverter.generateScriptStr(battle); - const scriptPath = path.join(CONTENT_PATH, this.scriptName); + const scriptPath = path.join(CONTENT_PATH, this.springName); await fs.promises.writeFile(scriptPath, script); await this.launch({ engineVersion: battle.battleOptions.engineVersion, gameVersion: battle.battleOptions.gameVersion, - mapScriptName: battle.battleOptions.mapScriptName, + mapSpringName: battle.battleOptions.map.springName, launchArg: scriptPath, }); } @@ -40,7 +40,7 @@ export class GameAPI { await this.launch({ engineVersion: replay.engineVersion, gameVersion: replay.gameVersion, - mapScriptName: replay.mapScriptName, + mapSpringName: replay.mapSpringName, launchArg: replay.filePath ? replay.filePath : path.join(REPLAYS_PATH, replay.fileName), }); } @@ -51,16 +51,16 @@ export class GameAPI { if (!gameVersion) { throw new Error("Could not parse game version from script"); } - const mapScriptName = script.match(/mapname\s*=\s*(.*);/)?.[1]; - if (!mapScriptName) { + const mapSpringName = script.match(/mapname\s*=\s*(.*);/)?.[1]; + if (!mapSpringName) { throw new Error("Could not parse map name from script"); } - const scriptPath = path.join(CONTENT_PATH, this.scriptName); + const scriptPath = path.join(CONTENT_PATH, this.springName); await fs.promises.writeFile(scriptPath, script); await this.launch({ engineVersion: DEFAULT_ENGINE_VERSION, gameVersion, - mapScriptName, + mapSpringName, launchArg: scriptPath, }); } @@ -68,16 +68,16 @@ export class GameAPI { public async launch({ engineVersion = DEFAULT_ENGINE_VERSION, gameVersion, - mapScriptName, + mapSpringName, launchArg, }: { engineVersion: string; gameVersion: string; - mapScriptName: string; + mapSpringName: string; launchArg: string; }): Promise { try { - log.info(`Launching game with engine: ${engineVersion}, game: ${gameVersion}, map: ${mapScriptName}`); + log.info(`Launching game with engine: ${engineVersion}, game: ${gameVersion}, map: ${mapSpringName}`); await this.fetchMissingContent(engineVersion, gameVersion); const enginePath = path.join(CONTENT_PATH, "engine", engineVersion).replaceAll("\\", "/"); const args = ["--write-dir", CONTENT_PATH, "--isolation", launchArg]; diff --git a/src/main/json/file-store.ts b/src/main/json/file-store.ts index d1ed0598..1547af9a 100644 --- a/src/main/json/file-store.ts +++ b/src/main/json/file-store.ts @@ -2,10 +2,8 @@ import type { Static, TObject } from "@sinclair/typebox"; import type { ValidateFunction } from "ajv"; import Ajv from "ajv"; import fs from "fs"; -import { assign } from "$/jaz-ts-utils/object"; import path from "path"; import { logger } from "@main/utils/logger"; -import { SetUndefinedValues } from "$/jaz-ts-utils/types"; const log = logger("file-store.ts"); @@ -40,9 +38,9 @@ export class FileStore { } // could throw if the data is invalid - public async update(data: SetUndefinedValues) { + public async update(data: Partial) { // not great since we're modifying the model before validation - assign(this.model, data); + Object.assign(this.model, data); const isValid = this.validator(this.model); if (isValid) { await this.write(); @@ -58,7 +56,7 @@ export class FileStore { const model = JSON.parse(modelStr); const isValid = this.validator(model); if (isValid) { - assign(this.model, model as Static); + Object.assign(this.model, model as Static); } else { log.error(`Error validating file: ${this.filePath}`, this.validator.errors); } diff --git a/src/main/main.ts b/src/main/main.ts index 56f9b1dc..86d4fc72 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -43,7 +43,6 @@ protocol.registerSchemesAsPrivileged([ scheme: "bar", privileges: { bypassCSP: true, - supportFetchAPI: true, // Required for fetch calls, such as in pixi.js }, }, ]); @@ -122,7 +121,7 @@ app.whenReady().then(() => { callback({ responseHeaders: { ...details.responseHeaders, - "Content-Security-Policy": ["default-src 'self' 'unsafe-inline' data: blob:"], // data: and blob: are required for PIXI.js + "Content-Security-Policy": ["default-src 'self' 'unsafe-inline' data: blob:"], }, }); }); diff --git a/src/main/model/start-script.ts b/src/main/model/start-script.ts index a8caa552..2403231d 100644 --- a/src/main/model/start-script.ts +++ b/src/main/model/start-script.ts @@ -11,7 +11,7 @@ export interface Game { mypasswd?: string; nohelperais?: number; gamestartdelay?: number; - scriptname?: string; + springName?: string; startpostype?: number; demofile?: string; savefile?: string; diff --git a/src/main/model/user.ts b/src/main/model/user.ts index d1631b2f..2560b3fc 100644 --- a/src/main/model/user.ts +++ b/src/main/model/user.ts @@ -12,7 +12,7 @@ export type User = { battleRoomState: { isSpectator?: boolean; isReady?: boolean; - teamId?: string; + teamId?: number; }; }; diff --git a/src/main/services/game.service.ts b/src/main/services/game.service.ts index b7fea864..8f90f0f0 100644 --- a/src/main/services/game.service.ts +++ b/src/main/services/game.service.ts @@ -12,7 +12,6 @@ function init() { function registerIpcHandlers(mainWindow: Electron.BrowserWindow) { // Content ipcMain.handle("game:downloadGame", (_, version: string) => gameContentAPI.downloadGame(version)); - ipcMain.handle("game:getGameOptions", (_, version: string) => gameContentAPI.getGameOptions(version)); ipcMain.handle("game:getScenarios", () => gameContentAPI.getScenarios()); ipcMain.handle("game:getInstalledVersions", () => gameContentAPI.installedVersions); ipcMain.handle("game:isVersionInstalled", (_, id: string) => gameContentAPI.isVersionInstalled(id)); diff --git a/src/main/services/maps.service.ts b/src/main/services/maps.service.ts index 5974e5ab..ee975b31 100644 --- a/src/main/services/maps.service.ts +++ b/src/main/services/maps.service.ts @@ -1,25 +1,39 @@ import { MapData } from "@main/content/maps/map-data"; import { mapContentAPI } from "@main/content/maps/map-content"; import { ipcMain } from "electron"; +import { MapMetadata } from "@main/content/maps/online-map"; +import { fetchMapImages } from "@main/content/maps/map-image"; function init() { mapContentAPI.init(); } +async function fetchAllMaps() { + const maps = await fetch("https://maps-metadata.beyondallreason.dev/latest/lobby_maps.validated.json"); + const mapsAsObject = await maps.json(); + const mapsAsArray = Object.values(mapsAsObject) as MapMetadata[]; + return mapsAsArray.map((map: MapMetadata) => { + // transform the map object to a MapData object + return { + ...map, + isInstalled: mapContentAPI.isVersionInstalled(map.springName), + } as MapData; + }); +} + function registerIpcHandlers(mainWindow: Electron.BrowserWindow) { - ipcMain.handle("maps:sync", (_, maps: { scriptName: string; fileName: string }[]) => mapContentAPI.sync(maps)); - ipcMain.handle("maps:downloadMap", (_, scriptName: string) => mapContentAPI.downloadMap(scriptName)); - ipcMain.handle("maps:downloadMaps", (_, scriptNames: string[]) => mapContentAPI.downloadMaps(scriptNames)); + ipcMain.handle("maps:downloadMap", (_, springName: string) => mapContentAPI.downloadMap(springName)); + ipcMain.handle("maps:downloadMaps", (_, springNames: string[]) => mapContentAPI.downloadMaps(springNames)); ipcMain.handle("maps:getInstalledVersions", () => mapContentAPI.installedVersions); ipcMain.handle("maps:isVersionInstalled", (_, id: string) => mapContentAPI.isVersionInstalled(id)); ipcMain.handle("maps:attemptCacheErrorMaps", () => mapContentAPI.attemptCacheErrorMaps()); + ipcMain.handle("maps:online:fetchAllMaps", () => fetchAllMaps()); + ipcMain.handle("maps:online:fetchMapImages", (_, imageSource: string) => fetchMapImages(imageSource)); + // Events - mapContentAPI.onMapCachingStarted.add((filename: string) => { - mainWindow.webContents.send("maps:mapCachingStarted", filename); - }); - mapContentAPI.onMapCached.add((mapData: MapData) => { - mainWindow.webContents.send("maps:mapCached", mapData); + mapContentAPI.onMapAdded.add((filename: string) => { + mainWindow.webContents.send("maps:mapAdded", filename); }); mapContentAPI.onMapDeleted.add((filename: string) => { mainWindow.webContents.send("maps:mapDeleted", filename); diff --git a/src/main/services/news.service.ts b/src/main/services/news.service.ts index 65142b14..3f4e306a 100644 --- a/src/main/services/news.service.ts +++ b/src/main/services/news.service.ts @@ -18,7 +18,7 @@ const MAX_NEWS_TO_LOAD = 7; let newsFeed: NewsFeedData | null = null; -async function fetchImageToBase64(url: string) { +export async function fetchImageToBase64(url: string) { try { const response = await fetch(url); if (!response.ok) { diff --git a/src/main/utils/start-script-converter.ts b/src/main/utils/start-script-converter.ts index d2d2f556..205f3080 100644 --- a/src/main/utils/start-script-converter.ts +++ b/src/main/utils/start-script-converter.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { assign } from "$/jaz-ts-utils/object"; import { BattleWithMetadata, isPlayer, StartPosType } from "@main/game/battle/battle-types"; import { AllyTeam, Bot, Game, Player, Team } from "@main/model/start-script"; @@ -43,18 +42,22 @@ class StartScriptConverter { }; allyTeams.push(allyTeam); - if (battle.battleOptions.startPosType === StartPosType.Boxes) { - const box = battle.battleOptions.startBoxes[allyTeam.id]; - if (box) { - assign(allyTeam, { - startrectleft: box.xPercent, - startrecttop: box.yPercent, - startrectright: box.xPercent + box.widthPercent, - startrectbottom: box.yPercent + box.heightPercent, - }); - } else { - console.warn(`Ally team ${allyTeam.id} has no defined start area for this map`); - } + if (battle.battleOptions.mapOptions.startPosType === StartPosType.Boxes) { + const startBoxesIndex = battle.battleOptions.mapOptions.startBoxesIndex; + const box = battle.battleOptions.map.startboxesSet[startBoxesIndex].startboxes[allyTeam.id]; + + //TODO: implement start area + throw new Error("Not implemented"); + // if (box) { + // Object.assign(allyTeam, { + // startrectleft: box.xPercent, + // startrecttop: box.yPercent, + // startrectright: box.xPercent + box.widthPercent, + // startrectbottom: box.yPercent + box.heightPercent, + // }); + // } else { + // console.warn(`Ally team ${allyTeam.id} has no defined start area for this map`); + // } } team.forEach((teamMember) => { @@ -112,11 +115,11 @@ class StartScriptConverter { return { gametype: battle.battleOptions.gameVersion, - mapname: battle.battleOptions.mapScriptName, - modoptions: battle.battleOptions.gameOptions, + mapname: battle.battleOptions.map.springName, + modoptions: battle.battleOptions.gameMode.options, ishost: 1, myplayername: battle.me.user.username, - startpostype: battle.battleOptions.startPosType, + startpostype: battle.battleOptions.mapOptions.startPosType, allyTeams, teams, players, diff --git a/src/preload/preload.ts b/src/preload/preload.ts index b282ace4..da31432d 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -6,15 +6,12 @@ import { Account } from "@main/services/account.service"; import { EngineVersion } from "@main/content/engine/engine-version"; import { GameVersion } from "@main/content/game/game-version"; import { MapData } from "@main/content/maps/map-data"; -import { LuaOptionSection } from "@main/content/game/lua-options"; import { Scenario } from "@main/content/game/scenario"; import { DownloadInfo } from "@main/content/downloads"; import { Info } from "@main/services/info.service"; import { NewsFeedData } from "@main/services/news.service"; import { BattleWithMetadata } from "@main/game/battle/battle-types"; -console.log("preload.ts loaded"); - const infoApi = { getInfo: (): Promise => ipcRenderer.invoke("info:get"), }; @@ -82,7 +79,6 @@ contextBridge.exposeInMainWorld("engine", engineApi); const gameApi = { // Content downloadGame: (version: string): Promise => ipcRenderer.invoke("game:downloadGame", version), - getGameOptions: (version: string): Promise => ipcRenderer.invoke("game:getOptions", version), getScenarios: (): Promise => ipcRenderer.invoke("game:getScenarios"), getInstalledVersions: (): Promise => ipcRenderer.invoke("game:getInstalledVersions"), isVersionInstalled: (version: string): Promise => ipcRenderer.invoke("game:isVersionInstalled", version), @@ -101,16 +97,18 @@ export type GameApi = typeof gameApi; contextBridge.exposeInMainWorld("game", gameApi); const mapsApi = { - sync: (maps: { scriptName: string; fileName: string }[]): Promise => ipcRenderer.invoke("maps:sync", maps), - downloadMap: (version: string): Promise => ipcRenderer.invoke("maps:downloadMap", version), - downloadMaps: (scriptNames: string[]): Promise => ipcRenderer.invoke("maps:downloadMaps", scriptNames), + // Content + downloadMap: (springName: string): Promise => ipcRenderer.invoke("maps:downloadMap", springName), + downloadMaps: (springNames: string[]): Promise => ipcRenderer.invoke("maps:downloadMaps", springNames), getInstalledVersions: (): Promise => ipcRenderer.invoke("maps:getInstalledVersions"), - isVersionInstalled: (id: string): Promise => ipcRenderer.invoke("maps:isVersionInstalled", id), - attemptCacheErrorMaps: (): Promise => ipcRenderer.invoke("maps:attemptCacheErrorMaps"), + isVersionInstalled: (springName: string): Promise => ipcRenderer.invoke("maps:isVersionInstalled", springName), + + // Online features + fetchAllMaps: (): Promise => ipcRenderer.invoke("maps:online:fetchAllMaps"), + fetchMapImages: (imageSource: string): Promise => ipcRenderer.invoke("maps:online:fetchMapImages", imageSource), // Events - onMapCachingStarted: (callback: (filename: string) => void) => ipcRenderer.on("maps:mapCachingStarted", (_event, filename) => callback(filename as string)), - onMapCached: (callback: (mapData: MapData) => void) => ipcRenderer.on("maps:mapCached", (_event, mapData) => callback(mapData as MapData)), + onMapAdded: (callback: (filename: string) => void) => ipcRenderer.on("maps:mapAdded", (_event, filename) => callback(filename as string)), onMapDeleted: (callback: (filename: string) => void) => ipcRenderer.on("maps:mapDeleted", (_event, filename) => callback(filename as string)), }; export type MapsApi = typeof mapsApi; diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 51a6e1bb..8dddb66b 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -112,20 +112,19 @@ router.afterEach(async (to) => { function onIntroEnd() { videoVisible.value = false; - console.log("Intro video ended"); } async function onPreloadDone() { state.value = "initial-setup"; // TODO: should also check to see if game and maps are installed (need to fix bug where interrupted game dl reports as successful install) const installedEngines = await window.engine.getInstalledVersions(); - console.log(installedEngines); + console.debug(installedEngines); if (installedEngines.length === 0) { state.value = "initial-setup"; return; } const installedGameVersions = await window.game.getInstalledVersions(); - console.log(installedGameVersions); + console.debug(installedGameVersions); if (installedGameVersions.length === 0) { state.value = "initial-setup"; return; @@ -135,7 +134,7 @@ async function onPreloadDone() { function onInitialSetupDone() { state.value = "default"; - console.log("Initial setup done"); + console.debug("Initial setup done"); } diff --git a/src/renderer/api/notifications.ts b/src/renderer/api/notifications.ts index 3e7d7b54..7dc4eee4 100644 --- a/src/renderer/api/notifications.ts +++ b/src/renderer/api/notifications.ts @@ -1,17 +1,19 @@ import { removeFromArray } from "$/jaz-ts-utils/object"; -import { v4 as uuid } from "uuid"; import { reactive } from "vue"; import { Alert, Event } from "@renderer/model/notifications"; import { audioApi } from "../audio/audio"; +let notificationId = 0; + class NotificationsAPI { public readonly alerts: Array = reactive([]); public readonly events: Array = reactive([]); public alert(alertConfig: Omit) { + notificationId++; const alert: Alert = { - id: uuid(), + id: notificationId.toString(), timeoutMs: Math.max(alertConfig.text.length * 75, 5000), severity: "info", ...alertConfig, @@ -20,8 +22,9 @@ class NotificationsAPI { } public event(eventConfig: Omit) { + notificationId++; const event: Event = { - id: uuid(), + id: notificationId.toString(), timeoutMs: Math.max(eventConfig.text.length * 75, 10000), playSound: true, ...eventConfig, diff --git a/src/renderer/assets/images/factions/armada_faction.png b/src/renderer/assets/images/factions/armada_faction.png new file mode 100644 index 0000000000000000000000000000000000000000..324429926f048be11c9a50aa188c32af365f8f30 GIT binary patch literal 4136 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 h9e_1i0P6s(`2w%Y3-mqCjcH-PwH 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 literal 0 HcmV?d00001 diff --git a/src/renderer/components/battle/AddBotModal.vue b/src/renderer/components/battle/AddBotModal.vue index d05e74ed..3e32375b 100644 --- a/src/renderer/components/battle/AddBotModal.vue +++ b/src/renderer/components/battle/AddBotModal.vue @@ -1,7 +1,13 @@ diff --git a/src/renderer/components/battle/GameModeComponent.vue b/src/renderer/components/battle/GameModeComponent.vue new file mode 100644 index 00000000..739d3846 --- /dev/null +++ b/src/renderer/components/battle/GameModeComponent.vue @@ -0,0 +1,115 @@ + + + + diff --git a/src/renderer/components/battle/LuaOptionsModal.vue b/src/renderer/components/battle/LuaOptionsModal.vue index 7516e5fb..693c4dc4 100644 --- a/src/renderer/components/battle/LuaOptionsModal.vue +++ b/src/renderer/components/battle/LuaOptionsModal.vue @@ -1,76 +1,89 @@