diff --git a/eslint.config.js b/eslint.config.js index 474e4d3..f04d925 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,19 +1,17 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; +import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(['**/dist']), { files: ['**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommended, - reactHooks.configs.flat.recommended, - reactRefresh.configs.vite, ], languageOptions: { globals: globals.browser, @@ -27,4 +25,11 @@ export default defineConfig([ }], }, }, -]) + { + files: ['webclient/**/*.{ts,tsx}'], + extends: [ + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + }, +]); diff --git a/package.json b/package.json index 3b8a6c0..5f386f6 100644 --- a/package.json +++ b/package.json @@ -4,43 +4,20 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", + "dev": "pnpm --parallel -r dev", + "build": "pnpm --parallel -r build", "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.6.1", - "@react-three/rapier": "^2.2.0", - "@tabler/icons-react": "^3.44.0", - "@types/three": "^0.184.1", - "blockly": "^12.5.1", - "install": "^0.13.0", - "mobx": "^6.15.4", - "mobx-react-lite": "^4.1.1", - "mobx-utils": "^6.1.1", - "nipplejs": "^1.0.4", - "npm": "^11.16.0", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-router-dom": "^7.16.0", - "three": "^0.184.0", - "uuid": "^14.0.0" + "client:dev": "pnpm --filter webclient dev", + "server:dev": "pnpm --filter server dev" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/node": "^24.12.3", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", "eslint": "^10.3.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", - "sass": "^1.100.0", "typescript": "~6.0.2", - "typescript-eslint": "^8.59.2", - "vite": "^8.0.12" + "typescript-eslint": "^8.59.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c36244f..f50da95 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,43 @@ settings: importers: .: + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.4.1) + '@types/node': + specifier: ^24.12.3 + version: 24.12.4 + eslint: + specifier: ^10.3.0 + version: 10.4.1 + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.4.1) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@10.4.1) + globals: + specifier: ^17.6.0 + version: 17.6.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + typescript-eslint: + specifier: ^8.59.2 + version: 8.60.0(eslint@10.4.1)(typescript@6.0.3) + + server: + dependencies: + fastify: + specifier: ^5.3.2 + version: 5.8.5 + devDependencies: + tsx: + specifier: ^4.19.4 + version: 4.22.4 + + webclient: dependencies: '@react-three/drei': specifier: ^10.7.7 @@ -26,9 +63,6 @@ importers: blockly: specifier: ^12.5.1 version: 12.5.1 - install: - specifier: ^0.13.0 - version: 0.13.0 mobx: specifier: ^6.15.4 version: 6.15.4 @@ -41,9 +75,6 @@ importers: nipplejs: specifier: ^1.0.4 version: 1.0.4 - npm: - specifier: ^11.16.0 - version: 11.16.0 react: specifier: ^19.2.6 version: 19.2.6 @@ -60,12 +91,6 @@ importers: specifier: ^14.0.0 version: 14.0.0 devDependencies: - '@eslint/js': - specifier: ^10.0.1 - version: 10.0.1(eslint@10.4.1) - '@types/node': - specifier: ^24.12.3 - version: 24.12.4 '@types/react': specifier: ^19.2.14 version: 19.2.15 @@ -74,31 +99,13 @@ importers: version: 19.2.3(@types/react@19.2.15) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0)) - eslint: - specifier: ^10.3.0 - version: 10.4.1 - eslint-plugin-react-hooks: - specifier: ^7.1.1 - version: 7.1.1(eslint@10.4.1) - eslint-plugin-react-refresh: - specifier: ^0.5.2 - version: 0.5.2(eslint@10.4.1) - globals: - specifier: ^17.6.0 - version: 17.6.0 + version: 6.0.2(vite@8.0.14(@types/node@24.12.4)(esbuild@0.28.0)(sass@1.100.0)(tsx@4.22.4)) sass: specifier: ^1.100.0 version: 1.100.0 - typescript: - specifier: ~6.0.2 - version: 6.0.3 - typescript-eslint: - specifier: ^8.59.2 - version: 8.60.0(eslint@10.4.1)(typescript@6.0.3) vite: specifier: ^8.0.12 - version: 8.0.14(@types/node@24.12.4)(sass@1.100.0) + version: 8.0.14(@types/node@24.12.4)(esbuild@0.28.0)(sass@1.100.0)(tsx@4.22.4) packages: @@ -219,6 +226,162 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -258,6 +421,24 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.0.3': + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + + '@fastify/forwarded@3.0.1': + resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -399,6 +580,9 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@react-three/drei@10.7.7': resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==} peerDependencies: @@ -674,6 +858,9 @@ packages: babel-plugin-react-compiler: optional: true + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -688,9 +875,27 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@9.2.0: + resolution: {integrity: sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -777,6 +982,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-gpu@5.0.70: resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} @@ -794,6 +1003,11 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -855,15 +1069,33 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-json-stringify@6.4.0: + resolution: {integrity: sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==} + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastify@5.8.5: + resolution: {integrity: sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -883,6 +1115,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + find-my-way@9.6.0: + resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + engines: {node: '>=20'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -960,9 +1196,9 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - install@0.13.0: - resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} - engines: {node: '>= 0.10'} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + engines: {node: '>= 10'} is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} @@ -1006,9 +1242,15 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1027,6 +1269,9 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1171,80 +1416,13 @@ packages: resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} engines: {node: '>=18'} - npm@11.16.0: - resolution: {integrity: sha512-A74XL8OxmcegZDMWPkWb5bEQppg8HdYwW3rBD2sPoS4UQHVajfaxBkqyzLeJ3wR0kZ+5xoTjItxXaF7eIXUsyw==} - engines: {node: ^20.17.0 || >=22.9.0} - hasBin: true - bundledDependencies: - - '@isaacs/string-locale-compare' - - '@npmcli/arborist' - - '@npmcli/config' - - '@npmcli/fs' - - '@npmcli/map-workspaces' - - '@npmcli/metavuln-calculator' - - '@npmcli/package-json' - - '@npmcli/promise-spawn' - - '@npmcli/redact' - - '@npmcli/run-script' - - '@sigstore/tuf' - - abbrev - - archy - - cacache - - chalk - - ci-info - - fastest-levenshtein - - fs-minipass - - glob - - graceful-fs - - hosted-git-info - - ini - - init-package-json - - is-cidr - - json-parse-even-better-errors - - libnpmaccess - - libnpmdiff - - libnpmexec - - libnpmfund - - libnpmorg - - libnpmpack - - libnpmpublish - - libnpmsearch - - libnpmteam - - libnpmversion - - make-fetch-happen - - minimatch - - minipass - - minipass-pipeline - - ms - - node-gyp - - nopt - - npm-audit-report - - npm-install-checks - - npm-package-arg - - npm-pick-manifest - - npm-profile - - npm-registry-fetch - - npm-user-validate - - p-map - - pacote - - parse-conflict-json - - proc-log - - qrcode-terminal - - read - - semver - - spdx-expression-parse - - ssri - - supports-color - - tar - - text-table - - tiny-relative-date - - treeverse - - validate-npm-package-name - - which - nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1275,6 +1453,16 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -1286,6 +1474,12 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + promise-worker-transferable@1.0.4: resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} @@ -1293,6 +1487,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + react-dom@19.2.6: resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: @@ -1332,10 +1529,28 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rolldown@1.0.2: resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1344,6 +1559,14 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1359,6 +1582,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1379,10 +1605,17 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stats-gl@2.4.2: resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} peerDependencies: @@ -1400,6 +1633,10 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + three-mesh-bvh@0.8.3: resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} peerDependencies: @@ -1424,6 +1661,10 @@ packages: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true + toad-cache@3.7.1: + resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} + engines: {node: '>=20'} + tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -1454,6 +1695,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-rat@0.1.2: resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} @@ -1797,6 +2043,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.28.0': + optional: true + + '@esbuild/linux-loong64@0.28.0': + optional: true + + '@esbuild/linux-mips64el@0.28.0': + optional: true + + '@esbuild/linux-ppc64@0.28.0': + optional: true + + '@esbuild/linux-riscv64@0.28.0': + optional: true + + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.28.0': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1)': dependencies: eslint: 10.4.1 @@ -1831,6 +2155,29 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@fastify/ajv-compiler@4.0.5': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 3.1.2 + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.0.3': + dependencies: + fast-json-stringify: 6.4.0 + + '@fastify/forwarded@3.0.1': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.1 + ipaddr.js: 2.4.0 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -1943,6 +2290,8 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true + '@pinojs/redact@0.4.0': {} + '@react-three/drei@10.7.7(@react-three/fiber@9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0))(@types/react@19.2.15)(@types/three@0.184.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)': dependencies: '@babel/runtime': 7.29.7 @@ -2207,10 +2556,12 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.6 - '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4)(esbuild@0.28.0)(sass@1.100.0)(tsx@4.22.4))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.14(@types/node@24.12.4)(sass@1.100.0) + vite: 8.0.14(@types/node@24.12.4)(esbuild@0.28.0)(sass@1.100.0)(tsx@4.22.4) + + abstract-logging@2.0.1: {} acorn-jsx@5.3.2(acorn@8.16.0): dependencies: @@ -2220,6 +2571,10 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -2227,6 +2582,20 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + atomic-sleep@1.0.0: {} + + avvio@9.2.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.20.1 + balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -2307,6 +2676,8 @@ snapshots: deep-is@0.1.4: {} + dequal@2.0.3: {} + detect-gpu@5.0.70: dependencies: webgl-constants: 1.1.1 @@ -2319,6 +2690,35 @@ snapshots: entities@6.0.1: {} + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -2402,12 +2802,51 @@ snapshots: esutils@2.0.3: {} + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@6.4.0: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 3.1.2 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + fast-levenshtein@2.0.6: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-uri@3.1.2: {} + + fastify@5.8.5: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.2.0 + fast-json-stringify: 6.4.0 + find-my-way: 9.6.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.8.1 + toad-cache: 3.7.1 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -2420,6 +2859,12 @@ snapshots: dependencies: flat-cache: 4.0.1 + find-my-way@9.6.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2487,7 +2932,7 @@ snapshots: imurmurhash@0.1.4: {} - install@0.13.0: {} + ipaddr.js@2.4.0: {} is-extglob@2.1.1: {} @@ -2541,8 +2986,14 @@ snapshots: json-buffer@3.0.1: {} + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -2560,6 +3011,12 @@ snapshots: dependencies: immediate: 3.0.6 + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 + lightningcss-android-arm64@1.32.0: optional: true @@ -2661,10 +3118,10 @@ snapshots: node-releases@2.0.46: {} - npm@11.16.0: {} - nwsapi@2.2.23: {} + on-exit-leak-free@2.1.2: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2694,6 +3151,26 @@ snapshots: picomatch@4.0.4: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + postcss@8.5.15: dependencies: nanoid: 3.3.12 @@ -2704,6 +3181,10 @@ snapshots: prelude-ls@1.2.1: {} + process-warning@4.0.1: {} + + process-warning@5.0.0: {} + promise-worker-transferable@1.0.4: dependencies: is-promise: 2.2.2 @@ -2711,6 +3192,8 @@ snapshots: punycode@2.3.1: {} + quick-format-unescaped@4.0.4: {} + react-dom@19.2.6(react@19.2.6): dependencies: react: 19.2.6 @@ -2740,8 +3223,18 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + + real-require@1.0.0: {} + require-from-string@2.0.2: {} + ret@0.5.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + rolldown@1.0.2: dependencies: '@oxc-project/types': 0.132.0 @@ -2765,6 +3258,12 @@ snapshots: rrweb-cssom@0.8.0: {} + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sass@1.100.0: @@ -2781,6 +3280,8 @@ snapshots: scheduler@0.27.0: {} + secure-json-parse@4.1.0: {} + semver@6.3.1: {} semver@7.8.1: {} @@ -2793,8 +3294,14 @@ snapshots: shebang-regex@3.0.0: {} + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stats-gl@2.4.2(@types/three@0.184.1)(three@0.184.0): dependencies: '@types/three': 0.184.1 @@ -2808,6 +3315,10 @@ snapshots: symbol-tree@3.2.4: {} + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + three-mesh-bvh@0.8.3(three@0.184.0): dependencies: three: 0.184.0 @@ -2835,6 +3346,8 @@ snapshots: dependencies: tldts-core: 6.1.86 + toad-cache@3.7.1: {} + tough-cookie@5.1.2: dependencies: tldts: 6.1.86 @@ -2864,6 +3377,12 @@ snapshots: tslib@2.8.1: optional: true + tsx@4.22.4: + dependencies: + esbuild: 0.28.0 + optionalDependencies: + fsevents: 2.3.3 + tunnel-rat@0.1.2(@types/react@19.2.15)(react@19.2.6): dependencies: zustand: 4.5.7(@types/react@19.2.15)(react@19.2.6) @@ -2909,7 +3428,7 @@ snapshots: uuid@14.0.0: {} - vite@8.0.14(@types/node@24.12.4)(sass@1.100.0): + vite@8.0.14(@types/node@24.12.4)(esbuild@0.28.0)(sass@1.100.0)(tsx@4.22.4): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2918,8 +3437,10 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.12.4 + esbuild: 0.28.0 fsevents: 2.3.3 sass: 1.100.0 + tsx: 4.22.4 w3c-xmlserializer@5.0.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 696c825..2124f75 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,6 @@ +packages: + - 'webclient' + - 'server' allowBuilds: '@parcel/watcher': false + esbuild: true diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..94322fe --- /dev/null +++ b/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "server", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -b", + "start": "node dist/index.js" + }, + "dependencies": { + "fastify": "^5.3.2" + }, + "devDependencies": { + "tsx": "^4.19.4" + } +} diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..afcd9cc --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,9 @@ +import Fastify from 'fastify'; + +const fastify = Fastify({ logger: true }); + +fastify.get('/', async (_request, _reply) => { + return { status: 'ok' }; +}); + +await fastify.listen({ port: 3000, host: '0.0.0.0' }); diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..26e37a1 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["src"] +} diff --git a/webclient/package.json b/webclient/package.json new file mode 100644 index 0000000..d33a2ad --- /dev/null +++ b/webclient/package.json @@ -0,0 +1,35 @@ +{ + "name": "webclient", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@react-three/rapier": "^2.2.0", + "@tabler/icons-react": "^3.44.0", + "@types/three": "^0.184.1", + "blockly": "^12.5.1", + "mobx": "^6.15.4", + "mobx-react-lite": "^4.1.1", + "mobx-utils": "^6.1.1", + "nipplejs": "^1.0.4", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.16.0", + "three": "^0.184.0", + "uuid": "^14.0.0" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "sass": "^1.100.0", + "vite": "^8.0.12" + } +} diff --git a/webclient/src/blockly/actions/physics/applyImpulse.ts b/webclient/src/blockly/actions/physics/applyImpulse.ts index 6f43360..20007ef 100644 --- a/webclient/src/blockly/actions/physics/applyImpulse.ts +++ b/webclient/src/blockly/actions/physics/applyImpulse.ts @@ -23,7 +23,7 @@ Blockly.Blocks['physics_apply_impulse_action'] = { javascriptGenerator.forBlock['physics_apply_impulse_action'] = function (block, generator) { const positionValue = generator.valueToCode(block, 'POSITION', Order.ATOMIC); const forceValue = block.getFieldValue('FORCE'); - return `api.applyImpulse(${positionValue}, ${forceValue});\n`; + return `yield helpers.actions.ApplyImpulse(${positionValue}, ${forceValue});\n`; }; export { }; diff --git a/webclient/src/blockly/actions/waitSeconds.ts b/webclient/src/blockly/actions/waitSeconds.ts index 904464b..d274e97 100644 --- a/webclient/src/blockly/actions/waitSeconds.ts +++ b/webclient/src/blockly/actions/waitSeconds.ts @@ -16,7 +16,7 @@ Blockly.Blocks['wait_seconds_action'] = { javascriptGenerator.forBlock['wait_seconds_action'] = function (block) { const seconds = block.getFieldValue('SECONDS'); - return `yield wait(${seconds});\n`; + return `yield helpers.actions.WaitSeconds(${seconds});\n`; }; export { }; diff --git a/webclient/src/blockly/generateCode.ts b/webclient/src/blockly/generateCode.ts index 253b203..98de00e 100644 --- a/webclient/src/blockly/generateCode.ts +++ b/webclient/src/blockly/generateCode.ts @@ -21,12 +21,16 @@ export function generateCode(workspace: Blockly.WorkspaceSvg): string { //TODO use args - result += `onGameEvent( + result += `eventBus.on( '${block.type.replace(/_event$/, '')}', function*(context) { - if (context.object && (context.object.id !== object.id)) + if (context.object && (context.object.id !== thisObject.id)) return; ${body} }, + { + thisObject, + thisObjectType, + }, ); `; } diff --git a/webclient/src/blockly/values/currentObject.ts b/webclient/src/blockly/values/currentObject.ts index 8eb2019..a5c3806 100644 --- a/webclient/src/blockly/values/currentObject.ts +++ b/webclient/src/blockly/values/currentObject.ts @@ -13,7 +13,7 @@ Blockly.Blocks['current_object_value'] = { }; javascriptGenerator.forBlock['current_object_value'] = function (_block, _generator) { - return ['object', Order.NONE]; + return ['thisObject', Order.NONE]; }; export { }; diff --git a/webclient/src/blockly/values/currentObjectType.ts b/webclient/src/blockly/values/currentObjectType.ts index c04c6eb..86b442c 100644 --- a/webclient/src/blockly/values/currentObjectType.ts +++ b/webclient/src/blockly/values/currentObjectType.ts @@ -13,7 +13,7 @@ Blockly.Blocks['current_object_type_value'] = { }; javascriptGenerator.forBlock['current_object_type_value'] = function (_block, _generator) { - return ['objectType', Order.NONE]; + return ['thisObjectType', Order.NONE]; }; export { }; diff --git a/webclient/src/blockly/values/objectType.ts b/webclient/src/blockly/values/objectType.ts index 1b17af3..035c37a 100644 --- a/webclient/src/blockly/values/objectType.ts +++ b/webclient/src/blockly/values/objectType.ts @@ -14,7 +14,7 @@ export function getObjectTypeDropDownOptions(): [string, string][] { Blockly.Blocks['object_type_value'] = { init(this: Blockly.Block) { - this.appendEndRowInput('NAME') + this.appendEndRowInput() .appendField( new Blockly.FieldDropdown(getObjectTypeDropDownOptions), 'OBJECT_TYPE', @@ -26,8 +26,9 @@ Blockly.Blocks['object_type_value'] = { } }; -javascriptGenerator.forBlock['current_object_type_value'] = function (_block, _generator) { - return ['objectType', Order.NONE]; +javascriptGenerator.forBlock['object_type_value'] = function (block, _generator) { + const objectTypeValue = block.getFieldValue('OBJECT_TYPE'); + return [`world.objectTypes[${objectTypeValue}]`, Order.NONE]; }; export { }; diff --git a/webclient/src/components/ObjectViewInternal.tsx b/webclient/src/components/ObjectViewInternal.tsx index 1b6d014..76f1b80 100644 --- a/webclient/src/components/ObjectViewInternal.tsx +++ b/webclient/src/components/ObjectViewInternal.tsx @@ -61,24 +61,11 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props useEffect( () => { - return reaction( - () => gameObj.pendingActions.impulse, - (impulse) => { - if (!impulse || !rbRef.current) - return; - - // eslint-disable-next-line prefer-const - let { direction, amplitude } = impulse; - amplitude *= 100; - const v = { x: direction[0] * amplitude, y: direction[1] * amplitude, z: direction[2] * amplitude }; - rbRef.current.applyImpulse(v, true); - runInAction(() => { - gameObj.pendingActions.impulse = undefined; - }); - }, - ); + runInAction(() => { gameObj.rb = () => rbRef.current; }); + return () => { runInAction(() => { gameObj.rb = () => null; }); }; }, - [gameObj.pendingActions, object.id] + // eslint-disable-next-line react-hooks/exhaustive-deps + [object.id], ); function handleClick(e: ThreeEvent) { diff --git a/webclient/src/components/scriptEditor/ScriptEditorView.tsx b/webclient/src/components/scriptEditor/ScriptEditorView.tsx index c181ab9..a88a039 100644 --- a/webclient/src/components/scriptEditor/ScriptEditorView.tsx +++ b/webclient/src/components/scriptEditor/ScriptEditorView.tsx @@ -85,6 +85,9 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV const current = objectTypeRef.current; const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace)); current.program = serialized; + + console.log(serialized); + try { current.javascript = generateCode(workspace); } diff --git a/webclient/src/model/gameFactory.ts b/webclient/src/model/gameFactory.ts index 82fc9b0..9e76c27 100644 --- a/webclient/src/model/gameFactory.ts +++ b/webclient/src/model/gameFactory.ts @@ -1,39 +1,18 @@ -import type { Game, RuntimeGameScene, RuntimeScene, World } from "../types"; -import type { GameEventContext, GameEventHandler, GeneratorEventHandler, InternalGameEventBus } from "../types/runtime/gameBus"; +import { toJS } from "mobx"; +import type { Game, RuntimeGameObjectInstance, RuntimeGameScene, RuntimeScene, ScriptAction, ScriptCoroutineContext, World } from "../types"; import { clone } from "../utils"; import { populateRuntimeScene } from "../utils/runtime"; -import { ObjectApi } from "../utils/runtime/objectApi"; -import type { GameScheduler, YieldCommand } from "./gameScheduler"; +import { ScriptGameEventBus } from "../scripts/eventBus"; +import * as ScriptActions from "../scripts/actions"; +import type { ScriptCoroutines } from "../scripts/coroutines"; -export class GameEventBus { - private handlers = new Map(); +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// type ScriptActionHelperFunction ScriptAction> = +// (...args: ConstructorParameters) => InstanceType; - on(event: string, handler: GameEventHandler): void { - console.log('subscribing to ' + event, handler); - let set = this.handlers.get(event); - if (!set) { - set = []; - this.handlers.set(event, set); - } - if (!set.includes(handler)) - set.push(handler); - } - - off(event: string, handler: GameEventHandler): void { - console.log('unubscribing from ' + event, handler); - const handlers = this.handlers.get(event); - if (!handlers) - return; - - this.handlers.set(event, handlers.filter((h) => h !== handler)); - } - - emit(event: string, context: GameEventContext): void { - console.log('emitting ' + event); - this.handlers.get(event) - ?.forEach((handler) => handler(context)); - } -} +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// const scriptActionHelperFunction = ScriptAction>(Cls: T): ScriptActionHelperFunction => +// (...args) => new Cls(...args) as InstanceType; export class GameFactory { @@ -66,47 +45,60 @@ export class GameFactory { public static evalGameScripts( world: World, scene: RuntimeGameScene, - eventBus: GameEventBus, - scheduler: GameScheduler, - ): void { + coroutines: ScriptCoroutines, + ): ScriptGameEventBus { - const handlerWrappers = new WeakMap(); + const eventBus = new ScriptGameEventBus(coroutines); - const wait = (seconds: number): YieldCommand => ({ type: 'waitSeconds', seconds }); - const waitFrames = (frames: number): YieldCommand => ({ type: 'waitFrames', frames }); - const waitUntil = (condition: () => boolean): YieldCommand => ({ type: 'waitUntil', condition }); - - const internalBus: InternalGameEventBus = { - onGameEvent(event, genFn) { - const wrapper: GameEventHandler = (ctx) => scheduler.startCoroutine(genFn(ctx) as Generator); - handlerWrappers.set(genFn, wrapper); - eventBus.on(event, wrapper); - }, - offGameEvent(event, genFn) { - const wrapper = handlerWrappers.get(genFn); - if (wrapper) { - eventBus.off(event, wrapper); - handlerWrappers.delete(genFn); - } - }, + const scriptHelpers = { + // convert all ScriptActions imported to a structure factory(coroutine): + // { + // WaitSeconds: (seconds) => new WaitSecondsScriptAction(coroutine, seconds), + // ... + // } + actions: (context: ScriptCoroutineContext) => Object.fromEntries( + Object.entries(ScriptActions) + .map(([name, Cls]) => [ + name.replace(/ScriptAction$/, ''), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (...args: any[]) => new (Cls as any)(context, ...args), + ]) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) as Record ScriptAction>, }; - try { - Object.values(world.objectTypes) - .filter((ot) => ot.javascript) - .forEach((ot) => { - const gameScript = eval(`(function gameScript({onGameEvent, offGameEvent, wait, waitFrames, waitUntil}, {api, object, objectType}) {${ot.javascript}})`); + Object.values(world.objectTypes) + .filter((ot) => ot.javascript) + .forEach((ot) => { + try { + const javascript = `(function gameScript(eventBus, helpers, thisObject, thisObjectType) {${ot.javascript}})`; + const gameScript = eval(javascript); Object.values(scene.objects) .filter((o) => o.typeId == ot.id) + .map((liveObj) => { + const snapshot = toJS(liveObj) as RuntimeGameObjectInstance; + snapshot.rb = () => liveObj.rb(); + return snapshot; + }) .forEach((o) => { - const api = new ObjectApi(o, ot, world, scene); - gameScript({ ...internalBus, wait, waitFrames, waitUntil }, { api, object: o, objectType: ot }); + try { + const localScriptHelpers = { + ...scriptHelpers, + actions: scriptHelpers.actions({ thisObject: o, thisObjectType: ot }), + }; + gameScript(eventBus, localScriptHelpers, o, ot); + } + catch (err) { + console.log('Error running object instance script:\n' + err); + } }); - }); - } - catch (err) { - console.log('Error running game script:\n' + err); - } + } + catch (err) { + console.log('Error evaluating object type script:\n' + err); + } + }); + + return eventBus; } } diff --git a/webclient/src/model/gameScheduler.ts b/webclient/src/model/gameScheduler.ts deleted file mode 100644 index 02cd33b..0000000 --- a/webclient/src/model/gameScheduler.ts +++ /dev/null @@ -1,66 +0,0 @@ -export type YieldCommand = - | { type: 'waitSeconds'; seconds: number } - | { type: 'waitFrames'; frames: number } - | { type: 'waitUntil'; condition: () => boolean }; - -type Coroutine = - & { gen: Generator } - & (| { wakeType: 'time'; remaining: number } - | { wakeType: 'frames'; remaining: number } - | { wakeType: 'condition'; test: () => boolean }); - -export class GameScheduler { - private coroutines: Coroutine[] = []; - - tick(deltaTime: number): void { - const next: Coroutine[] = []; - for (const co of this.coroutines) { - if (this.isReady(co)) -this.step(co, next); - else -next.push(this.countdown(co, deltaTime)); - } - this.coroutines = next; - } - - startCoroutine(gen: Generator): void { - const result = gen.next(); - if (!result.done) - this.coroutines.push(this.make(gen, result.value)); - } - - reset(): void { this.coroutines = []; } - - private isReady(co: Coroutine): boolean { - switch (co.wakeType) { - case 'time': return co.remaining <= 0; - case 'frames': return co.remaining <= 0; - case 'condition': return co.test(); - } - } - - private countdown(co: Coroutine, deltaTime: number): Coroutine { - switch (co.wakeType) { - case 'time': return { ...co, remaining: co.remaining - deltaTime }; - case 'frames': return { ...co, remaining: co.remaining - 1 }; - default: return co; - } - } - - private step(co: Coroutine, list: Coroutine[]): void { - const result = co.gen.next(); - if (!result.done) -list.push(this.make(co.gen, result.value)); - } - - private make(gen: Generator, cmd: YieldCommand): Coroutine { - switch (cmd.type) { - case 'waitSeconds': - return { gen, wakeType: 'time', remaining: cmd.seconds }; - case 'waitFrames': - return { gen, wakeType: 'frames', remaining: cmd.frames }; - case 'waitUntil': - return { gen, wakeType: 'condition', test: cmd.condition }; - } - } -} diff --git a/webclient/src/scripts/actions/applyImpulseAction.ts b/webclient/src/scripts/actions/applyImpulseAction.ts new file mode 100644 index 0000000..d43515e --- /dev/null +++ b/webclient/src/scripts/actions/applyImpulseAction.ts @@ -0,0 +1,32 @@ +import { ScriptAction, type ScriptCoroutineContext, type V3 } from "../../types"; + + +export class ApplyImpulseScriptAction extends ScriptAction { + + private targetPosition: V3; + private amplitude: number; + + constructor(coroutine: ScriptCoroutineContext, targetPosition: V3, amplitude: number) { + super(coroutine); + this.targetPosition = targetPosition; + this.amplitude = amplitude; + } + + public tick(_deltaTime: number): boolean { + const rb = this.coroutine.thisObject.rb(); + if (!rb) + return true; + + const { x: px, y: py, z: pz } = rb.translation(); + const dx = this.targetPosition[0] - px; + const dy = this.targetPosition[1] - py; + const dz = this.targetPosition[2] - pz; + const len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 1e-6) { + const scale = (this.amplitude * 100) / len; + rb.applyImpulse({ x: dx * scale, y: dy * scale, z: dz * scale }, true); + } + + return true; + } +} diff --git a/webclient/src/scripts/actions/index.ts b/webclient/src/scripts/actions/index.ts new file mode 100644 index 0000000..ff4e1ec --- /dev/null +++ b/webclient/src/scripts/actions/index.ts @@ -0,0 +1,4 @@ +export * from './waitConditionAction'; +export * from './waitFramesAction'; +export * from './waitSecondsAction'; +export * from './applyImpulseAction'; diff --git a/webclient/src/scripts/actions/waitConditionAction.ts b/webclient/src/scripts/actions/waitConditionAction.ts new file mode 100644 index 0000000..fc9dc28 --- /dev/null +++ b/webclient/src/scripts/actions/waitConditionAction.ts @@ -0,0 +1,15 @@ +import { ScriptAction, type ScriptCoroutineContext } from "../../types"; + +export class WaitConditionScriptAction extends ScriptAction { + + private condition: () => boolean; + + constructor(coroutine: ScriptCoroutineContext, condition: () => boolean) { + super(coroutine); + this.condition = condition; + } + + public tick(_deltaTime: number): boolean { + return this.condition(); + } +} diff --git a/webclient/src/scripts/actions/waitFramesAction.ts b/webclient/src/scripts/actions/waitFramesAction.ts new file mode 100644 index 0000000..2501277 --- /dev/null +++ b/webclient/src/scripts/actions/waitFramesAction.ts @@ -0,0 +1,16 @@ +import { ScriptAction, type ScriptCoroutineContext } from "../../types"; + +export class WaitFramesScriptAction extends ScriptAction { + + private remaining: number; + + constructor(coroutine: ScriptCoroutineContext, frames: number) { + super(coroutine); + this.remaining = frames; + } + + public tick(_deltaTime: number): boolean { + this.remaining--; + return this.remaining <= 0; + } +} diff --git a/webclient/src/scripts/actions/waitSecondsAction.ts b/webclient/src/scripts/actions/waitSecondsAction.ts new file mode 100644 index 0000000..e878af0 --- /dev/null +++ b/webclient/src/scripts/actions/waitSecondsAction.ts @@ -0,0 +1,16 @@ +import { ScriptAction, type ScriptCoroutineContext } from "../../types"; + +export class WaitSecondsScriptAction extends ScriptAction { + + private remaining: number; + + constructor(coroutine: ScriptCoroutineContext, seconds: number) { + super(coroutine); + this.remaining = seconds; + } + + public tick(deltaTime: number): boolean { + this.remaining -= deltaTime; // TODO should only consume as much time as needed and pass the remainder to next actions + return this.remaining <= 0; + } +} diff --git a/webclient/src/scripts/coroutines.ts b/webclient/src/scripts/coroutines.ts new file mode 100644 index 0000000..217239e --- /dev/null +++ b/webclient/src/scripts/coroutines.ts @@ -0,0 +1,46 @@ +import type { ScriptAction, ScriptCoroutine } from "../types"; + +export class ScriptCoroutines { + private items: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }[] = []; + + public push(coroutines: ScriptCoroutine[]) { + this.items.push( + ...coroutines + .map((c) => ({ coroutine: c })), + ); + } + + public tick(deltaTime: number): void { + // execute coroutines one by one and remove the ones that have finished + this.items = this.items + .filter((c) => !this.tickItem(c, deltaTime)); + } + + private tickItem(item: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }, deltaTime: number): boolean { + + if (!item.currentAction) { + try { + const yieldResult = item.coroutine.next(); + if (yieldResult.done) + return true; // coroutine exhausted + item.currentAction = yieldResult.value; + } + catch (err) { + console.error(err); + return true; // coroutine failed to yield an action or return + } + } + + try { + const done = item.currentAction.tick(deltaTime); + if (done) + item.currentAction = undefined; + } + catch (err) { + console.error(err); + item.currentAction = undefined; + } + + return false; + } +} \ No newline at end of file diff --git a/webclient/src/scripts/eventBus.ts b/webclient/src/scripts/eventBus.ts new file mode 100644 index 0000000..84e7fc9 --- /dev/null +++ b/webclient/src/scripts/eventBus.ts @@ -0,0 +1,52 @@ +import type { ScriptContext, ScriptCoroutineContext, ScriptGameEventHandler } from "../types"; +import type { ScriptCoroutines } from "./coroutines"; + +export type ScriptCoroutineGameEventHandler = { + handler: ScriptGameEventHandler; + coroutine: ScriptCoroutineContext; +} + +export class ScriptGameEventBus { + private coroutines: ScriptCoroutines; + private handlers: Record = {}; + + constructor(coroutines: ScriptCoroutines) { + this.coroutines = coroutines; + } + + on(event: string, handler: ScriptGameEventHandler, coroutine: ScriptCoroutineContext): void { + console.log('subscribing to ' + event, handler, coroutine); + let set = this.handlers[event]; + if (!set) { + set = []; + this.handlers[event] = set; + } + + if (!set.some((h) => h.handler === handler)) + set.push({ handler, coroutine }); + } + + off(event: string, handler: ScriptGameEventHandler): void { + console.log('unubscribing from ' + event, handler); + const handlers = this.handlers[event]; + if (!handlers) + return; + + this.handlers[event] = handlers.filter((h) => h.handler !== handler); + } + + emit(event: string, context: Omit): void { + console.log('emitting ' + event); + + const coroutines = this.handlers[event] + ?.map((h) => { + console.log(`passing ${event} to ${h.handler}(${JSON.stringify(context)}, ${JSON.stringify(h.coroutine)})`); + return h.handler({ ...context, ...h.coroutine }); + }); + + console.log(coroutines); + + if (coroutines) + this.coroutines.push(coroutines); + } +} \ No newline at end of file diff --git a/webclient/src/state/gameState.ts b/webclient/src/state/gameState.ts index 1a11b5b..3ca368b 100644 --- a/webclient/src/state/gameState.ts +++ b/webclient/src/state/gameState.ts @@ -1,10 +1,10 @@ import { makeAutoObservable, reaction, toJS } from "mobx"; import type { WorldState } from "./worldState"; -import type { Game, Pos3, RuntimeGameScene } from "../types"; +import type { Game, Pos3, RuntimeGameScene, ScriptContext, ScriptCoroutineContext } from "../types"; import type { CameraProps } from "@react-three/fiber"; -import { GameEventBus, GameFactory } from "../model/gameFactory"; -import { GameScheduler } from "../model/gameScheduler"; -import type { GameEventContext } from "../types/runtime/gameBus"; +import type { ScriptGameEventBus } from "../scripts/eventBus"; +import { GameFactory } from "../model/gameFactory"; +import { ScriptCoroutines } from "../scripts/coroutines"; export class GameState { private readonly world: WorldState; @@ -14,8 +14,8 @@ export class GameState { public isFirstTick: boolean; public scene: RuntimeGameScene; - private eventBus: GameEventBus; - private scheduler: GameScheduler; + private coroutines = new ScriptCoroutines(); + private eventBus: ScriptGameEventBus; private startAutoSave() { return reaction( @@ -45,17 +45,13 @@ export class GameState { this.isFirstTick = true; this.scene = game.scene; - const eventBus = new GameEventBus(); - this.eventBus = eventBus; - this.scheduler = new GameScheduler(); - this._stopAutoSave = this.startAutoSave(); makeAutoObservable(this); - GameFactory.evalGameScripts(rawWorld, this.scene, eventBus, this.scheduler); + this.eventBus = GameFactory.evalGameScripts(rawWorld, this.scene, this.coroutines); } - public emitEvent(event: string, context: Omit = {}) { + public emitEvent(event: string, context: Omit = {}) { this.eventBus.emit( event, { @@ -128,7 +124,8 @@ export class GameState { if (this.isPaused) return; - this.scheduler.tick(deltaTime); + this.coroutines.tick(deltaTime); + this.time += deltaTime; if (this.isFirstTick) { diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts index eafa3a5..6c17595 100644 --- a/webclient/src/types/index.ts +++ b/webclient/src/types/index.ts @@ -1,3 +1,4 @@ export * from './3d'; export * from './model'; export * from './runtime'; +export * from './scripts'; diff --git a/webclient/src/types/model/runtime.ts b/webclient/src/types/model/runtime.ts index 32dc31b..c53b4b6 100644 --- a/webclient/src/types/model/runtime.ts +++ b/webclient/src/types/model/runtime.ts @@ -2,6 +2,13 @@ import type { V3 } from "../3d"; import type { VoxelGroup } from "../runtime"; import type { ObjectInstance } from "./object"; +export type RigidBodyHandle = { + applyImpulse(impulse: { x: number; y: number; z: number }, wakeUp: boolean): void; + translation(): { x: number; y: number; z: number }; + linvel(): { x: number; y: number; z: number }; + angvel(): { x: number; y: number; z: number }; +}; + export type ObjectInstanceRuntimeData = { cache: { voxelGroups: VoxelGroup[]; @@ -9,9 +16,7 @@ export type ObjectInstanceRuntimeData = { boundingBox: { min: V3; max: V3 }; updatedAt: number; }; - pendingActions: { - impulse?: { direction: V3, amplitude: number }; - } + rb: () => RigidBodyHandle | null; } export type Runtime = T & ObjectInstanceRuntimeData; diff --git a/webclient/src/types/runtime/gameBus.ts b/webclient/src/types/runtime/gameBus.ts deleted file mode 100644 index 9845454..0000000 --- a/webclient/src/types/runtime/gameBus.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { RuntimeGameObjectInstance, Scene, World } from "../model"; - -export type GameEventContext = { - world: World; - scene: Scene; - object?: RuntimeGameObjectInstance; -} - -export type GameEventHandler = (context: GameEventContext) => void; - -export type GeneratorEventHandler = (context: GameEventContext) => Generator; - -export type InternalGameEventBus = { - onGameEvent: (event: string, handler: GeneratorEventHandler) => void; - offGameEvent: (event: string, handler: GeneratorEventHandler) => void; -} \ No newline at end of file diff --git a/webclient/src/types/scripts/actions/index.ts b/webclient/src/types/scripts/actions/index.ts new file mode 100644 index 0000000..f8fa4cb --- /dev/null +++ b/webclient/src/types/scripts/actions/index.ts @@ -0,0 +1,12 @@ +import type { ScriptCoroutineContext } from "../eventBus"; + +export abstract class ScriptAction { + + protected coroutine: ScriptCoroutineContext; + + constructor(coroutine: ScriptCoroutineContext) { + this.coroutine = coroutine; + } + + public abstract tick(deltaTime: number): boolean; // true means action has completed +} diff --git a/webclient/src/types/scripts/eventBus.ts b/webclient/src/types/scripts/eventBus.ts new file mode 100644 index 0000000..62dc99b --- /dev/null +++ b/webclient/src/types/scripts/eventBus.ts @@ -0,0 +1,17 @@ +import type { ObjectType, RuntimeGameObjectInstance, Scene, World } from "../model"; +import type { ScriptAction } from "./actions"; + +export type ScriptCoroutine = Generator; + +export type ScriptCoroutineContext = { + thisObject: RuntimeGameObjectInstance, + thisObjectType: ObjectType; +}; + +export type ScriptContext = ScriptCoroutineContext & { + world: World; + scene: Scene; + object?: RuntimeGameObjectInstance; +} + +export type ScriptGameEventHandler = (context: ScriptContext) => ScriptCoroutine; diff --git a/webclient/src/types/scripts/index.ts b/webclient/src/types/scripts/index.ts new file mode 100644 index 0000000..c2069e0 --- /dev/null +++ b/webclient/src/types/scripts/index.ts @@ -0,0 +1,2 @@ +export * from './eventBus'; +export * from './actions'; diff --git a/webclient/src/utils/runtime/object.ts b/webclient/src/utils/runtime/object.ts index 4fcea2d..a1f87a6 100644 --- a/webclient/src/utils/runtime/object.ts +++ b/webclient/src/utils/runtime/object.ts @@ -29,11 +29,11 @@ export function populateRuntimeObject(object: ObjectInstance, world: World): Run boundingBox: computeBoundingBox(objectType), updatedAt: 0, }, - pendingActions: {}, + rb: () => null, }; } export function depopulateRuntimeObject(object: RuntimeObjectInstance, _world: World): ObjectInstance { - const { cache: _cache, ...result } = object; + const { cache: _cache, rb: _rb, ...result } = object; return result; } diff --git a/webclient/src/utils/runtime/objectApi.ts b/webclient/src/utils/runtime/objectApi.ts deleted file mode 100644 index e0953f0..0000000 --- a/webclient/src/utils/runtime/objectApi.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ObjectType, RuntimeGameObjectInstance, RuntimeGameScene, V3, World } from "../../types"; - -export class ObjectApi { - private object: RuntimeGameObjectInstance; - private obectType: ObjectType; - private world: World; - private scene: RuntimeGameScene; - - constructor( - object: RuntimeGameObjectInstance, - obectType: ObjectType, - world: World, - scene: RuntimeGameScene, - ) { - this.object = object; - this.obectType = obectType; - this.world = world; - this.scene = scene; - } - - public applyImpulse(targetPosition: V3, amplitude: number) { - const [px, py, pz] = this.object.position; - const dx = targetPosition[0] - px; - const dy = targetPosition[1] - py; - const dz = targetPosition[2] - pz; - const len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len < 1e-6) - return; - const direction: V3 = [dx / len, dy / len, dz / len]; - this.object.pendingActions.impulse = { direction, amplitude }; - } -} diff --git a/webclient/vite.config.ts b/webclient/vite.config.ts index 4a5def4..cbb3ffe 100644 --- a/webclient/vite.config.ts +++ b/webclient/vite.config.ts @@ -4,4 +4,8 @@ import react from '@vitejs/plugin-react'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server: { + host: '0.0.0.0', + port: 8888, + } });