From c2387ccd8b954dac2b614f0ad157c3b2c74ee8ce Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:47:44 +1000 Subject: [PATCH 01/14] Bump packages; add other packages; update paths for SDK. --- package-lock.json | 509 ++++++++++++++++++++++++++++++---------------- package.json | 16 +- quasar.config.js | 5 +- 3 files changed, 343 insertions(+), 187 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae1b4d31..ea73e7d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,24 +14,28 @@ "@babylonjs/inspector": "^7.24.0", "@babylonjs/loaders": "^7.24.0", "@quasar/extras": "^1.16.12", + "@supabase/supabase-js": "^2.45.4", "@vircadia/web-sdk": "^2024.1.0", "@vueuse/core": "^11.0.3", "ammojs-typed": "^1.0.6", + "console-log-colors": "^0.5.0", "dompurify": "^3.0.3", "eight-colors": "^1.3.0", + "mobx": "^6.10.0", "pinia": "^2.2.2", - "quasar": "^2.16.10", + "quasar": "^2.17.0", "unique-names-generator": "^4.7.1", "vue": "^3.5.3", - "vue-router": "^4.4.3" + "vue-router": "^4.4.3", + "zod": "^3.23.8" }, "devDependencies": { "@jitsi/vue-sdk": "^1.0.4", - "@quasar/app-vite": "^1.9.3", + "@quasar/app-vite": "^1.10.0", "@types/dompurify": "^3.0.2", "@types/node": "^12.20.21", - "@typescript-eslint/eslint-plugin": "^5.10.0", - "@typescript-eslint/parser": "^5.10.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "autoprefixer": "^10.4.2", "eslint": "^8.10.0", "eslint-config-standard": "^17.0.0", @@ -44,7 +48,7 @@ "typescript": "^5.5.4" }, "engines": { - "node": "^20 || ^18", + "node": "^22 || ^20", "npm": ">= 10.1.0", "yarn": ">= 1.21.1" } @@ -529,7 +533,9 @@ } }, "node_modules/@quasar/app-vite": { - "version": "1.9.5", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@quasar/app-vite/-/app-vite-1.10.0.tgz", + "integrity": "sha512-9zJfSBh1GtsmS1c3zIKa78gwPAMJoJ4AHv7KftkaaLQ4F1z2DpoqKbJG8YiXVR9STOronPAh+4JNypFP1RHFfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -561,7 +567,7 @@ "open": "^8.4.0", "register-service-worker": "^1.7.2", "rollup-plugin-visualizer": "^5.5.4", - "sass": "^1.33.0", + "sass": "^1.79.1", "semver": "^7.3.5", "serialize-javascript": "^6.0.0", "table": "^6.8.0", @@ -669,6 +675,80 @@ "node": ">= 8.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.0.tgz", + "integrity": "sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", + "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.1.tgz", + "integrity": "sha512-EOSEZFm5pPuCPGCmLF1VOCS78DfkSz600PBuvBND/IZmMciJ1pmsS3ss6TkB6UkuvTybYiBh7gKOYyxoEO3USA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz", + "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.0.tgz", + "integrity": "sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.4", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.4.tgz", + "integrity": "sha512-E5p8/zOLaQ3a462MZnmnz03CrduA5ySH9hZyL03Y+QZLIOO4/Gs8Rdy4ZCKDHsN7x0xdanVEWWFN3pJFQr9/hg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.65.0", + "@supabase/functions-js": "2.4.1", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.1", + "@supabase/realtime-js": "2.10.2", + "@supabase/storage-js": "2.7.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "dev": true, @@ -761,11 +841,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json5": { "version": "0.0.29", "dev": true, @@ -778,7 +853,12 @@ }, "node_modules/@types/node": { "version": "12.20.55", - "dev": true, + "license": "MIT" + }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", "license": "MIT" }, "node_modules/@types/prop-types": { @@ -819,11 +899,6 @@ "@types/react": "*" } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "0.17.4", "dev": true, @@ -852,32 +927,42 @@ "version": "0.0.20", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -886,24 +971,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -912,15 +1000,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -928,25 +1018,24 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "*" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -954,11 +1043,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -966,20 +1057,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -991,41 +1085,84 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1477,14 +1614,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.5", "dev": true, @@ -2132,6 +2261,15 @@ "dev": true, "license": "MIT" }, + "node_modules/console-log-colors": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/console-log-colors/-/console-log-colors-0.5.0.tgz", + "integrity": "sha512-ixjGY06zMLCnNXeBdDKL6GuS1+/yQ1GorbZ9YsptP6jDgLqk/bBdQ5cTQvVveKUbO8Th6zwIhY8yRwhrpjXk6g==", + "license": "MIT", + "engines": { + "node": ">= 4.1.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "dev": true, @@ -2404,17 +2542,6 @@ "node": ">=8" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -3006,18 +3133,6 @@ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/eslint-utils": { "version": "3.0.0", "dev": true, @@ -3159,14 +3274,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "2.0.2", "license": "MIT" @@ -3690,25 +3797,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.0.1", "dev": true, @@ -4766,6 +4854,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mobx": { + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.2.tgz", + "integrity": "sha512-GIubI2qf+P6lG6rSEG0T2pg3jV9/0+O0ncF09+0umRe75+Cbnh1KNLM1GvbTY9RSc7QuU+LcPNZfxDY8B+3XRg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, "node_modules/mri": { "version": "1.2.0", "dev": true, @@ -4805,11 +4903,6 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "node_modules/negotiator": { "version": "0.6.3", "dev": true, @@ -5198,14 +5291,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.0.1", "license": "ISC" @@ -5382,9 +5467,9 @@ } }, "node_modules/quasar": { - "version": "2.16.10", - "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.16.10.tgz", - "integrity": "sha512-n4fPFabxtgUO2gsYXpg6WYkx0eTDi+CGyTmbI+HqiYQlGqAIa8qjuOIuKzX/wrkg5vKx4PvnCthTImOYHGT8Hg==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.17.0.tgz", + "integrity": "sha512-xFWwCt4FGuaC0M4/MA5drjBiCP7kj/5BsUPv2+dDIlyQG9YGvKIewCnWYYt02r4ijRqJSzPb7TsH89Gzkno1Mg==", "license": "MIT", "engines": { "node": ">= 10.18.1", @@ -5753,11 +5838,13 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.77.8", + "version": "1.79.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.3.tgz", + "integrity": "sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", + "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" }, @@ -5768,6 +5855,36 @@ "node": ">=14.0.0" } }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", + "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/sax": { "version": "1.1.4", "dev": true, @@ -5934,14 +6051,6 @@ "dev": true, "license": "ISC" }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/slice-ansi": { "version": "4.0.0", "dev": true, @@ -6286,6 +6395,25 @@ "node": ">=0.6" } }, + "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/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "dev": true, @@ -6310,25 +6438,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/tsutils": { - "version": "3.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -6671,6 +6780,12 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack-merge": { "version": "5.10.0", "dev": true, @@ -6684,6 +6799,16 @@ "node": ">=10.0.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "dev": true, @@ -6792,6 +6917,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "dev": true, @@ -6876,6 +7022,15 @@ "engines": { "node": ">= 10" } + }, + "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 e418e2e3..e5e22923 100644 --- a/package.json +++ b/package.json @@ -29,19 +29,23 @@ "ammojs-typed": "^1.0.6", "dompurify": "^3.0.3", "eight-colors": "^1.3.0", + "mobx": "^6.10.0", "pinia": "^2.2.2", - "quasar": "^2.16.10", + "quasar": "^2.17.0", "unique-names-generator": "^4.7.1", "vue": "^3.5.3", - "vue-router": "^4.4.3" + "vue-router": "^4.4.3", + "zod": "^3.23.8", + "@supabase/supabase-js": "^2.45.4", + "console-log-colors": "^0.5.0" }, "devDependencies": { "@jitsi/vue-sdk": "^1.0.4", - "@quasar/app-vite": "^1.9.3", + "@quasar/app-vite": "^1.10.0", "@types/dompurify": "^3.0.2", "@types/node": "^12.20.21", - "@typescript-eslint/eslint-plugin": "^5.10.0", - "@typescript-eslint/parser": "^5.10.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "autoprefixer": "^10.4.2", "eslint": "^8.10.0", "eslint-config-standard": "^17.0.0", @@ -54,7 +58,7 @@ "typescript": "^5.5.4" }, "engines": { - "node": "^20 || ^18", + "node": "^22 || ^20", "npm": ">= 10.1.0", "yarn": ">= 1.21.1" }, diff --git a/quasar.config.js b/quasar.config.js index f522f6b8..7d589f1a 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -92,10 +92,7 @@ module.exports = configure(function (ctx) { "@Modules": path.resolve(__dirname, "./src/modules"), "@Public": path.resolve(__dirname, "./public"), "@Stores": path.resolve(__dirname, "./src/stores"), - "@World-Client": path.resolve( - __dirname, - "./src/vircadia-world/typescript/src/client" - ), + "@World": path.resolve(__dirname, "./src/vircadia-world/src"), }, env: { From 08d9c2d65457ee5edb5f66704ee466d27da9547b Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:48:02 +1000 Subject: [PATCH 02/14] Update paths and include paths. --- tsconfig.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 9fd9687f..6de224a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,12 @@ "@Modules/*": ["./src/modules/*"], "@Public/*": ["./public/*"], "@Stores/*": ["./src/stores/*"], - "@World-Client/*": ["./src/vircadia-world/typescript/src/client/*"] + "@World/*": ["./src/vircadia-world/src/*"] } }, - "include": ["src/**/*", "src/vircadia-world/src/**/*"] + "include": [ + "src/**/*", + "src/vircadia-world/src/**/*", + "src/vircadia-world/src/shared/modules/vircadia-world-meta/**/*" + ] } From c8e0866deafa03a5f80d7aab0685a9d73dcd4dd0 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:48:52 +1000 Subject: [PATCH 03/14] Mild refactor; begin adding Script functionality. --- README.md | 5 + src/App.vue | 9 +- src/components/AudioLevel.vue | 138 +- src/components/overlays/settings/Audio.vue | 466 +++--- .../components/components/ModelComponent.ts | 20 +- src/modules/scene/ScriptManager.ts | 100 ++ src/modules/scene/renderer.ts | 2 +- src/modules/scene/soundEffects.ts | 68 +- src/modules/scene/vscene.ts | 3 +- src/pages/FirstTimeSetup.vue | 1334 ++++++++--------- src/vircadia-world | 2 +- types/vircadia_gameUse.ts | 4 + 12 files changed, 1132 insertions(+), 1019 deletions(-) create mode 100644 src/modules/scene/ScriptManager.ts diff --git a/README.md b/README.md index 89525de7..f204b5e8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,11 @@ Vircadia Web relies on the Vircadia Assets submodule, so you must pull recursive git clone --recursive https://github.com/vircadia/vircadia-web.git ``` +If you forget to pull recursively, you can initialize the submodule later with: +```sh +git submodule update --init --recursive +``` + ### Install the dependencies ```sh diff --git a/src/App.vue b/src/App.vue index 7146d044..369d14bd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,14 +14,13 @@ import { onMounted } from "vue"; import { applicationStore } from "@Stores/index"; import { Utility } from "@Modules/utility"; -import { Client } from "@World-Client/client"; import Log from "@Modules/debugging/log"; // Fetch and initialize configuration info Log.debug(Log.types.OTHER, `APP: Initialize`); Utility.initializeConfig(); -onMounted(async () => { +onMounted(() => { // Log the SDK version. console.log("Starting Vircadia Web using SDK version:", applicationStore.globalConsts.SDK_VERSION_TAG); // Called after the APP is visible. This starts the engines doing things. @@ -32,11 +31,5 @@ onMounted(async () => { if (preloader) { preloader.classList.add("hide"); } - - // await Client.Setup.InitializeVircadiaWorld({ - // host: "http://localhost", - // port: 3000, - // agentId: "1234567890", - // }); }); diff --git a/src/components/AudioLevel.vue b/src/components/AudioLevel.vue index 05f4ec98..15e8b3a5 100644 --- a/src/components/AudioLevel.vue +++ b/src/components/AudioLevel.vue @@ -1,69 +1,69 @@ - - - - - - - + + + + + + + diff --git a/src/components/overlays/settings/Audio.vue b/src/components/overlays/settings/Audio.vue index d71a6aff..cd39fa4f 100644 --- a/src/components/overlays/settings/Audio.vue +++ b/src/components/overlays/settings/Audio.vue @@ -1,233 +1,233 @@ - - - - - + + + + + diff --git a/src/modules/entity/components/components/ModelComponent.ts b/src/modules/entity/components/components/ModelComponent.ts index 2ea785fb..be04e2d6 100644 --- a/src/modules/entity/components/components/ModelComponent.ts +++ b/src/modules/entity/components/components/ModelComponent.ts @@ -25,6 +25,7 @@ import Log from "@Modules/debugging/log"; import { LODManager } from "@Modules/scene/LODManager"; import { LightmapManager } from "@Modules/scene/LightmapManager"; import { LightManager } from "@Modules/scene/LightManager"; +import { ScriptManager } from "@Modules/scene/ScriptManager"; const InteractiveModelTypes = [ { name: "chair", condition: /^(?:animate_sitting|animate_seat)/iu }, @@ -59,26 +60,35 @@ export class ModelComponent extends MeshComponent { this._modelURL = entity.modelURL; + const scene = this._gameObject.getScene(); + void SceneLoader.ImportMeshAsync( "", entity.modelURL, undefined, - this._gameObject.getScene(), + scene, (event) => { updateContentLoadingProgress(event, entity.name); } ) .then((result) => { + + let meshes = result.meshes; // LOD Handling meshes = LODManager.setLODLevels(meshes); // Lightmap Handling - if (this._gameObject?.getScene()) { - meshes = LightmapManager.applySceneLightmapsToMeshes(meshes, this._gameObject.getScene()); + if (scene) { + meshes = LightmapManager.applySceneLightmapsToMeshes(meshes, scene); } // Light Handling - if (this._gameObject?.getScene()) { - LightManager.applyLightProperties(meshes, this._gameObject.getScene()); + if (scene) { + LightManager.applyLightProperties(meshes, scene); + } + + // Script Handling + if (scene) { + ScriptManager.executeScriptsOnMeshes(meshes); } this.mesh = meshes[0]; diff --git a/src/modules/scene/ScriptManager.ts b/src/modules/scene/ScriptManager.ts new file mode 100644 index 00000000..eeaf9090 --- /dev/null +++ b/src/modules/scene/ScriptManager.ts @@ -0,0 +1,100 @@ +// +// ScriptManager.ts +// +// Created by Kalila on 23 Sep 2024. +// Copyright 2024 Vircadia contributors. +// Copyright 2024 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { + + type AbstractMesh, + +} from "@babylonjs/core"; +import { transpile } from "typescript"; +import Log from "../debugging/log"; +import { glTF as MeshTypes } from "../../../types/vircadia_gameUse"; + +export class ScriptManager { + public static executeScriptsOnMeshes(meshes: AbstractMesh[]): void { + for (const mesh of meshes) { + const metadataExtras = mesh?.metadata?.gltf?.extras ?? mesh?.parent?.metadata?.gltf?.extras; + + const meshMetadata = new MeshTypes.Metadata(metadataExtras as Partial); + + let script: string = meshMetadata.vircadia_script; + + if (!script) { + // Create a test script default + script = ` + mesh.actionManager = new BABYLON.ActionManager(mesh._scene); + + mesh.actionManager.registerAction(new BABYLON.ExecuteCodeAction( + BABYLON.ActionManager.OnEveryFrameTrigger, + function () { + + mesh.rotation.y += BABYLON.Tools.ToRadians(0.1); + + } + )); + `; + } + + try { + this.executeWithContext(script, { mesh }); + Log.debug( + + Log.types.ENTITIES, + `### Executed script for mesh ${mesh.name}.` + + ); + + } catch (error) { + + Log.error( + + Log.types.ENTITIES, + + `Failed to execute script for mesh ${mesh.name}: ${error}` + ); + + } + + } + } + + private static transpile(script: string): string { + Log.info(Log.types.ENTITIES, `Transpiling script: ${script}`); + + const transpiledScript: string = (transpile as (input: string) => string)(script); + + Log.info(Log.types.ENTITIES, `Transpiled script: ${transpiledScript}`); + + return transpiledScript; + } + + private static wrapAndTranspile(script: string, contextKeys: string[]): string { + const contextParamsString = contextKeys.join(', '); + const wrappedScript = ` + (function(${contextParamsString}) { + ${script} + }); + `; + + return this.transpile(wrappedScript); + } + + static executeWithContext(script: string, context: Record): any { + const contextKeys = Object.keys(context); + const wrappedAndTranspiledScript = this.wrapAndTranspile(script, contextKeys); + + Log.info(Log.types.ENTITIES, `Executing script with context: ${wrappedAndTranspiledScript}`); + + // eslint-disable-next-line no-eval + const scriptFunction = eval(wrappedAndTranspiledScript) as (...args: unknown[]) => unknown; + return scriptFunction(...Object.values(context) as unknown[]); + } +} diff --git a/src/modules/scene/renderer.ts b/src/modules/scene/renderer.ts index e42de957..bd6b8e8a 100644 --- a/src/modules/scene/renderer.ts +++ b/src/modules/scene/renderer.ts @@ -19,7 +19,7 @@ import { CustomLoadingScreen } from "@Modules/scene/LoadingScreen"; * Static methods controlling the rendering of the scene(s). */ export class Renderer { - private static _engine = undefined; + private static _engine = undefined; private static _renderingScenes = undefined; private static _webgpuSupported = false; private static _intervalId = >null; diff --git a/src/modules/scene/soundEffects.ts b/src/modules/scene/soundEffects.ts index 5fa7d3ba..76628fc6 100644 --- a/src/modules/scene/soundEffects.ts +++ b/src/modules/scene/soundEffects.ts @@ -1,34 +1,34 @@ -// -// soundEffects.ts -// -// Created by Giga on 12 Jan 2023. -// Copyright 2023 Vircadia contributors. -// Copyright 2023 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -const SFX = { - SFXMessageNotification: "/assets/audio/sound effects/bubblepop.ogg" -}; - -const DEFAULT_SFX_VOLUME = 0.5; - -/** - * Play a sound effect. - * @param sound The sound effect to play. - * @param loop Set `true` to loop the sound effect (optional). - * @param volume The volume of the sound effect (optional). - */ -export async function playSound(sound: keyof typeof SFX, loop = false, volume = DEFAULT_SFX_VOLUME): Promise { - // Create the audio element for the sound effect. - const newSoundEffect = new Audio(SFX[sound]); - - // Configure the audio element. - newSoundEffect.loop = Boolean(loop); - newSoundEffect.volume = Number(volume); - - // Play the sound effect. - await newSoundEffect.play(); -} +// +// soundEffects.ts +// +// Created by Giga on 12 Jan 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const SFX = { + SFXMessageNotification: "/assets/audio/sound effects/bubblepop.ogg" +}; + +const DEFAULT_SFX_VOLUME = 0.5; + +/** + * Play a sound effect. + * @param sound The sound effect to play. + * @param loop Set `true` to loop the sound effect (optional). + * @param volume The volume of the sound effect (optional). + */ +export async function playSound(sound: keyof typeof SFX, loop = false, volume = DEFAULT_SFX_VOLUME): Promise { + // Create the audio element for the sound effect. + const newSoundEffect = new Audio(SFX[sound]); + + // Configure the audio element. + newSoundEffect.loop = Boolean(loop); + newSoundEffect.volume = Number(volume); + + // Play the sound effect. + await newSoundEffect.play(); +} diff --git a/src/modules/scene/vscene.ts b/src/modules/scene/vscene.ts index d6cb065a..fdfa9e02 100644 --- a/src/modules/scene/vscene.ts +++ b/src/modules/scene/vscene.ts @@ -26,6 +26,7 @@ import { Quaternion, Vector3, Color4, + WebGPUEngine, } from "@babylonjs/core"; import Ammo from "ammojs-typed"; import "@babylonjs/loaders/glTF"; @@ -72,7 +73,7 @@ const AvatarAnimationUrl = "/assets/animations/AnimationsBasic.glb"; */ export class VScene { _sceneId: number; - _engine: Engine; + _engine: Engine | WebGPUEngine; _scene: Scene; private _css3DRenderer: Nullable = null; _myAvatar: Nullable = null; diff --git a/src/pages/FirstTimeSetup.vue b/src/pages/FirstTimeSetup.vue index be95ab1b..68f851f8 100644 --- a/src/pages/FirstTimeSetup.vue +++ b/src/pages/FirstTimeSetup.vue @@ -1,667 +1,667 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/vircadia-world b/src/vircadia-world index 46e1d82f..833754ff 160000 --- a/src/vircadia-world +++ b/src/vircadia-world @@ -1 +1 @@ -Subproject commit 46e1d82f9d79e1f10e7e14d1b386a56a26d768a2 +Subproject commit 833754ffa610b32161b55c6f2d602dd10021880d diff --git a/types/vircadia_gameUse.ts b/types/vircadia_gameUse.ts index 8eca9e91..eaf889a2 100644 --- a/types/vircadia_gameUse.ts +++ b/types/vircadia_gameUse.ts @@ -15,6 +15,8 @@ export namespace glTF { vircadia_lightmap_texcoord: number | null; vircadia_lightmap_use_as_shadowmap: boolean | null; vircadia_lightmap_mode: Light.LightmapMode | null; + // Script + vircadia_script: string | null; } export class Metadata implements MetadataInterface { @@ -35,6 +37,8 @@ export namespace glTF { public vircadia_lightmap_texcoord = null; public vircadia_lightmap_use_as_shadowmap = null; public vircadia_lightmap_mode = null; + // Script + public vircadia_script = null; constructor(metadata?: Partial>) { if (metadata) { From 6db84318acceb12e2d4063a80aa4e06469695465 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:41:25 +1000 Subject: [PATCH 04/14] Scripting works now. --- .../components/components/ModelComponent.ts | 2 +- src/modules/scene/ScriptManager.ts | 42 ++++++------------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/modules/entity/components/components/ModelComponent.ts b/src/modules/entity/components/components/ModelComponent.ts index be04e2d6..a17306c9 100644 --- a/src/modules/entity/components/components/ModelComponent.ts +++ b/src/modules/entity/components/components/ModelComponent.ts @@ -88,7 +88,7 @@ export class ModelComponent extends MeshComponent { // Script Handling if (scene) { - ScriptManager.executeScriptsOnMeshes(meshes); + ScriptManager.executeScriptsOnMeshes(meshes, scene); } this.mesh = meshes[0]; diff --git a/src/modules/scene/ScriptManager.ts b/src/modules/scene/ScriptManager.ts index eeaf9090..b64c660e 100644 --- a/src/modules/scene/ScriptManager.ts +++ b/src/modules/scene/ScriptManager.ts @@ -10,68 +10,50 @@ // import { - type AbstractMesh, - + Scene, + ActionManager } from "@babylonjs/core"; +import * as BABYLON from "@babylonjs/core"; import { transpile } from "typescript"; import Log from "../debugging/log"; import { glTF as MeshTypes } from "../../../types/vircadia_gameUse"; export class ScriptManager { - public static executeScriptsOnMeshes(meshes: AbstractMesh[]): void { + public static executeScriptsOnMeshes(meshes: AbstractMesh[], scene: Scene): void { for (const mesh of meshes) { const metadataExtras = mesh?.metadata?.gltf?.extras ?? mesh?.parent?.metadata?.gltf?.extras; const meshMetadata = new MeshTypes.Metadata(metadataExtras as Partial); - let script: string = meshMetadata.vircadia_script; + const script: string = meshMetadata.vircadia_script; if (!script) { - // Create a test script default - script = ` - mesh.actionManager = new BABYLON.ActionManager(mesh._scene); - - mesh.actionManager.registerAction(new BABYLON.ExecuteCodeAction( - BABYLON.ActionManager.OnEveryFrameTrigger, - function () { - - mesh.rotation.y += BABYLON.Tools.ToRadians(0.1); - - } - )); - `; + continue; } try { - this.executeWithContext(script, { mesh }); + this.executeWithContext(script, { mesh, BABYLON, scene }); Log.debug( - Log.types.ENTITIES, - `### Executed script for mesh ${mesh.name}.` - + `Executed script for mesh ${mesh.name}.` ); - } catch (error) { - Log.error( - Log.types.ENTITIES, - `Failed to execute script for mesh ${mesh.name}: ${error}` ); - + throw error; } - } } private static transpile(script: string): string { - Log.info(Log.types.ENTITIES, `Transpiling script: ${script}`); + Log.debug(Log.types.ENTITIES, `Transpiling script: ${script}`); const transpiledScript: string = (transpile as (input: string) => string)(script); - Log.info(Log.types.ENTITIES, `Transpiled script: ${transpiledScript}`); + Log.debug(Log.types.ENTITIES, `Transpiled script: ${transpiledScript}`); return transpiledScript; } @@ -91,7 +73,7 @@ export class ScriptManager { const contextKeys = Object.keys(context); const wrappedAndTranspiledScript = this.wrapAndTranspile(script, contextKeys); - Log.info(Log.types.ENTITIES, `Executing script with context: ${wrappedAndTranspiledScript}`); + Log.debug(Log.types.ENTITIES, `Executing script with context: ${wrappedAndTranspiledScript}`); // eslint-disable-next-line no-eval const scriptFunction = eval(wrappedAndTranspiledScript) as (...args: unknown[]) => unknown; From ee95ca6d15626407dd3f883584b195739c2fdc4a Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:23:40 +1000 Subject: [PATCH 05/14] Make skybox not pickable. --- src/modules/entity/components/components/SkyboxComponent.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/entity/components/components/SkyboxComponent.ts b/src/modules/entity/components/components/SkyboxComponent.ts index a6d9d24c..10be740f 100644 --- a/src/modules/entity/components/components/SkyboxComponent.ts +++ b/src/modules/entity/components/components/SkyboxComponent.ts @@ -59,6 +59,9 @@ export class SkyboxComponent extends MeshComponent { } skyBox.infiniteDistance = true; skyBox.id = id; + skyBox.isPickable = false; + skyBox.isNearGrabbable = false; + skyBox.isNearPickable = false; skyBox.renderingGroupId = DEFAULT_MESH_RENDER_GROUP_ID; this.mesh = skyBox; From f0634882559fbb77fc0cdc60ff830154a58d2125 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:23:48 +1000 Subject: [PATCH 06/14] Fix scripting timing. --- src/modules/scene/ScriptManager.ts | 66 ++++++++++++++++++++++++++---- src/vircadia-world | 2 +- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/modules/scene/ScriptManager.ts b/src/modules/scene/ScriptManager.ts index b64c660e..8e9f104b 100644 --- a/src/modules/scene/ScriptManager.ts +++ b/src/modules/scene/ScriptManager.ts @@ -26,26 +26,76 @@ export class ScriptManager { const meshMetadata = new MeshTypes.Metadata(metadataExtras as Partial); - const script: string = meshMetadata.vircadia_script; + if (!scene.actionManager) { + scene.actionManager = new ActionManager(scene); + } + + if (!mesh.actionManager) { + mesh.actionManager = new ActionManager(scene); + } + + let script: string = meshMetadata.vircadia_script ?? ''; if (!script) { - continue; + script = this.getDefaultScript(); } try { - this.executeWithContext(script, { mesh, BABYLON, scene }); - Log.debug( - Log.types.ENTITIES, - `Executed script for mesh ${mesh.name}.` - ); + // Wrap the script execution in a setTimeout to ensure the mesh is fully initialized + setTimeout(() => { + this.executeWithContext(script, { mesh, BABYLON, scene }); + Log.debug( + Log.types.ENTITIES, + `Executed script for mesh ${mesh.name}.` + ); + }, 0); } catch (error) { Log.error( Log.types.ENTITIES, `Failed to execute script for mesh ${mesh.name}: ${error}` ); - throw error; + // Consider how you want to handle this error. Throwing might stop processing other meshes. + // throw error; + } + } + } + + private static getDefaultScript(): string { + return ` + if (mesh && mesh.actionManager) { + mesh.isPickable = true; + console.info('Mesh action manager: ' + mesh.actionManager + ', mesh is pickable: ' + mesh.isPickable); + + // Hover to scale + try { + mesh.actionManager.registerAction( + new BABYLON.ExecuteCodeAction( + BABYLON.ActionManager.OnPointerOverTrigger, + function() { + console.info('Hovering over ' + mesh.name); + mesh.scaling = new BABYLON.Vector3(1.2, 1.2, 1.2); + } + ) + ); + + mesh.actionManager.registerAction( + new BABYLON.ExecuteCodeAction( + BABYLON.ActionManager.OnPointerOutTrigger, + function() { + console.info('Hovering left ' + mesh.name); + mesh.scaling = BABYLON.Vector3.One(); + } + ) + ); + + console.log("Interactive script attached to " + mesh.name); + } catch (error) { + console.error("Failed to register hover actions for " + mesh.name + ": " + error); } + } else { + console.warn("Mesh or action manager not available for " + (mesh ? mesh.name : "unknown mesh")); } + `; } private static transpile(script: string): string { diff --git a/src/vircadia-world b/src/vircadia-world index 833754ff..b434d685 160000 --- a/src/vircadia-world +++ b/src/vircadia-world @@ -1 +1 @@ -Subproject commit 833754ffa610b32161b55c6f2d602dd10021880d +Subproject commit b434d685d94b72dcb42ee3ab0a9942223b514075 From 0e9df71b5cf4d4b524356adaa61275253df5e4cc Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:16:58 +1000 Subject: [PATCH 07/14] Bump Babylon to 7.27.0 --- package-lock.json | 24 ++++++++++++------------ package.json | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea73e7d0..effc617c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@babylonjs/core": "^7.24.0", - "@babylonjs/inspector": "^7.24.0", - "@babylonjs/loaders": "^7.24.0", + "@babylonjs/core": "^7.27.0", + "@babylonjs/inspector": "^7.27.0", + "@babylonjs/loaders": "^7.27.0", "@quasar/extras": "^1.16.12", "@supabase/supabase-js": "^2.45.4", "@vircadia/web-sdk": "^2024.1.0", @@ -231,9 +231,9 @@ } }, "node_modules/@babylonjs/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.24.0.tgz", - "integrity": "sha512-B8fdGmr8D6VmCrCG/AEbdl8Q+lBv+itszqaMqHvEqSnumfRtNXXgATofc+SyF2IoCm1ax7f20hnXZBJDXjjE5g==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.27.0.tgz", + "integrity": "sha512-/GyZe1b0y/LfxAxX+i20Wtkr76fqah+OJZi2F5pgF/MSpm2KfJnSObeWqZDi01CAYM9zLXTfPiMupDdhUv54vw==", "license": "Apache-2.0" }, "node_modules/@babylonjs/gui": { @@ -260,9 +260,9 @@ } }, "node_modules/@babylonjs/inspector": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-7.24.0.tgz", - "integrity": "sha512-wzygKg3Yr0VUcxKXG+fL0s7B8W6uoJS1kX7O5VV+kVABXawQT+X0J7IxSAFjFNNWLGcZ0cftsibC0zHbri0rEw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-7.27.0.tgz", + "integrity": "sha512-91pCyK+0NdPFQvTW4d11Wevm/DsTHvBBNTalq5/KgCYuqjpsNr3C8G0RMDLW4EOH6jllLGuu76lPgCKj+EblIQ==", "license": "Apache-2.0", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.0", @@ -281,9 +281,9 @@ } }, "node_modules/@babylonjs/loaders": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.24.0.tgz", - "integrity": "sha512-175rubt9r8d/1xX65FDvza7IOaxMNguNAWiYnWbf8yFxcxFjRPTT/rKQr96HHnKaebA0giGb0Ywa5NtnT+dWzQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.27.0.tgz", + "integrity": "sha512-xBlVQ/m+UWhaMEn6q1j3ktu/GcrIxHEc9ybGOkOHst4Fk+5ebvFymjbscaQ20MEWa42Eue4x55rntDGUnCjYsw==", "license": "Apache-2.0", "peerDependencies": { "@babylonjs/core": "^7.0.0", diff --git a/package.json b/package.json index e5e22923..adc7504e 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "copy-workers": "node build_scripts/copyWorkers.mjs" }, "dependencies": { - "@babylonjs/core": "^7.24.0", - "@babylonjs/inspector": "^7.24.0", - "@babylonjs/loaders": "^7.24.0", + "@babylonjs/core": "^7.27.0", + "@babylonjs/inspector": "^7.27.0", + "@babylonjs/loaders": "^7.27.0", "@quasar/extras": "^1.16.12", "@vircadia/web-sdk": "^2024.1.0", "@vueuse/core": "^11.0.3", From f1d6339f94a4ce98ec4e8f9f457c37a8cd7915a4 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:43:12 +1000 Subject: [PATCH 08/14] Remove unused code. --- .../components/components/ModelComponent.ts | 1 - src/modules/scene/ScriptManager.ts | 44 ++----------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/modules/entity/components/components/ModelComponent.ts b/src/modules/entity/components/components/ModelComponent.ts index a17306c9..8beec529 100644 --- a/src/modules/entity/components/components/ModelComponent.ts +++ b/src/modules/entity/components/components/ModelComponent.ts @@ -85,7 +85,6 @@ export class ModelComponent extends MeshComponent { if (scene) { LightManager.applyLightProperties(meshes, scene); } - // Script Handling if (scene) { ScriptManager.executeScriptsOnMeshes(meshes, scene); diff --git a/src/modules/scene/ScriptManager.ts b/src/modules/scene/ScriptManager.ts index 8e9f104b..64782bde 100644 --- a/src/modules/scene/ScriptManager.ts +++ b/src/modules/scene/ScriptManager.ts @@ -34,16 +34,16 @@ export class ScriptManager { mesh.actionManager = new ActionManager(scene); } - let script: string = meshMetadata.vircadia_script ?? ''; + const script: string = meshMetadata.vircadia_script ?? ''; if (!script) { - script = this.getDefaultScript(); + continue; } try { // Wrap the script execution in a setTimeout to ensure the mesh is fully initialized setTimeout(() => { - this.executeWithContext(script, { mesh, BABYLON, scene }); + this.executeWithContext(script, { BABYLON, mesh, scene }); Log.debug( Log.types.ENTITIES, `Executed script for mesh ${mesh.name}.` @@ -60,44 +60,6 @@ export class ScriptManager { } } - private static getDefaultScript(): string { - return ` - if (mesh && mesh.actionManager) { - mesh.isPickable = true; - console.info('Mesh action manager: ' + mesh.actionManager + ', mesh is pickable: ' + mesh.isPickable); - - // Hover to scale - try { - mesh.actionManager.registerAction( - new BABYLON.ExecuteCodeAction( - BABYLON.ActionManager.OnPointerOverTrigger, - function() { - console.info('Hovering over ' + mesh.name); - mesh.scaling = new BABYLON.Vector3(1.2, 1.2, 1.2); - } - ) - ); - - mesh.actionManager.registerAction( - new BABYLON.ExecuteCodeAction( - BABYLON.ActionManager.OnPointerOutTrigger, - function() { - console.info('Hovering left ' + mesh.name); - mesh.scaling = BABYLON.Vector3.One(); - } - ) - ); - - console.log("Interactive script attached to " + mesh.name); - } catch (error) { - console.error("Failed to register hover actions for " + mesh.name + ": " + error); - } - } else { - console.warn("Mesh or action manager not available for " + (mesh ? mesh.name : "unknown mesh")); - } - `; - } - private static transpile(script: string): string { Log.debug(Log.types.ENTITIES, `Transpiling script: ${script}`); From 756c4b90bcc078a9fb99b6b1b71d7075fc3e0f5c Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:51:09 +1000 Subject: [PATCH 09/14] Fix desktop build. --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6f226162..dbe96195 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,7 +27,7 @@ module.exports = { parser: "@typescript-eslint/parser", project: resolve(__dirname, "./tsconfig.json"), tsconfigRootDir: __dirname, - ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features + ecmaVersion: 2022, // Allows for the parsing of modern ECMAScript features sourceType: "module", // Allows for the use of imports }, From eb8266b2e8493137b513af44411966568e29b15d Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Sat, 28 Sep 2024 00:00:28 +1000 Subject: [PATCH 10/14] Bump node vers for desktop build 16 -> 20 --- .github/workflows/build_desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_desktop.yml b/.github/workflows/build_desktop.yml index bd6a325b..5a5e62d8 100644 --- a/.github/workflows/build_desktop.yml +++ b/.github/workflows/build_desktop.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20.x - name: Install Rust uses: dtolnay/rust-toolchain@stable From 7b218d6107f0fd897ba8d1f833e434bf8a431f17 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:22:48 +1000 Subject: [PATCH 11/14] WIP --- quasar.config.js | 198 ++++++++++++++ src/modules/avatar/DefaultModels.ts | 321 ++++------------------ src/modules/avatar/StoreInterface.ts | 313 +++++++++++----------- src/stores/user-store.ts | 380 +++++++++++++-------------- 4 files changed, 592 insertions(+), 620 deletions(-) diff --git a/quasar.config.js b/quasar.config.js index 7d589f1a..22f50a7f 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -180,6 +180,204 @@ module.exports = configure(function (ctx) { process.env.VRCA_WIZARD_BUTTON_TEXT ?? "Get Started", // Desktop App VRCA_DESKTOP_MODE: process.env.VRCA_DESKTOP_MODE, + VRCA_DEFAULT_AVATARS: JSON.stringify([ + { + name: "Sara", + image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", + file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", + scale: 1, + starred: true, + }, + { + name: "Mark", + image: "/assets/models/avatars/Mark-small.webp", + file: "/assets/models/avatars/Mark.glb", + scale: 1, + starred: false, + }, + { + name: "Megan", + image: "/assets/models/avatars/Megan-small.webp", + file: "/assets/models/avatars/Megan.glb", + scale: 1, + starred: false, + }, + { + name: "Jack", + image: "/assets/models/avatars/Jack-small.webp", + file: "/assets/models/avatars/Jack.glb", + scale: 1, + starred: false, + }, + { + name: "Martha", + image: "/assets/models/avatars/Martha-small.webp", + file: "/assets/models/avatars/Martha.glb", + scale: 1, + starred: false, + }, + { + name: "Miles", + image: "/assets/models/avatars/Miles-small.webp", + file: "/assets/models/avatars/Miles.glb", + scale: 1, + starred: false, + }, + { + name: "Taylor", + image: "/assets/models/avatars/Taylor-small.webp", + file: "/assets/models/avatars/Taylor.glb", + scale: 1, + starred: false, + }, + { + name: "Tiffany", + image: "/assets/models/avatars/Tiffany-small.webp", + file: "/assets/models/avatars/Tiffany.glb", + scale: 1, + starred: false, + }, + { + name: "Victor", + image: "/assets/models/avatars/Victor-small.webp", + file: "/assets/models/avatars/Victor.glb", + scale: 1, + starred: false, + }, + { + name: "Audrey", + image: "/assets/models/avatars/Audrey-small.webp", + file: "/assets/models/avatars/Audrey.glb", + scale: 1, + starred: false, + }, + { + name: "Kristine", + image: "/assets/models/avatars/Kristine-small.webp", + file: "/assets/models/avatars/Kristine.glb", + scale: 1, + starred: false, + }, + { + name: "William", + image: "/assets/models/avatars/William-small.webp", + file: "/assets/models/avatars/William.glb", + scale: 1, + starred: false, + }, + { + name: "Erica", + image: "/assets/models/avatars/Erica-small.webp", + file: "/assets/models/avatars/Erica.glb", + scale: 1, + starred: false, + }, + { + name: "Samantha", + image: "/assets/models/avatars/Samantha-small.webp", + file: "/assets/models/avatars/Samantha.glb", + scale: 1, + starred: false, + }, + { + name: "Roman", + image: "/assets/models/avatars/Roman-small.webp", + file: "/assets/models/avatars/Roman.glb", + scale: 1, + starred: false, + }, + { + name: "Cathy", + image: "/assets/models/avatars/Cathy-small.webp", + file: "/assets/models/avatars/Cathy.glb", + scale: 1, + starred: false, + }, + { + name: "Lucas", + image: "/assets/models/avatars/Lucas-small.webp", + file: "/assets/models/avatars/Lucas.glb", + scale: 1, + starred: false, + }, + { + name: "Michaella", + image: "/assets/models/avatars/Michaella-small.webp", + file: "/assets/models/avatars/Michaella.glb", + scale: 1, + starred: false, + }, + { + name: "David", + image: "/assets/models/avatars/David-small.webp", + file: "/assets/models/avatars/David.glb", + scale: 1, + starred: false, + }, + { + name: "Rochella", + image: "/assets/models/avatars/Rochella-small.webp", + file: "/assets/models/avatars/Rochella.glb", + scale: 1, + starred: false, + }, + { + name: "Susan", + image: "/assets/models/avatars/Susan-small.webp", + file: "/assets/models/avatars/Susan.glb", + scale: 1, + starred: false, + }, + { + name: "Diego", + image: "/assets/models/avatars/Diego-small.webp", + file: "/assets/models/avatars/Diego.glb", + scale: 1, + starred: false, + }, + { + name: "Jameson", + image: "/assets/models/avatars/Jameson-small.webp", + file: "/assets/models/avatars/Jameson.glb", + scale: 1, + starred: false, + }, + { + name: "Kevin", + image: "/assets/models/avatars/Kevin-small.webp", + file: "/assets/models/avatars/Kevin.glb", + scale: 1, + starred: false, + }, + { + name: "Lila", + image: "/assets/models/avatars/Lila-small.webp", + file: "/assets/models/avatars/Lila.glb", + scale: 1, + starred: false, + }, + { + name: "Vikki", + image: "/assets/models/avatars/Vikki-small.webp", + file: "/assets/models/avatars/Vikki.glb", + scale: 1, + starred: false, + }, + { + name: "Jonas", + image: "/assets/models/avatars/Jonas-small.webp", + file: "/assets/models/avatars/Jonas.glb", + scale: 1, + starred: false, + }, + { + name: "Kelly", + image: "/assets/models/avatars/Kelly-small.webp", + file: "/assets/models/avatars/Kelly.glb", + scale: 1, + starred: false, + }, + ]), }, }, diff --git a/src/modules/avatar/DefaultModels.ts b/src/modules/avatar/DefaultModels.ts index 978e8f5d..544708b2 100644 --- a/src/modules/avatar/DefaultModels.ts +++ b/src/modules/avatar/DefaultModels.ts @@ -1,271 +1,50 @@ -// -// DefaultModels.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -// TODO: Get most of these variables from the config instead of this file (so it can be overridden with environment variables correctly). - -const modelRoot = "/assets/models/avatars/"; - -export interface AvatarModel { - name: string, - image: string, - file: string, - scale: number, - starred: boolean -} - -export interface AvatarModelMap { - [key: string]: AvatarModel -} - -/** - * @returns The URL of the default avatar model. - */ -export function defaultActiveAvatarUrl(): string { - return `${modelRoot}sara.glb`; -} - -/** - * @returns The ID of the default avatar model. - */ -export function defaultActiveAvatarId(): string { - return "HTP45FSQ"; -} - -/** - * @returns The fallback avatar model. - */ -export function fallbackAvatar(): AvatarModel { - return { - name: "Maria", - image: `${modelRoot}Maria-small.webp`, - file: `${modelRoot}default_avatar.glb`, - scale: 1, - starred: false - }; -} - -/** - * @returns The URL of the fallback avatar model. - */ -export function fallbackAvatarUrl(): string { - return fallbackAvatar().file; -} - -/** - * @returns The ID of the fallback avatar model. - */ -export function fallbackAvatarId(): string { - return "FALLBACK"; -} - -/** - * @returns The default collection of avatar models. - */ -export function defaultAvatars(): AvatarModelMap { - return { - HTP45FSQ: { - name: "Sara", - image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", - file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", - scale: 1, - starred: true - } as AvatarModel, - ZPNSHHIJ: { - name: "Mark", - image: `${modelRoot}Mark-small.webp`, - file: `${modelRoot}Mark.glb`, - scale: 1, - starred: false - } as AvatarModel, - C5E0NT3P: { - name: "Megan", - image: `${modelRoot}Megan-small.webp`, - file: `${modelRoot}Megan.glb`, - scale: 1, - starred: false - } as AvatarModel, - HYGME2O8: { - name: "Jack", - image: `${modelRoot}Jack-small.webp`, - file: `${modelRoot}Jack.glb`, - scale: 1, - starred: false - } as AvatarModel, - AIOUPXVY: { - name: "Martha", - image: `${modelRoot}Martha-small.webp`, - file: `${modelRoot}Martha.glb`, - scale: 1, - starred: false - } as AvatarModel, - LRX76LNL: { - name: "Miles", - image: `${modelRoot}Miles-small.webp`, - file: `${modelRoot}Miles.glb`, - scale: 1, - starred: false - } as AvatarModel, - HTLZ3SVU: { - name: "Taylor", - image: `${modelRoot}Taylor-small.webp`, - file: `${modelRoot}Taylor.glb`, - scale: 1, - starred: false - } as AvatarModel, - EPS62RC9: { - name: "Tiffany", - image: `${modelRoot}Tiffany-small.webp`, - file: `${modelRoot}Tiffany.glb`, - scale: 1, - starred: false - } as AvatarModel, - QIA9XG4G: { - name: "Victor", - image: `${modelRoot}Victor-small.webp`, - file: `${modelRoot}Victor.glb`, - scale: 1, - starred: false - } as AvatarModel, - N5PBHE7C: { - name: "Audrey", - image: `${modelRoot}Audrey-small.webp`, - file: `${modelRoot}Audrey.glb`, - scale: 1, - starred: false - } as AvatarModel, - E7RCM559: { - name: "Kristine", - image: `${modelRoot}Kristine-small.webp`, - file: `${modelRoot}Kristine.glb`, - scale: 1, - starred: false - } as AvatarModel, - SG35OH2Y: { - name: "William", - image: `${modelRoot}William-small.webp`, - file: `${modelRoot}William.glb`, - scale: 1, - starred: false - } as AvatarModel, - JKV34GST: { - name: "Erica", - image: `${modelRoot}Erica-small.webp`, - file: `${modelRoot}Erica.glb`, - scale: 1, - starred: false - } as AvatarModel, - X5AII7GT: { - name: "Samantha", - image: `${modelRoot}Samantha-small.webp`, - file: `${modelRoot}Samantha.glb`, - scale: 1, - starred: false - } as AvatarModel, - ZGK9IGRB: { - name: "Roman", - image: `${modelRoot}Roman-small.webp`, - file: `${modelRoot}Roman.glb`, - scale: 1, - starred: false - } as AvatarModel, - DBYRNKR8: { - name: "Cathy", - image: `${modelRoot}Cathy-small.webp`, - file: `${modelRoot}Cathy.glb`, - scale: 1, - starred: false - } as AvatarModel, - EG1XOUR4: { - name: "Lucas", - image: `${modelRoot}Lucas-small.webp`, - file: `${modelRoot}Lucas.glb`, - scale: 1, - starred: false - } as AvatarModel, - OPX471R4: { - name: "Michaella", - image: `${modelRoot}Michaella-small.webp`, - file: `${modelRoot}Michaella.glb`, - scale: 1, - starred: false - } as AvatarModel, - V5DYP68J: { - name: "David", - image: `${modelRoot}David-small.webp`, - file: `${modelRoot}David.glb`, - scale: 1, - starred: false - } as AvatarModel, - M9G7AFFC: { - name: "Rochella", - image: `${modelRoot}Rochella-small.webp`, - file: `${modelRoot}Rochella.glb`, - scale: 1, - starred: false - } as AvatarModel, - LHUVJ7RA: { - name: "Susan", - image: `${modelRoot}Susan-small.webp`, - file: `${modelRoot}Susan.glb`, - scale: 1, - starred: false - } as AvatarModel, - EQQC5125: { - name: "Diego", - image: `${modelRoot}Diego-small.webp`, - file: `${modelRoot}Diego.glb`, - scale: 1, - starred: false - } as AvatarModel, - GYC8OLSF: { - name: "Jameson", - image: `${modelRoot}Jameson-small.webp`, - file: `${modelRoot}Jameson.glb`, - scale: 1, - starred: false - } as AvatarModel, - OFTR0UR0: { - name: "Kevin", - image: `${modelRoot}Kevin-small.webp`, - file: `${modelRoot}Kevin.glb`, - scale: 1, - starred: false - } as AvatarModel, - VO3YR5QC: { - name: "Lila", - image: `${modelRoot}Lila-small.webp`, - file: `${modelRoot}Lila.glb`, - scale: 1, - starred: false - } as AvatarModel, - S4Q8O9CE: { - name: "Vikki", - image: `${modelRoot}Vikki-small.webp`, - file: `${modelRoot}Vikki.glb`, - scale: 1, - starred: false - } as AvatarModel, - ETQZ8G3W: { - name: "Jonas", - image: `${modelRoot}Jonas-small.webp`, - file: `${modelRoot}Jonas.glb`, - scale: 1, - starred: false - } as AvatarModel, - D8WRU1KS: { - name: "Kelly", - image: `${modelRoot}Kelly-small.webp`, - file: `${modelRoot}Kelly.glb`, - scale: 1, - starred: false - } as AvatarModel, - FALLBACK: fallbackAvatar() - } as AvatarModelMap; -} +// +// DefaultModels.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// TODO: Get most of these variables from the config instead of this file (so it can be overridden with environment variables correctly). + +export interface AvatarModel { + name: string, + image: string, + file: string, + scale: number, + starred: boolean +} + +/** + * @returns The URL of the default avatar model. + */ +export function defaultActiveAvatarUrl(): string { + return defaultAvatars()[0].file; +} + +/** + * @returns The name of the default avatar model. + */ +export function defaultActiveAvatarName(): string { + return defaultAvatars()[0].name; +} + +/** + * @returns The default collection of avatar models. + * @throws {Error} If parsing VRCA_DEFAULT_AVATARS fails or the list is empty. + */ +export function defaultAvatars(): AvatarModel[] { + if (!process.env.VRCA_DEFAULT_AVATARS) { + throw new Error("VRCA_DEFAULT_AVATARS is not defined in the environment"); + } + + const avatars = JSON.parse(process.env.VRCA_DEFAULT_AVATARS); + if (!Array.isArray(avatars) || avatars.length === 0) { + throw new Error("VRCA_DEFAULT_AVATARS is not a valid array or is empty"); + } + + return avatars; +} diff --git a/src/modules/avatar/StoreInterface.ts b/src/modules/avatar/StoreInterface.ts index 818085bb..86b146cd 100644 --- a/src/modules/avatar/StoreInterface.ts +++ b/src/modules/avatar/StoreInterface.ts @@ -1,159 +1,154 @@ -// -// StoreInterface.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { fallbackAvatar, fallbackAvatarId, type AvatarModel } from "./DefaultModels"; -import { userStore } from "@Stores/index"; -import { Renderer } from "@Modules/scene"; - -/** - * Static methods for interacting with the list of avatar models in the Store. - */ -export class AvatarStoreInterface { - /** - * Generate a random, alpha-numeric, 8 character long ID string that is unique within the avatar Store. - * @returns An ID string. - */ - private static _generateID(): string { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - const idLength = 8; - function generate(): string { - let id = ""; - for (let i = 0; i < idLength; i += 1) { - id += chars[Math.floor(Math.random() * chars.length)]; - } - return id; - } - let uniqueId = generate(); - // Ensure that the ID doesn't already exist in the Store. - while (uniqueId in userStore.avatar.models) { - uniqueId = generate(); - } - return uniqueId; - } - - /** - * Retrieve the data (name, thumbnail, scale, etc) for a given avatar model. - * @param modelId The ID of the model to retrieve. - * @param key `(Optional)` A specific property of the model to retrieve. - * @returns The data for the requested avatar model, or the fallback model if the requested one doesn't exist. - * If a key was specified, only the value of that property is returned. - */ - public static getModelData(modelId: string | number): AvatarModel; - public static getModelData(modelId: string | number, key: T): AvatarModel[T]; - public static getModelData(modelId: string | number, key?: T): AvatarModel | AvatarModel[T] { - const models = userStore.avatar.models; - if (key && key in (models[modelId] || fallbackAvatar())) { - return models[modelId][key]; - } - return models[modelId] || fallbackAvatar(); - } - - /** - * Retrieve the data (name, thumbnail, scale, etc) for the avatar model currently equipped by the user. - * @param key `(Optional)` A specific property of the model to retrieve. - * @returns The data for the currently equipped avatar model, or the fallback model if the currently equipped one doesn't exist. - * If a key was specified, only the value of that property is returned. - */ - public static getActiveModelData(): AvatarModel; - public static getActiveModelData(key: T): AvatarModel[T]; - public static getActiveModelData(key?: T): AvatarModel | AvatarModel[T] { - const activeModel = userStore.avatar.activeModel; - if (key) { - return this.getModelData(activeModel, key); - } - return this.getModelData(activeModel); - } - - /** - * @returns A stringified map of all the stored avatar models. - */ - public static getAllModelsJSON(): string { - return JSON.stringify(userStore.avatar.models); - } - - /** - * Set the value of a specific property of an avatar model. - * @param modelId The ID of the model to update. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setModelData(modelId: string | number, key: T, value: AvatarModel[T]): void { - if (modelId in userStore.avatar.models) { - userStore.avatar.models[modelId][key] = value; - // If the model's file was updated, make it the active model so there is immediate feedback on that change. - if (key === "file") { - this.setActiveModel(modelId); - } - } - } - - /** - * Set the value of a specific property of the avatar model currently equipped by the user. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setActiveModelData(key: keyof AvatarModel, value: string | number | boolean): void { - const activeModel = userStore.avatar.activeModel; - this.setModelData(activeModel, key, value); - } - - /** - * Create an entry for a new avatar model in the Store. - * @param modelData The data (name, thumbnail, scale, etc) for the new avatar model. - * @param setToActive `(Optional)` Set equip this model. - * @returns The ID of the new model. - */ - public static createNewModel(modelData: AvatarModel, setToActive = true): string { - const ID = this._generateID(); - userStore.avatar.models[ID] = modelData; - if (setToActive) { - this.setActiveModel(ID); - } - return ID; - } - - /** - * Remove a model from the Store. - * @param modelId The ID of the model to remove. - */ - public static removeModel(modelId: string | number): void { - // Prevent the fallback model from being deleted. - if (modelId === fallbackAvatarId()) { - return; - } - - // Switch to the fallback model if the removed model is currently equipped. - if (modelId === userStore.avatar.activeModel) { - this.setActiveModel(fallbackAvatarId()); - } - - // Remove the requested model from the Store. - if (modelId in userStore.avatar.models) { - delete userStore.avatar.models[modelId]; - } - } - - /** - * Equip a particular model. - * @param modelId The ID of the model to equip. - */ - public static setActiveModel(modelId: string | number): void { - if (modelId in userStore.avatar.models) { - userStore.avatar.activeModel = typeof modelId === "number" ? modelId.toString() : modelId; - } - try { - const scene = Renderer.getScene(); - scene.loadMyAvatar(AvatarStoreInterface.getModelData(modelId, "file")).catch((err) => console.warn("Failed to load avatar:", err)); - } catch (error) { - console.warn("Cannot render active avatar model before the scene has been loaded.", error); - } - } -} +// +// StoreInterface.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { userStore } from "@Stores/index"; +import { Renderer } from "@Modules/scene"; + +export interface AvatarModel { + name: string, + image: string, + file: string, + scale: number, + starred: boolean +} + +/** + * Static methods for interacting with the list of avatar models in the Store. + */ +export class AvatarStoreInterface { + /** + * @returns The default collection of avatar models. + * @throws {Error} If parsing VRCA_DEFAULT_AVATARS fails or the list is empty. + */ + private static defaultAvatars(): AvatarModel[] { + if (!process.env.VRCA_DEFAULT_AVATARS) { + throw new Error("VRCA_DEFAULT_AVATARS is not defined in the environment"); + } + + const avatars = JSON.parse(process.env.VRCA_DEFAULT_AVATARS); + if (!Array.isArray(avatars) || avatars.length === 0) { + throw new Error("VRCA_DEFAULT_AVATARS is not a valid array or is empty"); + } + + return avatars; + } + + /** + * @returns The URL of the default avatar model. + */ + public static defaultActiveAvatarUrl(): string { + return this.defaultAvatars()[0].file; + } + + /** + * @returns The name of the default avatar model. + */ + public static defaultActiveAvatarName(): string { + return this.defaultAvatars()[0].name; + } + + /** + * Retrieve the data for a given avatar model. + * @param index The index of the model to retrieve. + * @returns The data for the requested avatar model, or undefined if the index is out of bounds. + */ + public static getModelData(index: number): AvatarModel | undefined { + return userStore.avatar.models[index]; + } + + /** + * Retrieve the data for the avatar model currently equipped by the user. + * @returns The data for the currently equipped avatar model, or undefined if no model is equipped. + */ + public static getActiveModelData(): AvatarModel | undefined { + return this.getModelData(userStore.avatar.activeModelIndex); + } + + /** + * @returns A stringified array of all the stored avatar models. + */ + public static getAllModelsJSON(): string { + return JSON.stringify(userStore.avatar.models); + } + + /** + * Set the value of a specific property of an avatar model. + * @param index The index of the model to update. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setModelData(index: number, key: T, value: AvatarModel[T]): void { + if (index >= 0 && index < userStore.avatar.models.length) { + userStore.avatar.models[index][key] = value; + // If the model's file was updated, make it the active model so there is immediate feedback on that change. + if (key === "file") { + this.setActiveModel(index); + } + } + } + + /** + * Set the value of a specific property of the avatar model currently equipped by the user. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setActiveModelData(key: T, value: AvatarModel[T]): void { + this.setModelData(userStore.avatar.activeModelIndex, key, value); + } + + /** + * Add a new avatar model to the Store. + * @param modelData The data for the new avatar model. + * @param setToActive `(Optional)` Set to equip this model. + * @returns The index of the new model. + */ + public static addNewModel(modelData: AvatarModel, setToActive = true): number { + const newIndex = userStore.avatar.models.length; + userStore.avatar.models.push(modelData); + if (setToActive) { + this.setActiveModel(newIndex); + } + return newIndex; + } + + /** + * Remove a model from the Store. + * @param index The index of the model to remove. + */ + public static removeModel(index: number): void { + if (index >= 0 && index < userStore.avatar.models.length) { + userStore.avatar.models.splice(index, 1); + // If the removed model was active, set the first model as active + if (index === userStore.avatar.activeModelIndex) { + this.setActiveModel(0); + } else if (index < userStore.avatar.activeModelIndex) { + // Adjust the active model index if a model before it was removed + userStore.avatar.activeModelIndex--; + } + } + } + + /** + * Equip a particular model. + * @param index The index of the model to equip. + */ + public static setActiveModel(index: number): void { + if (index >= 0 && index < userStore.avatar.models.length) { + userStore.avatar.activeModelIndex = index; + try { + const scene = Renderer.getScene(); + scene.loadMyAvatar(userStore.avatar.models[index].file).catch((err) => console.warn("Failed to load avatar:", err)); + } catch (error) { + console.warn("Cannot render active avatar model before the scene has been loaded.", error); + } + } + } +} diff --git a/src/stores/user-store.ts b/src/stores/user-store.ts index dadf1f4a..cfde9962 100644 --- a/src/stores/user-store.ts +++ b/src/stores/user-store.ts @@ -1,190 +1,190 @@ -// -// user-store.ts -// -// Created by Giga on 30 May 2023. -// Copyright 2023 Vircadia contributors. -// Copyright 2023 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { defineStore } from "pinia"; -import { useStorage } from "@vueuse/core"; -import { Vec3 } from "@vircadia/web-sdk"; -import { onAttributeChangePayload } from "@Modules/account"; -import { defaultActiveAvatarId, defaultAvatars } from "@Modules/avatar/DefaultModels"; -import type { Domain } from "@Base/modules/domain/domain"; -import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; -import { DataMapper } from "@Modules/domain/dataMapper"; -import type { vec3 } from "@vircadia/web-sdk"; - -const persistentStorageMedium = localStorage; - -const defaultControls = { - keyboard: { - movement: { - walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, - walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, - walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, - walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, - run: { name: "Run", keycode: "ShiftLeft" } as Keybind, - jump: { name: "Jump", keycode: "Space" } as Keybind, - crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, - fly: { name: "Fly", keycode: "KeyF" } as Keybind, - sit: { name: "Sit", keycode: "KeyG" } as Keybind, - clap: { name: "Clap", keycode: "KeyH" } as Keybind, - salute: { name: "Salute", keycode: "KeyJ" } as Keybind - }, - camera: { - pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, - pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, - yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, - yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, - firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, - thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, - collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind - }, - audio: { - mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, - pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind - }, - other: { - resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, - toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, - openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind - } - }, - mouse: { - acceleration: true, - invert: false, - sensitivity: 50 - } -}; - -export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; -export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; -export interface Keybind { - name: string, - keycode: string -} - -export interface LocationBookmark { - name: string, - color: string, - url: string -} - -export const useUserStore = defineStore("user", { - state: () => ({ - avatar: useStorage( - "userAvatarSettings", - { - displayName: "anonymous", - showLabels: true, - position: Vec3.ZERO, - location: "0,0,0", - models: defaultAvatars(), - activeModel: defaultActiveAvatarId() - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Graphics configuration. - graphics: useStorage( - "userGraphicsSettings", - { - fieldOfView: 85, - bloom: true, - fxaaEnabled: true, - msaa: 2, - sharpen: false, - fpsCounter: false, - cameraBobbing: true - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Information about the logged in account. Refer to Account module. - account: useStorage( - "userAccountSettings", - { - id: "UNKNOWN", - username: "Guest", - isLoggedIn: false, - accessToken: "UNKNOWN", - tokenType: "Bearer", - scope: "UNKNOWN", - isAdmin: false, - useAsAdmin: false, - images: { - hero: undefined as string | undefined, - tiny: undefined as string | undefined, - thumbnail: undefined as string | undefined - } - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Saved bookmarks. - bookmarks: useStorage( - "userBookmarks", - { - locations: [] as Array - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ), - // Controls. - controls: useStorage("userControlSettings", - defaultControls, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ) - }), - - actions: { - /** - * Reset the state of the store to default. - */ - reset(): void { - this.$reset(); - }, - /** - * Update the stored account information for the current user. - * @param data - */ - updateAccountInfo(data: onAttributeChangePayload): void { - this.account.accessToken = data.accessToken; - this.account.isAdmin = data.isAdmin; - this.account.isLoggedIn = data.isLoggedIn; - this.account.scope = data.scope; - this.account.tokenType = data.accessTokenType; - this.account.username = data.accountInfo.username; - Object.assign(this.account.images, data.accountInfo.images ?? {}); - }, - /** - * Update the stored information for the local avatar. - * @param domain A reference to the domain server connection instance. - * @param domainAvatar `(Optional)` A reference to the local avatar instance. - * @param position `(Optional)` The new position of the local avatar in the world. - */ - updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { - const domainLocation = domain.DomainClient - ? domain.Location.protocol + "//" + domain.Location.host - : "Disconnected"; - if (domainAvatar) { - const myAvaInfo = domainAvatar.MyAvatar; - this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; - this.avatar.location - = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; - this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; - } - // An optional update to just the avatar's position. - if (position) { - this.avatar.position = position; - this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; - } - } - } -}); +// +// user-store.ts +// +// Created by Giga on 30 May 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { defineStore } from "pinia"; +import { useStorage } from "@vueuse/core"; +import { Vec3 } from "@vircadia/web-sdk"; +import { onAttributeChangePayload } from "@Modules/account"; +import { defaultActiveAvatarId, defaultAvatars } from "@Modules/avatar/DefaultModels"; +import type { Domain } from "@Base/modules/domain/domain"; +import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; +import { DataMapper } from "@Modules/domain/dataMapper"; +import type { vec3 } from "@vircadia/web-sdk"; + +const persistentStorageMedium = localStorage; + +const defaultControls = { + keyboard: { + movement: { + walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, + walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, + walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, + walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, + run: { name: "Run", keycode: "ShiftLeft" } as Keybind, + jump: { name: "Jump", keycode: "Space" } as Keybind, + crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, + fly: { name: "Fly", keycode: "KeyF" } as Keybind, + sit: { name: "Sit", keycode: "KeyG" } as Keybind, + clap: { name: "Clap", keycode: "KeyH" } as Keybind, + salute: { name: "Salute", keycode: "KeyJ" } as Keybind + }, + camera: { + pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, + pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, + yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, + yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, + firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, + thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, + collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind + }, + audio: { + mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, + pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind + }, + other: { + resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, + toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, + openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind + } + }, + mouse: { + acceleration: true, + invert: false, + sensitivity: 50 + } +}; + +export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; +export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; +export interface Keybind { + name: string, + keycode: string +} + +export interface LocationBookmark { + name: string, + color: string, + url: string +} + +export const useUserStore = defineStore("user", { + state: () => ({ + avatar: useStorage( + "userAvatarSettings", + { + displayName: "anonymous", + showLabels: true, + position: Vec3.ZERO, + location: "0,0,0", + avatars: defaultAvatars(), + activeAvatar: defaultActiveAvatarId() + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Graphics configuration. + graphics: useStorage( + "userGraphicsSettings", + { + fieldOfView: 85, + bloom: true, + fxaaEnabled: true, + msaa: 2, + sharpen: false, + fpsCounter: false, + cameraBobbing: true + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Information about the logged in account. Refer to Account module. + account: useStorage( + "userAccountSettings", + { + id: "UNKNOWN", + username: "Guest", + isLoggedIn: false, + accessToken: "UNKNOWN", + tokenType: "Bearer", + scope: "UNKNOWN", + isAdmin: false, + useAsAdmin: false, + images: { + hero: undefined as string | undefined, + tiny: undefined as string | undefined, + thumbnail: undefined as string | undefined + } + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Saved bookmarks. + bookmarks: useStorage( + "userBookmarks", + { + locations: [] as Array + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ), + // Controls. + controls: useStorage("userControlSettings", + defaultControls, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ) + }), + + actions: { + /** + * Reset the state of the store to default. + */ + reset(): void { + this.$reset(); + }, + /** + * Update the stored account information for the current user. + * @param data + */ + updateAccountInfo(data: onAttributeChangePayload): void { + this.account.accessToken = data.accessToken; + this.account.isAdmin = data.isAdmin; + this.account.isLoggedIn = data.isLoggedIn; + this.account.scope = data.scope; + this.account.tokenType = data.accessTokenType; + this.account.username = data.accountInfo.username; + Object.assign(this.account.images, data.accountInfo.images ?? {}); + }, + /** + * Update the stored information for the local avatar. + * @param domain A reference to the domain server connection instance. + * @param domainAvatar `(Optional)` A reference to the local avatar instance. + * @param position `(Optional)` The new position of the local avatar in the world. + */ + updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { + const domainLocation = domain.DomainClient + ? domain.Location.protocol + "//" + domain.Location.host + : "Disconnected"; + if (domainAvatar) { + const myAvaInfo = domainAvatar.MyAvatar; + this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; + this.avatar.location + = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; + this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; + } + // An optional update to just the avatar's position. + if (position) { + this.avatar.position = position; + this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; + } + } + } +}); From f5c0a9715f88ec7614692dc21c21a2dd76b886dd Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:52:52 +1000 Subject: [PATCH 12/14] Revert "WIP" This reverts commit 7b218d6107f0fd897ba8d1f833e434bf8a431f17. --- quasar.config.js | 198 -------------- src/modules/avatar/DefaultModels.ts | 321 ++++++++++++++++++---- src/modules/avatar/StoreInterface.ts | 313 +++++++++++----------- src/stores/user-store.ts | 380 +++++++++++++-------------- 4 files changed, 620 insertions(+), 592 deletions(-) diff --git a/quasar.config.js b/quasar.config.js index 22f50a7f..7d589f1a 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -180,204 +180,6 @@ module.exports = configure(function (ctx) { process.env.VRCA_WIZARD_BUTTON_TEXT ?? "Get Started", // Desktop App VRCA_DESKTOP_MODE: process.env.VRCA_DESKTOP_MODE, - VRCA_DEFAULT_AVATARS: JSON.stringify([ - { - name: "Sara", - image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", - file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", - scale: 1, - starred: true, - }, - { - name: "Mark", - image: "/assets/models/avatars/Mark-small.webp", - file: "/assets/models/avatars/Mark.glb", - scale: 1, - starred: false, - }, - { - name: "Megan", - image: "/assets/models/avatars/Megan-small.webp", - file: "/assets/models/avatars/Megan.glb", - scale: 1, - starred: false, - }, - { - name: "Jack", - image: "/assets/models/avatars/Jack-small.webp", - file: "/assets/models/avatars/Jack.glb", - scale: 1, - starred: false, - }, - { - name: "Martha", - image: "/assets/models/avatars/Martha-small.webp", - file: "/assets/models/avatars/Martha.glb", - scale: 1, - starred: false, - }, - { - name: "Miles", - image: "/assets/models/avatars/Miles-small.webp", - file: "/assets/models/avatars/Miles.glb", - scale: 1, - starred: false, - }, - { - name: "Taylor", - image: "/assets/models/avatars/Taylor-small.webp", - file: "/assets/models/avatars/Taylor.glb", - scale: 1, - starred: false, - }, - { - name: "Tiffany", - image: "/assets/models/avatars/Tiffany-small.webp", - file: "/assets/models/avatars/Tiffany.glb", - scale: 1, - starred: false, - }, - { - name: "Victor", - image: "/assets/models/avatars/Victor-small.webp", - file: "/assets/models/avatars/Victor.glb", - scale: 1, - starred: false, - }, - { - name: "Audrey", - image: "/assets/models/avatars/Audrey-small.webp", - file: "/assets/models/avatars/Audrey.glb", - scale: 1, - starred: false, - }, - { - name: "Kristine", - image: "/assets/models/avatars/Kristine-small.webp", - file: "/assets/models/avatars/Kristine.glb", - scale: 1, - starred: false, - }, - { - name: "William", - image: "/assets/models/avatars/William-small.webp", - file: "/assets/models/avatars/William.glb", - scale: 1, - starred: false, - }, - { - name: "Erica", - image: "/assets/models/avatars/Erica-small.webp", - file: "/assets/models/avatars/Erica.glb", - scale: 1, - starred: false, - }, - { - name: "Samantha", - image: "/assets/models/avatars/Samantha-small.webp", - file: "/assets/models/avatars/Samantha.glb", - scale: 1, - starred: false, - }, - { - name: "Roman", - image: "/assets/models/avatars/Roman-small.webp", - file: "/assets/models/avatars/Roman.glb", - scale: 1, - starred: false, - }, - { - name: "Cathy", - image: "/assets/models/avatars/Cathy-small.webp", - file: "/assets/models/avatars/Cathy.glb", - scale: 1, - starred: false, - }, - { - name: "Lucas", - image: "/assets/models/avatars/Lucas-small.webp", - file: "/assets/models/avatars/Lucas.glb", - scale: 1, - starred: false, - }, - { - name: "Michaella", - image: "/assets/models/avatars/Michaella-small.webp", - file: "/assets/models/avatars/Michaella.glb", - scale: 1, - starred: false, - }, - { - name: "David", - image: "/assets/models/avatars/David-small.webp", - file: "/assets/models/avatars/David.glb", - scale: 1, - starred: false, - }, - { - name: "Rochella", - image: "/assets/models/avatars/Rochella-small.webp", - file: "/assets/models/avatars/Rochella.glb", - scale: 1, - starred: false, - }, - { - name: "Susan", - image: "/assets/models/avatars/Susan-small.webp", - file: "/assets/models/avatars/Susan.glb", - scale: 1, - starred: false, - }, - { - name: "Diego", - image: "/assets/models/avatars/Diego-small.webp", - file: "/assets/models/avatars/Diego.glb", - scale: 1, - starred: false, - }, - { - name: "Jameson", - image: "/assets/models/avatars/Jameson-small.webp", - file: "/assets/models/avatars/Jameson.glb", - scale: 1, - starred: false, - }, - { - name: "Kevin", - image: "/assets/models/avatars/Kevin-small.webp", - file: "/assets/models/avatars/Kevin.glb", - scale: 1, - starred: false, - }, - { - name: "Lila", - image: "/assets/models/avatars/Lila-small.webp", - file: "/assets/models/avatars/Lila.glb", - scale: 1, - starred: false, - }, - { - name: "Vikki", - image: "/assets/models/avatars/Vikki-small.webp", - file: "/assets/models/avatars/Vikki.glb", - scale: 1, - starred: false, - }, - { - name: "Jonas", - image: "/assets/models/avatars/Jonas-small.webp", - file: "/assets/models/avatars/Jonas.glb", - scale: 1, - starred: false, - }, - { - name: "Kelly", - image: "/assets/models/avatars/Kelly-small.webp", - file: "/assets/models/avatars/Kelly.glb", - scale: 1, - starred: false, - }, - ]), }, }, diff --git a/src/modules/avatar/DefaultModels.ts b/src/modules/avatar/DefaultModels.ts index 544708b2..978e8f5d 100644 --- a/src/modules/avatar/DefaultModels.ts +++ b/src/modules/avatar/DefaultModels.ts @@ -1,50 +1,271 @@ -// -// DefaultModels.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -// TODO: Get most of these variables from the config instead of this file (so it can be overridden with environment variables correctly). - -export interface AvatarModel { - name: string, - image: string, - file: string, - scale: number, - starred: boolean -} - -/** - * @returns The URL of the default avatar model. - */ -export function defaultActiveAvatarUrl(): string { - return defaultAvatars()[0].file; -} - -/** - * @returns The name of the default avatar model. - */ -export function defaultActiveAvatarName(): string { - return defaultAvatars()[0].name; -} - -/** - * @returns The default collection of avatar models. - * @throws {Error} If parsing VRCA_DEFAULT_AVATARS fails or the list is empty. - */ -export function defaultAvatars(): AvatarModel[] { - if (!process.env.VRCA_DEFAULT_AVATARS) { - throw new Error("VRCA_DEFAULT_AVATARS is not defined in the environment"); - } - - const avatars = JSON.parse(process.env.VRCA_DEFAULT_AVATARS); - if (!Array.isArray(avatars) || avatars.length === 0) { - throw new Error("VRCA_DEFAULT_AVATARS is not a valid array or is empty"); - } - - return avatars; -} +// +// DefaultModels.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// TODO: Get most of these variables from the config instead of this file (so it can be overridden with environment variables correctly). + +const modelRoot = "/assets/models/avatars/"; + +export interface AvatarModel { + name: string, + image: string, + file: string, + scale: number, + starred: boolean +} + +export interface AvatarModelMap { + [key: string]: AvatarModel +} + +/** + * @returns The URL of the default avatar model. + */ +export function defaultActiveAvatarUrl(): string { + return `${modelRoot}sara.glb`; +} + +/** + * @returns The ID of the default avatar model. + */ +export function defaultActiveAvatarId(): string { + return "HTP45FSQ"; +} + +/** + * @returns The fallback avatar model. + */ +export function fallbackAvatar(): AvatarModel { + return { + name: "Maria", + image: `${modelRoot}Maria-small.webp`, + file: `${modelRoot}default_avatar.glb`, + scale: 1, + starred: false + }; +} + +/** + * @returns The URL of the fallback avatar model. + */ +export function fallbackAvatarUrl(): string { + return fallbackAvatar().file; +} + +/** + * @returns The ID of the fallback avatar model. + */ +export function fallbackAvatarId(): string { + return "FALLBACK"; +} + +/** + * @returns The default collection of avatar models. + */ +export function defaultAvatars(): AvatarModelMap { + return { + HTP45FSQ: { + name: "Sara", + image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", + file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", + scale: 1, + starred: true + } as AvatarModel, + ZPNSHHIJ: { + name: "Mark", + image: `${modelRoot}Mark-small.webp`, + file: `${modelRoot}Mark.glb`, + scale: 1, + starred: false + } as AvatarModel, + C5E0NT3P: { + name: "Megan", + image: `${modelRoot}Megan-small.webp`, + file: `${modelRoot}Megan.glb`, + scale: 1, + starred: false + } as AvatarModel, + HYGME2O8: { + name: "Jack", + image: `${modelRoot}Jack-small.webp`, + file: `${modelRoot}Jack.glb`, + scale: 1, + starred: false + } as AvatarModel, + AIOUPXVY: { + name: "Martha", + image: `${modelRoot}Martha-small.webp`, + file: `${modelRoot}Martha.glb`, + scale: 1, + starred: false + } as AvatarModel, + LRX76LNL: { + name: "Miles", + image: `${modelRoot}Miles-small.webp`, + file: `${modelRoot}Miles.glb`, + scale: 1, + starred: false + } as AvatarModel, + HTLZ3SVU: { + name: "Taylor", + image: `${modelRoot}Taylor-small.webp`, + file: `${modelRoot}Taylor.glb`, + scale: 1, + starred: false + } as AvatarModel, + EPS62RC9: { + name: "Tiffany", + image: `${modelRoot}Tiffany-small.webp`, + file: `${modelRoot}Tiffany.glb`, + scale: 1, + starred: false + } as AvatarModel, + QIA9XG4G: { + name: "Victor", + image: `${modelRoot}Victor-small.webp`, + file: `${modelRoot}Victor.glb`, + scale: 1, + starred: false + } as AvatarModel, + N5PBHE7C: { + name: "Audrey", + image: `${modelRoot}Audrey-small.webp`, + file: `${modelRoot}Audrey.glb`, + scale: 1, + starred: false + } as AvatarModel, + E7RCM559: { + name: "Kristine", + image: `${modelRoot}Kristine-small.webp`, + file: `${modelRoot}Kristine.glb`, + scale: 1, + starred: false + } as AvatarModel, + SG35OH2Y: { + name: "William", + image: `${modelRoot}William-small.webp`, + file: `${modelRoot}William.glb`, + scale: 1, + starred: false + } as AvatarModel, + JKV34GST: { + name: "Erica", + image: `${modelRoot}Erica-small.webp`, + file: `${modelRoot}Erica.glb`, + scale: 1, + starred: false + } as AvatarModel, + X5AII7GT: { + name: "Samantha", + image: `${modelRoot}Samantha-small.webp`, + file: `${modelRoot}Samantha.glb`, + scale: 1, + starred: false + } as AvatarModel, + ZGK9IGRB: { + name: "Roman", + image: `${modelRoot}Roman-small.webp`, + file: `${modelRoot}Roman.glb`, + scale: 1, + starred: false + } as AvatarModel, + DBYRNKR8: { + name: "Cathy", + image: `${modelRoot}Cathy-small.webp`, + file: `${modelRoot}Cathy.glb`, + scale: 1, + starred: false + } as AvatarModel, + EG1XOUR4: { + name: "Lucas", + image: `${modelRoot}Lucas-small.webp`, + file: `${modelRoot}Lucas.glb`, + scale: 1, + starred: false + } as AvatarModel, + OPX471R4: { + name: "Michaella", + image: `${modelRoot}Michaella-small.webp`, + file: `${modelRoot}Michaella.glb`, + scale: 1, + starred: false + } as AvatarModel, + V5DYP68J: { + name: "David", + image: `${modelRoot}David-small.webp`, + file: `${modelRoot}David.glb`, + scale: 1, + starred: false + } as AvatarModel, + M9G7AFFC: { + name: "Rochella", + image: `${modelRoot}Rochella-small.webp`, + file: `${modelRoot}Rochella.glb`, + scale: 1, + starred: false + } as AvatarModel, + LHUVJ7RA: { + name: "Susan", + image: `${modelRoot}Susan-small.webp`, + file: `${modelRoot}Susan.glb`, + scale: 1, + starred: false + } as AvatarModel, + EQQC5125: { + name: "Diego", + image: `${modelRoot}Diego-small.webp`, + file: `${modelRoot}Diego.glb`, + scale: 1, + starred: false + } as AvatarModel, + GYC8OLSF: { + name: "Jameson", + image: `${modelRoot}Jameson-small.webp`, + file: `${modelRoot}Jameson.glb`, + scale: 1, + starred: false + } as AvatarModel, + OFTR0UR0: { + name: "Kevin", + image: `${modelRoot}Kevin-small.webp`, + file: `${modelRoot}Kevin.glb`, + scale: 1, + starred: false + } as AvatarModel, + VO3YR5QC: { + name: "Lila", + image: `${modelRoot}Lila-small.webp`, + file: `${modelRoot}Lila.glb`, + scale: 1, + starred: false + } as AvatarModel, + S4Q8O9CE: { + name: "Vikki", + image: `${modelRoot}Vikki-small.webp`, + file: `${modelRoot}Vikki.glb`, + scale: 1, + starred: false + } as AvatarModel, + ETQZ8G3W: { + name: "Jonas", + image: `${modelRoot}Jonas-small.webp`, + file: `${modelRoot}Jonas.glb`, + scale: 1, + starred: false + } as AvatarModel, + D8WRU1KS: { + name: "Kelly", + image: `${modelRoot}Kelly-small.webp`, + file: `${modelRoot}Kelly.glb`, + scale: 1, + starred: false + } as AvatarModel, + FALLBACK: fallbackAvatar() + } as AvatarModelMap; +} diff --git a/src/modules/avatar/StoreInterface.ts b/src/modules/avatar/StoreInterface.ts index 86b146cd..818085bb 100644 --- a/src/modules/avatar/StoreInterface.ts +++ b/src/modules/avatar/StoreInterface.ts @@ -1,154 +1,159 @@ -// -// StoreInterface.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { userStore } from "@Stores/index"; -import { Renderer } from "@Modules/scene"; - -export interface AvatarModel { - name: string, - image: string, - file: string, - scale: number, - starred: boolean -} - -/** - * Static methods for interacting with the list of avatar models in the Store. - */ -export class AvatarStoreInterface { - /** - * @returns The default collection of avatar models. - * @throws {Error} If parsing VRCA_DEFAULT_AVATARS fails or the list is empty. - */ - private static defaultAvatars(): AvatarModel[] { - if (!process.env.VRCA_DEFAULT_AVATARS) { - throw new Error("VRCA_DEFAULT_AVATARS is not defined in the environment"); - } - - const avatars = JSON.parse(process.env.VRCA_DEFAULT_AVATARS); - if (!Array.isArray(avatars) || avatars.length === 0) { - throw new Error("VRCA_DEFAULT_AVATARS is not a valid array or is empty"); - } - - return avatars; - } - - /** - * @returns The URL of the default avatar model. - */ - public static defaultActiveAvatarUrl(): string { - return this.defaultAvatars()[0].file; - } - - /** - * @returns The name of the default avatar model. - */ - public static defaultActiveAvatarName(): string { - return this.defaultAvatars()[0].name; - } - - /** - * Retrieve the data for a given avatar model. - * @param index The index of the model to retrieve. - * @returns The data for the requested avatar model, or undefined if the index is out of bounds. - */ - public static getModelData(index: number): AvatarModel | undefined { - return userStore.avatar.models[index]; - } - - /** - * Retrieve the data for the avatar model currently equipped by the user. - * @returns The data for the currently equipped avatar model, or undefined if no model is equipped. - */ - public static getActiveModelData(): AvatarModel | undefined { - return this.getModelData(userStore.avatar.activeModelIndex); - } - - /** - * @returns A stringified array of all the stored avatar models. - */ - public static getAllModelsJSON(): string { - return JSON.stringify(userStore.avatar.models); - } - - /** - * Set the value of a specific property of an avatar model. - * @param index The index of the model to update. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setModelData(index: number, key: T, value: AvatarModel[T]): void { - if (index >= 0 && index < userStore.avatar.models.length) { - userStore.avatar.models[index][key] = value; - // If the model's file was updated, make it the active model so there is immediate feedback on that change. - if (key === "file") { - this.setActiveModel(index); - } - } - } - - /** - * Set the value of a specific property of the avatar model currently equipped by the user. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setActiveModelData(key: T, value: AvatarModel[T]): void { - this.setModelData(userStore.avatar.activeModelIndex, key, value); - } - - /** - * Add a new avatar model to the Store. - * @param modelData The data for the new avatar model. - * @param setToActive `(Optional)` Set to equip this model. - * @returns The index of the new model. - */ - public static addNewModel(modelData: AvatarModel, setToActive = true): number { - const newIndex = userStore.avatar.models.length; - userStore.avatar.models.push(modelData); - if (setToActive) { - this.setActiveModel(newIndex); - } - return newIndex; - } - - /** - * Remove a model from the Store. - * @param index The index of the model to remove. - */ - public static removeModel(index: number): void { - if (index >= 0 && index < userStore.avatar.models.length) { - userStore.avatar.models.splice(index, 1); - // If the removed model was active, set the first model as active - if (index === userStore.avatar.activeModelIndex) { - this.setActiveModel(0); - } else if (index < userStore.avatar.activeModelIndex) { - // Adjust the active model index if a model before it was removed - userStore.avatar.activeModelIndex--; - } - } - } - - /** - * Equip a particular model. - * @param index The index of the model to equip. - */ - public static setActiveModel(index: number): void { - if (index >= 0 && index < userStore.avatar.models.length) { - userStore.avatar.activeModelIndex = index; - try { - const scene = Renderer.getScene(); - scene.loadMyAvatar(userStore.avatar.models[index].file).catch((err) => console.warn("Failed to load avatar:", err)); - } catch (error) { - console.warn("Cannot render active avatar model before the scene has been loaded.", error); - } - } - } -} +// +// StoreInterface.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { fallbackAvatar, fallbackAvatarId, type AvatarModel } from "./DefaultModels"; +import { userStore } from "@Stores/index"; +import { Renderer } from "@Modules/scene"; + +/** + * Static methods for interacting with the list of avatar models in the Store. + */ +export class AvatarStoreInterface { + /** + * Generate a random, alpha-numeric, 8 character long ID string that is unique within the avatar Store. + * @returns An ID string. + */ + private static _generateID(): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const idLength = 8; + function generate(): string { + let id = ""; + for (let i = 0; i < idLength; i += 1) { + id += chars[Math.floor(Math.random() * chars.length)]; + } + return id; + } + let uniqueId = generate(); + // Ensure that the ID doesn't already exist in the Store. + while (uniqueId in userStore.avatar.models) { + uniqueId = generate(); + } + return uniqueId; + } + + /** + * Retrieve the data (name, thumbnail, scale, etc) for a given avatar model. + * @param modelId The ID of the model to retrieve. + * @param key `(Optional)` A specific property of the model to retrieve. + * @returns The data for the requested avatar model, or the fallback model if the requested one doesn't exist. + * If a key was specified, only the value of that property is returned. + */ + public static getModelData(modelId: string | number): AvatarModel; + public static getModelData(modelId: string | number, key: T): AvatarModel[T]; + public static getModelData(modelId: string | number, key?: T): AvatarModel | AvatarModel[T] { + const models = userStore.avatar.models; + if (key && key in (models[modelId] || fallbackAvatar())) { + return models[modelId][key]; + } + return models[modelId] || fallbackAvatar(); + } + + /** + * Retrieve the data (name, thumbnail, scale, etc) for the avatar model currently equipped by the user. + * @param key `(Optional)` A specific property of the model to retrieve. + * @returns The data for the currently equipped avatar model, or the fallback model if the currently equipped one doesn't exist. + * If a key was specified, only the value of that property is returned. + */ + public static getActiveModelData(): AvatarModel; + public static getActiveModelData(key: T): AvatarModel[T]; + public static getActiveModelData(key?: T): AvatarModel | AvatarModel[T] { + const activeModel = userStore.avatar.activeModel; + if (key) { + return this.getModelData(activeModel, key); + } + return this.getModelData(activeModel); + } + + /** + * @returns A stringified map of all the stored avatar models. + */ + public static getAllModelsJSON(): string { + return JSON.stringify(userStore.avatar.models); + } + + /** + * Set the value of a specific property of an avatar model. + * @param modelId The ID of the model to update. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setModelData(modelId: string | number, key: T, value: AvatarModel[T]): void { + if (modelId in userStore.avatar.models) { + userStore.avatar.models[modelId][key] = value; + // If the model's file was updated, make it the active model so there is immediate feedback on that change. + if (key === "file") { + this.setActiveModel(modelId); + } + } + } + + /** + * Set the value of a specific property of the avatar model currently equipped by the user. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setActiveModelData(key: keyof AvatarModel, value: string | number | boolean): void { + const activeModel = userStore.avatar.activeModel; + this.setModelData(activeModel, key, value); + } + + /** + * Create an entry for a new avatar model in the Store. + * @param modelData The data (name, thumbnail, scale, etc) for the new avatar model. + * @param setToActive `(Optional)` Set equip this model. + * @returns The ID of the new model. + */ + public static createNewModel(modelData: AvatarModel, setToActive = true): string { + const ID = this._generateID(); + userStore.avatar.models[ID] = modelData; + if (setToActive) { + this.setActiveModel(ID); + } + return ID; + } + + /** + * Remove a model from the Store. + * @param modelId The ID of the model to remove. + */ + public static removeModel(modelId: string | number): void { + // Prevent the fallback model from being deleted. + if (modelId === fallbackAvatarId()) { + return; + } + + // Switch to the fallback model if the removed model is currently equipped. + if (modelId === userStore.avatar.activeModel) { + this.setActiveModel(fallbackAvatarId()); + } + + // Remove the requested model from the Store. + if (modelId in userStore.avatar.models) { + delete userStore.avatar.models[modelId]; + } + } + + /** + * Equip a particular model. + * @param modelId The ID of the model to equip. + */ + public static setActiveModel(modelId: string | number): void { + if (modelId in userStore.avatar.models) { + userStore.avatar.activeModel = typeof modelId === "number" ? modelId.toString() : modelId; + } + try { + const scene = Renderer.getScene(); + scene.loadMyAvatar(AvatarStoreInterface.getModelData(modelId, "file")).catch((err) => console.warn("Failed to load avatar:", err)); + } catch (error) { + console.warn("Cannot render active avatar model before the scene has been loaded.", error); + } + } +} diff --git a/src/stores/user-store.ts b/src/stores/user-store.ts index cfde9962..dadf1f4a 100644 --- a/src/stores/user-store.ts +++ b/src/stores/user-store.ts @@ -1,190 +1,190 @@ -// -// user-store.ts -// -// Created by Giga on 30 May 2023. -// Copyright 2023 Vircadia contributors. -// Copyright 2023 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { defineStore } from "pinia"; -import { useStorage } from "@vueuse/core"; -import { Vec3 } from "@vircadia/web-sdk"; -import { onAttributeChangePayload } from "@Modules/account"; -import { defaultActiveAvatarId, defaultAvatars } from "@Modules/avatar/DefaultModels"; -import type { Domain } from "@Base/modules/domain/domain"; -import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; -import { DataMapper } from "@Modules/domain/dataMapper"; -import type { vec3 } from "@vircadia/web-sdk"; - -const persistentStorageMedium = localStorage; - -const defaultControls = { - keyboard: { - movement: { - walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, - walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, - walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, - walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, - run: { name: "Run", keycode: "ShiftLeft" } as Keybind, - jump: { name: "Jump", keycode: "Space" } as Keybind, - crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, - fly: { name: "Fly", keycode: "KeyF" } as Keybind, - sit: { name: "Sit", keycode: "KeyG" } as Keybind, - clap: { name: "Clap", keycode: "KeyH" } as Keybind, - salute: { name: "Salute", keycode: "KeyJ" } as Keybind - }, - camera: { - pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, - pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, - yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, - yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, - firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, - thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, - collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind - }, - audio: { - mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, - pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind - }, - other: { - resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, - toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, - openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind - } - }, - mouse: { - acceleration: true, - invert: false, - sensitivity: 50 - } -}; - -export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; -export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; -export interface Keybind { - name: string, - keycode: string -} - -export interface LocationBookmark { - name: string, - color: string, - url: string -} - -export const useUserStore = defineStore("user", { - state: () => ({ - avatar: useStorage( - "userAvatarSettings", - { - displayName: "anonymous", - showLabels: true, - position: Vec3.ZERO, - location: "0,0,0", - avatars: defaultAvatars(), - activeAvatar: defaultActiveAvatarId() - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Graphics configuration. - graphics: useStorage( - "userGraphicsSettings", - { - fieldOfView: 85, - bloom: true, - fxaaEnabled: true, - msaa: 2, - sharpen: false, - fpsCounter: false, - cameraBobbing: true - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Information about the logged in account. Refer to Account module. - account: useStorage( - "userAccountSettings", - { - id: "UNKNOWN", - username: "Guest", - isLoggedIn: false, - accessToken: "UNKNOWN", - tokenType: "Bearer", - scope: "UNKNOWN", - isAdmin: false, - useAsAdmin: false, - images: { - hero: undefined as string | undefined, - tiny: undefined as string | undefined, - thumbnail: undefined as string | undefined - } - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Saved bookmarks. - bookmarks: useStorage( - "userBookmarks", - { - locations: [] as Array - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ), - // Controls. - controls: useStorage("userControlSettings", - defaultControls, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ) - }), - - actions: { - /** - * Reset the state of the store to default. - */ - reset(): void { - this.$reset(); - }, - /** - * Update the stored account information for the current user. - * @param data - */ - updateAccountInfo(data: onAttributeChangePayload): void { - this.account.accessToken = data.accessToken; - this.account.isAdmin = data.isAdmin; - this.account.isLoggedIn = data.isLoggedIn; - this.account.scope = data.scope; - this.account.tokenType = data.accessTokenType; - this.account.username = data.accountInfo.username; - Object.assign(this.account.images, data.accountInfo.images ?? {}); - }, - /** - * Update the stored information for the local avatar. - * @param domain A reference to the domain server connection instance. - * @param domainAvatar `(Optional)` A reference to the local avatar instance. - * @param position `(Optional)` The new position of the local avatar in the world. - */ - updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { - const domainLocation = domain.DomainClient - ? domain.Location.protocol + "//" + domain.Location.host - : "Disconnected"; - if (domainAvatar) { - const myAvaInfo = domainAvatar.MyAvatar; - this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; - this.avatar.location - = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; - this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; - } - // An optional update to just the avatar's position. - if (position) { - this.avatar.position = position; - this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; - } - } - } -}); +// +// user-store.ts +// +// Created by Giga on 30 May 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { defineStore } from "pinia"; +import { useStorage } from "@vueuse/core"; +import { Vec3 } from "@vircadia/web-sdk"; +import { onAttributeChangePayload } from "@Modules/account"; +import { defaultActiveAvatarId, defaultAvatars } from "@Modules/avatar/DefaultModels"; +import type { Domain } from "@Base/modules/domain/domain"; +import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; +import { DataMapper } from "@Modules/domain/dataMapper"; +import type { vec3 } from "@vircadia/web-sdk"; + +const persistentStorageMedium = localStorage; + +const defaultControls = { + keyboard: { + movement: { + walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, + walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, + walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, + walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, + run: { name: "Run", keycode: "ShiftLeft" } as Keybind, + jump: { name: "Jump", keycode: "Space" } as Keybind, + crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, + fly: { name: "Fly", keycode: "KeyF" } as Keybind, + sit: { name: "Sit", keycode: "KeyG" } as Keybind, + clap: { name: "Clap", keycode: "KeyH" } as Keybind, + salute: { name: "Salute", keycode: "KeyJ" } as Keybind + }, + camera: { + pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, + pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, + yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, + yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, + firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, + thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, + collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind + }, + audio: { + mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, + pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind + }, + other: { + resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, + toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, + openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind + } + }, + mouse: { + acceleration: true, + invert: false, + sensitivity: 50 + } +}; + +export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; +export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; +export interface Keybind { + name: string, + keycode: string +} + +export interface LocationBookmark { + name: string, + color: string, + url: string +} + +export const useUserStore = defineStore("user", { + state: () => ({ + avatar: useStorage( + "userAvatarSettings", + { + displayName: "anonymous", + showLabels: true, + position: Vec3.ZERO, + location: "0,0,0", + models: defaultAvatars(), + activeModel: defaultActiveAvatarId() + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Graphics configuration. + graphics: useStorage( + "userGraphicsSettings", + { + fieldOfView: 85, + bloom: true, + fxaaEnabled: true, + msaa: 2, + sharpen: false, + fpsCounter: false, + cameraBobbing: true + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Information about the logged in account. Refer to Account module. + account: useStorage( + "userAccountSettings", + { + id: "UNKNOWN", + username: "Guest", + isLoggedIn: false, + accessToken: "UNKNOWN", + tokenType: "Bearer", + scope: "UNKNOWN", + isAdmin: false, + useAsAdmin: false, + images: { + hero: undefined as string | undefined, + tiny: undefined as string | undefined, + thumbnail: undefined as string | undefined + } + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Saved bookmarks. + bookmarks: useStorage( + "userBookmarks", + { + locations: [] as Array + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ), + // Controls. + controls: useStorage("userControlSettings", + defaultControls, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ) + }), + + actions: { + /** + * Reset the state of the store to default. + */ + reset(): void { + this.$reset(); + }, + /** + * Update the stored account information for the current user. + * @param data + */ + updateAccountInfo(data: onAttributeChangePayload): void { + this.account.accessToken = data.accessToken; + this.account.isAdmin = data.isAdmin; + this.account.isLoggedIn = data.isLoggedIn; + this.account.scope = data.scope; + this.account.tokenType = data.accessTokenType; + this.account.username = data.accountInfo.username; + Object.assign(this.account.images, data.accountInfo.images ?? {}); + }, + /** + * Update the stored information for the local avatar. + * @param domain A reference to the domain server connection instance. + * @param domainAvatar `(Optional)` A reference to the local avatar instance. + * @param position `(Optional)` The new position of the local avatar in the world. + */ + updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { + const domainLocation = domain.DomainClient + ? domain.Location.protocol + "//" + domain.Location.host + : "Disconnected"; + if (domainAvatar) { + const myAvaInfo = domainAvatar.MyAvatar; + this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; + this.avatar.location + = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; + this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; + } + // An optional update to just the avatar's position. + if (position) { + this.avatar.position = position; + this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; + } + } + } +}); From 5bd5d5dac7efc5f8be76c0ff38ba8a9ca5c9b604 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Tue, 1 Oct 2024 02:21:47 +1000 Subject: [PATCH 13/14] Update how avatars are handled. --- quasar.config.js | 213 ++++++++++++++ src/modules/avatar/DefaultModels.ts | 341 +++++----------------- src/modules/avatar/StoreInterface.ts | 317 ++++++++++---------- src/stores/user-store.ts | 415 +++++++++++++++------------ src/vircadia-world | 2 +- 5 files changed, 667 insertions(+), 621 deletions(-) diff --git a/quasar.config.js b/quasar.config.js index 7d589f1a..ca1712e6 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -180,6 +180,219 @@ module.exports = configure(function (ctx) { process.env.VRCA_WIZARD_BUTTON_TEXT ?? "Get Started", // Desktop App VRCA_DESKTOP_MODE: process.env.VRCA_DESKTOP_MODE, + // Profile + // Profile + VRCA_DEFAULT_AVATAR_DISPLAY_NAME: + process.env.VRCA_DEFAULT_AVATAR_DISPLAY_NAME ?? "anonymous", + VRCA_DEFAULT_AVATARS: + process.env.VRCA_DEFAULT_AVATARS ?? + JSON.stringify({ + HTP45FSQ: { + name: "Sara", + image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", + file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", + scale: 1, + starred: true, + }, + KLM23NOP: { + name: "Mark", + image: "/assets/models/avatars/Mark-small.webp", + file: "/assets/models/avatars/Mark.glb", + scale: 1, + starred: false, + }, + QRS78TUV: { + name: "Megan", + image: "/assets/models/avatars/Megan-small.webp", + file: "/assets/models/avatars/Megan.glb", + scale: 1, + starred: false, + }, + WXY12ZAB: { + name: "Jack", + image: "/assets/models/avatars/Jack-small.webp", + file: "/assets/models/avatars/Jack.glb", + scale: 1, + starred: false, + }, + CDE56FGH: { + name: "Martha", + image: "/assets/models/avatars/Martha-small.webp", + file: "/assets/models/avatars/Martha.glb", + scale: 1, + starred: false, + }, + IJK90LMN: { + name: "Miles", + image: "/assets/models/avatars/Miles-small.webp", + file: "/assets/models/avatars/Miles.glb", + scale: 1, + starred: false, + }, + OPQ34RST: { + name: "Taylor", + image: "/assets/models/avatars/Taylor-small.webp", + file: "/assets/models/avatars/Taylor.glb", + scale: 1, + starred: false, + }, + UVW78XYZ: { + name: "Tiffany", + image: "/assets/models/avatars/Tiffany-small.webp", + file: "/assets/models/avatars/Tiffany.glb", + scale: 1, + starred: false, + }, + ABC12DEF: { + name: "Victor", + image: "/assets/models/avatars/Victor-small.webp", + file: "/assets/models/avatars/Victor.glb", + scale: 1, + starred: false, + }, + GHI56JKL: { + name: "Audrey", + image: "/assets/models/avatars/Audrey-small.webp", + file: "/assets/models/avatars/Audrey.glb", + scale: 1, + starred: false, + }, + MNO90PQR: { + name: "Kristine", + image: "/assets/models/avatars/Kristine-small.webp", + file: "/assets/models/avatars/Kristine.glb", + scale: 1, + starred: false, + }, + STU34VWX: { + name: "William", + image: "/assets/models/avatars/William-small.webp", + file: "/assets/models/avatars/William.glb", + scale: 1, + starred: false, + }, + YZA78BCD: { + name: "Erica", + image: "/assets/models/avatars/Erica-small.webp", + file: "/assets/models/avatars/Erica.glb", + scale: 1, + starred: false, + }, + EFG12HIJ: { + name: "Samantha", + image: "/assets/models/avatars/Samantha-small.webp", + file: "/assets/models/avatars/Samantha.glb", + scale: 1, + starred: false, + }, + KLM56NOP: { + name: "Roman", + image: "/assets/models/avatars/Roman-small.webp", + file: "/assets/models/avatars/Roman.glb", + scale: 1, + starred: false, + }, + QRS90TUV: { + name: "Cathy", + image: "/assets/models/avatars/Cathy-small.webp", + file: "/assets/models/avatars/Cathy.glb", + scale: 1, + starred: false, + }, + WXY34ZAB: { + name: "Lucas", + image: "/assets/models/avatars/Lucas-small.webp", + file: "/assets/models/avatars/Lucas.glb", + scale: 1, + starred: false, + }, + CDE78FGH: { + name: "Michaella", + image: "/assets/models/avatars/Michaella-small.webp", + file: "/assets/models/avatars/Michaella.glb", + scale: 1, + starred: false, + }, + IJK12LMN: { + name: "David", + image: "/assets/models/avatars/David-small.webp", + file: "/assets/models/avatars/David.glb", + scale: 1, + starred: false, + }, + OPQ56RST: { + name: "Rochella", + image: "/assets/models/avatars/Rochella-small.webp", + file: "/assets/models/avatars/Rochella.glb", + scale: 1, + starred: false, + }, + UVW90XYZ: { + name: "Susan", + image: "/assets/models/avatars/Susan-small.webp", + file: "/assets/models/avatars/Susan.glb", + scale: 1, + starred: false, + }, + ABC34DEF: { + name: "Diego", + image: "/assets/models/avatars/Diego-small.webp", + file: "/assets/models/avatars/Diego.glb", + scale: 1, + starred: false, + }, + GHI78JKL: { + name: "Jameson", + image: "/assets/models/avatars/Jameson-small.webp", + file: "/assets/models/avatars/Jameson.glb", + scale: 1, + starred: false, + }, + MNO12PQR: { + name: "Kevin", + image: "/assets/models/avatars/Kevin-small.webp", + file: "/assets/models/avatars/Kevin.glb", + scale: 1, + starred: false, + }, + STU56VWX: { + name: "Lila", + image: "/assets/models/avatars/Lila-small.webp", + file: "/assets/models/avatars/Lila.glb", + scale: 1, + starred: false, + }, + YZA90BCD: { + name: "Vikki", + image: "/assets/models/avatars/Vikki-small.webp", + file: "/assets/models/avatars/Vikki.glb", + scale: 1, + starred: false, + }, + EFG34HIJ: { + name: "Jonas", + image: "/assets/models/avatars/Jonas-small.webp", + file: "/assets/models/avatars/Jonas.glb", + scale: 1, + starred: false, + }, + KLM78NOP: { + name: "Kelly", + image: "/assets/models/avatars/Kelly-small.webp", + file: "/assets/models/avatars/Kelly.glb", + scale: 1, + starred: false, + }, + }), + VRCA_FALLBACK_AVATAR: + process.env.VRCA_FALLBACK_AVATAR ?? + JSON.stringify({ + name: "Maria", + image: "/assets/models/avatars/Maria-small.webp", + file: "/assets/models/avatars/Maria.glb", + scale: 1, + starred: false, + }), }, }, diff --git a/src/modules/avatar/DefaultModels.ts b/src/modules/avatar/DefaultModels.ts index 978e8f5d..232122ee 100644 --- a/src/modules/avatar/DefaultModels.ts +++ b/src/modules/avatar/DefaultModels.ts @@ -1,271 +1,70 @@ -// -// DefaultModels.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -// TODO: Get most of these variables from the config instead of this file (so it can be overridden with environment variables correctly). - -const modelRoot = "/assets/models/avatars/"; - -export interface AvatarModel { - name: string, - image: string, - file: string, - scale: number, - starred: boolean -} - -export interface AvatarModelMap { - [key: string]: AvatarModel -} - -/** - * @returns The URL of the default avatar model. - */ -export function defaultActiveAvatarUrl(): string { - return `${modelRoot}sara.glb`; -} - -/** - * @returns The ID of the default avatar model. - */ -export function defaultActiveAvatarId(): string { - return "HTP45FSQ"; -} - -/** - * @returns The fallback avatar model. - */ -export function fallbackAvatar(): AvatarModel { - return { - name: "Maria", - image: `${modelRoot}Maria-small.webp`, - file: `${modelRoot}default_avatar.glb`, - scale: 1, - starred: false - }; -} - -/** - * @returns The URL of the fallback avatar model. - */ -export function fallbackAvatarUrl(): string { - return fallbackAvatar().file; -} - -/** - * @returns The ID of the fallback avatar model. - */ -export function fallbackAvatarId(): string { - return "FALLBACK"; -} - -/** - * @returns The default collection of avatar models. - */ -export function defaultAvatars(): AvatarModelMap { - return { - HTP45FSQ: { - name: "Sara", - image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", - file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", - scale: 1, - starred: true - } as AvatarModel, - ZPNSHHIJ: { - name: "Mark", - image: `${modelRoot}Mark-small.webp`, - file: `${modelRoot}Mark.glb`, - scale: 1, - starred: false - } as AvatarModel, - C5E0NT3P: { - name: "Megan", - image: `${modelRoot}Megan-small.webp`, - file: `${modelRoot}Megan.glb`, - scale: 1, - starred: false - } as AvatarModel, - HYGME2O8: { - name: "Jack", - image: `${modelRoot}Jack-small.webp`, - file: `${modelRoot}Jack.glb`, - scale: 1, - starred: false - } as AvatarModel, - AIOUPXVY: { - name: "Martha", - image: `${modelRoot}Martha-small.webp`, - file: `${modelRoot}Martha.glb`, - scale: 1, - starred: false - } as AvatarModel, - LRX76LNL: { - name: "Miles", - image: `${modelRoot}Miles-small.webp`, - file: `${modelRoot}Miles.glb`, - scale: 1, - starred: false - } as AvatarModel, - HTLZ3SVU: { - name: "Taylor", - image: `${modelRoot}Taylor-small.webp`, - file: `${modelRoot}Taylor.glb`, - scale: 1, - starred: false - } as AvatarModel, - EPS62RC9: { - name: "Tiffany", - image: `${modelRoot}Tiffany-small.webp`, - file: `${modelRoot}Tiffany.glb`, - scale: 1, - starred: false - } as AvatarModel, - QIA9XG4G: { - name: "Victor", - image: `${modelRoot}Victor-small.webp`, - file: `${modelRoot}Victor.glb`, - scale: 1, - starred: false - } as AvatarModel, - N5PBHE7C: { - name: "Audrey", - image: `${modelRoot}Audrey-small.webp`, - file: `${modelRoot}Audrey.glb`, - scale: 1, - starred: false - } as AvatarModel, - E7RCM559: { - name: "Kristine", - image: `${modelRoot}Kristine-small.webp`, - file: `${modelRoot}Kristine.glb`, - scale: 1, - starred: false - } as AvatarModel, - SG35OH2Y: { - name: "William", - image: `${modelRoot}William-small.webp`, - file: `${modelRoot}William.glb`, - scale: 1, - starred: false - } as AvatarModel, - JKV34GST: { - name: "Erica", - image: `${modelRoot}Erica-small.webp`, - file: `${modelRoot}Erica.glb`, - scale: 1, - starred: false - } as AvatarModel, - X5AII7GT: { - name: "Samantha", - image: `${modelRoot}Samantha-small.webp`, - file: `${modelRoot}Samantha.glb`, - scale: 1, - starred: false - } as AvatarModel, - ZGK9IGRB: { - name: "Roman", - image: `${modelRoot}Roman-small.webp`, - file: `${modelRoot}Roman.glb`, - scale: 1, - starred: false - } as AvatarModel, - DBYRNKR8: { - name: "Cathy", - image: `${modelRoot}Cathy-small.webp`, - file: `${modelRoot}Cathy.glb`, - scale: 1, - starred: false - } as AvatarModel, - EG1XOUR4: { - name: "Lucas", - image: `${modelRoot}Lucas-small.webp`, - file: `${modelRoot}Lucas.glb`, - scale: 1, - starred: false - } as AvatarModel, - OPX471R4: { - name: "Michaella", - image: `${modelRoot}Michaella-small.webp`, - file: `${modelRoot}Michaella.glb`, - scale: 1, - starred: false - } as AvatarModel, - V5DYP68J: { - name: "David", - image: `${modelRoot}David-small.webp`, - file: `${modelRoot}David.glb`, - scale: 1, - starred: false - } as AvatarModel, - M9G7AFFC: { - name: "Rochella", - image: `${modelRoot}Rochella-small.webp`, - file: `${modelRoot}Rochella.glb`, - scale: 1, - starred: false - } as AvatarModel, - LHUVJ7RA: { - name: "Susan", - image: `${modelRoot}Susan-small.webp`, - file: `${modelRoot}Susan.glb`, - scale: 1, - starred: false - } as AvatarModel, - EQQC5125: { - name: "Diego", - image: `${modelRoot}Diego-small.webp`, - file: `${modelRoot}Diego.glb`, - scale: 1, - starred: false - } as AvatarModel, - GYC8OLSF: { - name: "Jameson", - image: `${modelRoot}Jameson-small.webp`, - file: `${modelRoot}Jameson.glb`, - scale: 1, - starred: false - } as AvatarModel, - OFTR0UR0: { - name: "Kevin", - image: `${modelRoot}Kevin-small.webp`, - file: `${modelRoot}Kevin.glb`, - scale: 1, - starred: false - } as AvatarModel, - VO3YR5QC: { - name: "Lila", - image: `${modelRoot}Lila-small.webp`, - file: `${modelRoot}Lila.glb`, - scale: 1, - starred: false - } as AvatarModel, - S4Q8O9CE: { - name: "Vikki", - image: `${modelRoot}Vikki-small.webp`, - file: `${modelRoot}Vikki.glb`, - scale: 1, - starred: false - } as AvatarModel, - ETQZ8G3W: { - name: "Jonas", - image: `${modelRoot}Jonas-small.webp`, - file: `${modelRoot}Jonas.glb`, - scale: 1, - starred: false - } as AvatarModel, - D8WRU1KS: { - name: "Kelly", - image: `${modelRoot}Kelly-small.webp`, - file: `${modelRoot}Kelly.glb`, - scale: 1, - starred: false - } as AvatarModel, - FALLBACK: fallbackAvatar() - } as AvatarModelMap; -} +// +// DefaultModels.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const modelRoot = "/assets/models/avatars/" + +export interface AvatarModel { + name: string, + image: string, + file: string, + scale: number, + starred: boolean +} + +export interface AvatarModelMap { + [key: string]: AvatarModel +} + +/** + * @returns The URL of the default avatar model. + */ +export function defaultActiveAvatarUrl(): string { + return `${modelRoot}sara.glb`; +} + +/** + * @returns The ID of the default avatar model. + */ +export function defaultActiveAvatarId(): string { + return "HTP45FSQ"; +} + +/** + * @returns The fallback avatar model. + */ +export function fallbackAvatar(): AvatarModel { + const configFallbackAvatar = JSON.parse(process.env.VRCA_FALLBACK_AVATAR as string); + + return configFallbackAvatar as AvatarModel; +} + +/** + * @returns The URL of the fallback avatar model. + */ +export function fallbackAvatarUrl(): string { + return fallbackAvatar().file; +} + +/** + * @returns The ID of the fallback avatar model. + */ +export function fallbackAvatarId(): string { + return "FALLBACK"; +} + +/** + * @returns The default collection of avatar models. + */ +export function defaultAvatars(): AvatarModelMap { + const configDefaultAvatars = JSON.parse(process.env.VRCA_DEFAULT_AVATARS ?? JSON.stringify([fallbackAvatar()])); + + return configDefaultAvatars as AvatarModelMap; +} diff --git a/src/modules/avatar/StoreInterface.ts b/src/modules/avatar/StoreInterface.ts index 818085bb..76b71d94 100644 --- a/src/modules/avatar/StoreInterface.ts +++ b/src/modules/avatar/StoreInterface.ts @@ -1,159 +1,158 @@ -// -// StoreInterface.ts -// -// Created by Giga on 1 Sep 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { fallbackAvatar, fallbackAvatarId, type AvatarModel } from "./DefaultModels"; -import { userStore } from "@Stores/index"; -import { Renderer } from "@Modules/scene"; - -/** - * Static methods for interacting with the list of avatar models in the Store. - */ -export class AvatarStoreInterface { - /** - * Generate a random, alpha-numeric, 8 character long ID string that is unique within the avatar Store. - * @returns An ID string. - */ - private static _generateID(): string { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - const idLength = 8; - function generate(): string { - let id = ""; - for (let i = 0; i < idLength; i += 1) { - id += chars[Math.floor(Math.random() * chars.length)]; - } - return id; - } - let uniqueId = generate(); - // Ensure that the ID doesn't already exist in the Store. - while (uniqueId in userStore.avatar.models) { - uniqueId = generate(); - } - return uniqueId; - } - - /** - * Retrieve the data (name, thumbnail, scale, etc) for a given avatar model. - * @param modelId The ID of the model to retrieve. - * @param key `(Optional)` A specific property of the model to retrieve. - * @returns The data for the requested avatar model, or the fallback model if the requested one doesn't exist. - * If a key was specified, only the value of that property is returned. - */ - public static getModelData(modelId: string | number): AvatarModel; - public static getModelData(modelId: string | number, key: T): AvatarModel[T]; - public static getModelData(modelId: string | number, key?: T): AvatarModel | AvatarModel[T] { - const models = userStore.avatar.models; - if (key && key in (models[modelId] || fallbackAvatar())) { - return models[modelId][key]; - } - return models[modelId] || fallbackAvatar(); - } - - /** - * Retrieve the data (name, thumbnail, scale, etc) for the avatar model currently equipped by the user. - * @param key `(Optional)` A specific property of the model to retrieve. - * @returns The data for the currently equipped avatar model, or the fallback model if the currently equipped one doesn't exist. - * If a key was specified, only the value of that property is returned. - */ - public static getActiveModelData(): AvatarModel; - public static getActiveModelData(key: T): AvatarModel[T]; - public static getActiveModelData(key?: T): AvatarModel | AvatarModel[T] { - const activeModel = userStore.avatar.activeModel; - if (key) { - return this.getModelData(activeModel, key); - } - return this.getModelData(activeModel); - } - - /** - * @returns A stringified map of all the stored avatar models. - */ - public static getAllModelsJSON(): string { - return JSON.stringify(userStore.avatar.models); - } - - /** - * Set the value of a specific property of an avatar model. - * @param modelId The ID of the model to update. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setModelData(modelId: string | number, key: T, value: AvatarModel[T]): void { - if (modelId in userStore.avatar.models) { - userStore.avatar.models[modelId][key] = value; - // If the model's file was updated, make it the active model so there is immediate feedback on that change. - if (key === "file") { - this.setActiveModel(modelId); - } - } - } - - /** - * Set the value of a specific property of the avatar model currently equipped by the user. - * @param key The property to update. - * @param value The new value for that property. - */ - public static setActiveModelData(key: keyof AvatarModel, value: string | number | boolean): void { - const activeModel = userStore.avatar.activeModel; - this.setModelData(activeModel, key, value); - } - - /** - * Create an entry for a new avatar model in the Store. - * @param modelData The data (name, thumbnail, scale, etc) for the new avatar model. - * @param setToActive `(Optional)` Set equip this model. - * @returns The ID of the new model. - */ - public static createNewModel(modelData: AvatarModel, setToActive = true): string { - const ID = this._generateID(); - userStore.avatar.models[ID] = modelData; - if (setToActive) { - this.setActiveModel(ID); - } - return ID; - } - - /** - * Remove a model from the Store. - * @param modelId The ID of the model to remove. - */ - public static removeModel(modelId: string | number): void { - // Prevent the fallback model from being deleted. - if (modelId === fallbackAvatarId()) { - return; - } - - // Switch to the fallback model if the removed model is currently equipped. - if (modelId === userStore.avatar.activeModel) { - this.setActiveModel(fallbackAvatarId()); - } - - // Remove the requested model from the Store. - if (modelId in userStore.avatar.models) { - delete userStore.avatar.models[modelId]; - } - } - - /** - * Equip a particular model. - * @param modelId The ID of the model to equip. - */ - public static setActiveModel(modelId: string | number): void { - if (modelId in userStore.avatar.models) { - userStore.avatar.activeModel = typeof modelId === "number" ? modelId.toString() : modelId; - } - try { - const scene = Renderer.getScene(); - scene.loadMyAvatar(AvatarStoreInterface.getModelData(modelId, "file")).catch((err) => console.warn("Failed to load avatar:", err)); - } catch (error) { - console.warn("Cannot render active avatar model before the scene has been loaded.", error); - } - } -} +// +// StoreInterface.ts +// +// Created by Giga on 1 Sep 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { fallbackAvatar, fallbackAvatarId, type AvatarModel } from "./DefaultModels"; +import { userStore } from "@Stores/index"; +import { Renderer } from "@Modules/scene"; + +/** + * Static methods for interacting with the list of avatar models in the Store. + */ +export class AvatarStoreInterface { + /** + * Generate a random, alpha-numeric, 8 character long ID string that is unique within the avatar Store. + * @returns An ID string. + */ + private static _generateID(): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const idLength = 8; + function generate(): string { + let id = ""; + for (let i = 0; i < idLength; i += 1) { + id += chars[Math.floor(Math.random() * chars.length)]; + } + return id; + } + let uniqueId = generate(); + // Ensure that the ID doesn't already exist in the Store. + while (uniqueId in userStore.avatar.models) { + uniqueId = generate(); + } + return uniqueId; + } + + /** + * Retrieve the data (name, thumbnail, scale, etc) for a given avatar model. + * @param modelId The ID of the model to retrieve. + * @param key `(Optional)` A specific property of the model to retrieve. + * @returns The data for the requested avatar model, or the fallback model if the requested one doesn't exist. + * If a key was specified, only the value of that property is returned. + */ + public static getModelData(modelId: string | number): AvatarModel; + public static getModelData(modelId: string | number, key: T): AvatarModel[T]; + public static getModelData(modelId: string | number, key?: T): AvatarModel | AvatarModel[T] { + const models = userStore.avatar.models; + const model = models[modelId] || fallbackAvatar(); + + if (key && key in model) { + return model[key]; + } + return model; + } + + /** + * Retrieve the data (name, thumbnail, scale, etc) for the avatar model currently equipped by the user. + * @param key `(Optional)` A specific property of the model to retrieve. + * @returns The data for the currently equipped avatar model, or the fallback model if the currently equipped one doesn't exist. + * If a key was specified, only the value of that property is returned. + */ + public static getActiveModelData(): AvatarModel; + public static getActiveModelData(key: T): AvatarModel[T]; + public static getActiveModelData(key?: T): AvatarModel | AvatarModel[T] { + const activeModel = userStore.avatar.activeModel; + return this.getModelData(activeModel, key as T); + } + + /** + * @returns A stringified map of all the stored avatar models. + */ + public static getAllModelsJSON(): string { + return JSON.stringify(userStore.avatar.models); + } + + /** + * Set the value of a specific property of an avatar model. + * @param modelId The ID of the model to update. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setModelData(modelId: string | number, key: T, value: AvatarModel[T]): void { + if (modelId in userStore.avatar.models) { + userStore.avatar.models[modelId][key] = value; + // If the model's file was updated, make it the active model so there is immediate feedback on that change. + if (key === "file") { + this.setActiveModel(modelId); + } + } + } + + /** + * Set the value of a specific property of the avatar model currently equipped by the user. + * @param key The property to update. + * @param value The new value for that property. + */ + public static setActiveModelData(key: keyof AvatarModel, value: string | number | boolean): void { + const activeModel = userStore.avatar.activeModel; + this.setModelData(activeModel, key, value); + } + + /** + * Create an entry for a new avatar model in the Store. + * @param modelData The data (name, thumbnail, scale, etc) for the new avatar model. + * @param setToActive `(Optional)` Set equip this model. + * @returns The ID of the new model. + */ + public static createNewModel(modelData: AvatarModel, setToActive = true): string { + const ID = this._generateID(); + userStore.avatar.models[ID] = modelData; + if (setToActive) { + this.setActiveModel(ID); + } + return ID; + } + + /** + * Remove a model from the Store. + * @param modelId The ID of the model to remove. + */ + public static removeModel(modelId: string | number): void { + // Prevent the fallback model from being deleted. + if (modelId === fallbackAvatarId()) { + return; + } + + // Switch to the fallback model if the removed model is currently equipped. + if (modelId === userStore.avatar.activeModel) { + this.setActiveModel(fallbackAvatarId()); + } + + // Remove the requested model from the Store. + if (modelId in userStore.avatar.models) { + delete userStore.avatar.models[modelId]; + } + } + + /** + * Equip a particular model. + * @param modelId The ID of the model to equip. + */ + public static setActiveModel(modelId: string | number): void { + if (modelId in userStore.avatar.models) { + userStore.avatar.activeModel = typeof modelId === "number" ? modelId.toString() : modelId; + } + try { + const scene = Renderer.getScene(); + scene.loadMyAvatar(AvatarStoreInterface.getModelData(modelId, "file")).catch((err) => console.warn("Failed to load avatar:", err)); + } catch (error) { + console.warn("Cannot render active avatar model before the scene has been loaded.", error); + } + } +} diff --git a/src/stores/user-store.ts b/src/stores/user-store.ts index dadf1f4a..3fe2aba7 100644 --- a/src/stores/user-store.ts +++ b/src/stores/user-store.ts @@ -1,190 +1,225 @@ -// -// user-store.ts -// -// Created by Giga on 30 May 2023. -// Copyright 2023 Vircadia contributors. -// Copyright 2023 DigiSomni LLC. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import { defineStore } from "pinia"; -import { useStorage } from "@vueuse/core"; -import { Vec3 } from "@vircadia/web-sdk"; -import { onAttributeChangePayload } from "@Modules/account"; -import { defaultActiveAvatarId, defaultAvatars } from "@Modules/avatar/DefaultModels"; -import type { Domain } from "@Base/modules/domain/domain"; -import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; -import { DataMapper } from "@Modules/domain/dataMapper"; -import type { vec3 } from "@vircadia/web-sdk"; - -const persistentStorageMedium = localStorage; - -const defaultControls = { - keyboard: { - movement: { - walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, - walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, - walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, - walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, - run: { name: "Run", keycode: "ShiftLeft" } as Keybind, - jump: { name: "Jump", keycode: "Space" } as Keybind, - crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, - fly: { name: "Fly", keycode: "KeyF" } as Keybind, - sit: { name: "Sit", keycode: "KeyG" } as Keybind, - clap: { name: "Clap", keycode: "KeyH" } as Keybind, - salute: { name: "Salute", keycode: "KeyJ" } as Keybind - }, - camera: { - pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, - pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, - yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, - yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, - firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, - thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, - collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind - }, - audio: { - mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, - pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind - }, - other: { - resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, - toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, - openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind - } - }, - mouse: { - acceleration: true, - invert: false, - sensitivity: 50 - } -}; - -export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; -export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; -export interface Keybind { - name: string, - keycode: string -} - -export interface LocationBookmark { - name: string, - color: string, - url: string -} - -export const useUserStore = defineStore("user", { - state: () => ({ - avatar: useStorage( - "userAvatarSettings", - { - displayName: "anonymous", - showLabels: true, - position: Vec3.ZERO, - location: "0,0,0", - models: defaultAvatars(), - activeModel: defaultActiveAvatarId() - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Graphics configuration. - graphics: useStorage( - "userGraphicsSettings", - { - fieldOfView: 85, - bloom: true, - fxaaEnabled: true, - msaa: 2, - sharpen: false, - fpsCounter: false, - cameraBobbing: true - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Information about the logged in account. Refer to Account module. - account: useStorage( - "userAccountSettings", - { - id: "UNKNOWN", - username: "Guest", - isLoggedIn: false, - accessToken: "UNKNOWN", - tokenType: "Bearer", - scope: "UNKNOWN", - isAdmin: false, - useAsAdmin: false, - images: { - hero: undefined as string | undefined, - tiny: undefined as string | undefined, - thumbnail: undefined as string | undefined - } - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: false } - ), - // Saved bookmarks. - bookmarks: useStorage( - "userBookmarks", - { - locations: [] as Array - }, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ), - // Controls. - controls: useStorage("userControlSettings", - defaultControls, - persistentStorageMedium, - { mergeDefaults: true, listenToStorageChanges: true } - ) - }), - - actions: { - /** - * Reset the state of the store to default. - */ - reset(): void { - this.$reset(); - }, - /** - * Update the stored account information for the current user. - * @param data - */ - updateAccountInfo(data: onAttributeChangePayload): void { - this.account.accessToken = data.accessToken; - this.account.isAdmin = data.isAdmin; - this.account.isLoggedIn = data.isLoggedIn; - this.account.scope = data.scope; - this.account.tokenType = data.accessTokenType; - this.account.username = data.accountInfo.username; - Object.assign(this.account.images, data.accountInfo.images ?? {}); - }, - /** - * Update the stored information for the local avatar. - * @param domain A reference to the domain server connection instance. - * @param domainAvatar `(Optional)` A reference to the local avatar instance. - * @param position `(Optional)` The new position of the local avatar in the world. - */ - updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { - const domainLocation = domain.DomainClient - ? domain.Location.protocol + "//" + domain.Location.host - : "Disconnected"; - if (domainAvatar) { - const myAvaInfo = domainAvatar.MyAvatar; - this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; - this.avatar.location - = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; - this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; - } - // An optional update to just the avatar's position. - if (position) { - this.avatar.position = position; - this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; - } - } - } -}); +// +// user-store.ts +// +// Created by Giga on 30 May 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { defineStore } from "pinia"; +import { useStorage } from "@vueuse/core"; +import { Vec3 } from "@vircadia/web-sdk"; +import { onAttributeChangePayload } from "@Modules/account"; +import { defaultActiveAvatarId, defaultAvatars, type AvatarModelMap } from "@Modules/avatar/DefaultModels"; +import type { Domain } from "@Base/modules/domain/domain"; +import type { DomainAvatarClient } from "@Base/modules/domain/avatar"; +import { DataMapper } from "@Modules/domain/dataMapper"; +import type { vec3 } from "@vircadia/web-sdk"; + +const persistentStorageMedium = localStorage; + +// Function to generate a hash of an object +function generateHash(obj: object): string { + const str = JSON.stringify(obj); + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32-bit integer + } + return Math.abs(hash).toString(16); +} + +// Function to get the current avatar settings with version hash +function getCurrentAvatarSettings() { + const defaultModels = defaultAvatars(); + const modelVersion = generateHash(defaultModels); + + return { + displayName: process.env.VRCA_DEFAULT_AVATAR_DISPLAY_NAME ?? "anonymous", + showLabels: true, + position: Vec3.ZERO, + location: "0,0,0", + models: defaultModels, + activeModel: defaultActiveAvatarId(), + modelVersion + }; +} + +const defaultControls = { + keyboard: { + movement: { + walkForwards: { name: "Walk Forwards", keycode: "KeyW" } as Keybind, + walkBackwards: { name: "Walk Backwards", keycode: "KeyS" } as Keybind, + walkLeft: { name: "Walk Left", keycode: "KeyA" } as Keybind, + walkRight: { name: "Walk Right", keycode: "KeyD" } as Keybind, + run: { name: "Run", keycode: "ShiftLeft" } as Keybind, + jump: { name: "Jump", keycode: "Space" } as Keybind, + crouch: { name: "Crouch", keycode: "KeyC" } as Keybind, + fly: { name: "Fly", keycode: "KeyF" } as Keybind, + sit: { name: "Sit", keycode: "KeyG" } as Keybind, + clap: { name: "Clap", keycode: "KeyH" } as Keybind, + salute: { name: "Salute", keycode: "KeyJ" } as Keybind + }, + camera: { + pitchUp: { name: "Pitch Up", keycode: "ArrowUp" } as Keybind, + pitchDown: { name: "Pitch Down", keycode: "ArrowDown" } as Keybind, + yawLeft: { name: "Yaw Left", keycode: "ArrowLeft" } as Keybind, + yawRight: { name: "Yaw Right", keycode: "ArrowRight" } as Keybind, + firstPerson: { name: "First-Person", keycode: "Digit1" } as Keybind, + thirdPerson: { name: "Third-Person", keycode: "Digit3" } as Keybind, + collisions: { name: "Toggle Collisions", keycode: "Digit4" } as Keybind + }, + audio: { + mute: { name: "Toggle Mic Mute", keycode: "KeyV" } as Keybind, + pushToTalk: { name: "Push-To-Talk", keycode: "KeyB" } as Keybind + }, + other: { + resetPosition: { name: "Reset Position", keycode: "KeyK" } as Keybind, + toggleMenu: { name: "Toggle Menu", keycode: "KeyM" } as Keybind, + openChat: { name: "Open Chat", keycode: "KeyT" } as Keybind + } + }, + mouse: { + acceleration: true, + invert: false, + sensitivity: 50 + } +}; + +export type KeyboardControlCategory = keyof typeof defaultControls.keyboard; +export type KeyboardControl = keyof typeof defaultControls.keyboard[T]; +export interface Keybind { + name: string, + keycode: string +} + +export interface LocationBookmark { + name: string, + color: string, + url: string +} + +export const useUserStore = defineStore("user", { + state: () => ({ + avatar: useStorage( + "userAvatarSettings", + getCurrentAvatarSettings(), + persistentStorageMedium, + { + mergeDefaults: (storageValue, defaults) => { + if (!storageValue || storageValue.modelVersion !== defaults.modelVersion) { + // If the model version has changed, update models and reset active model + return { + ...storageValue, + models: defaults.models, + activeModel: defaults.activeModel, + modelVersion: defaults.modelVersion + }; + } + return { ...defaults, ...storageValue }; + }, + listenToStorageChanges: false + } + ), + // Graphics configuration. + graphics: useStorage( + "userGraphicsSettings", + { + fieldOfView: 85, + bloom: true, + fxaaEnabled: true, + msaa: 2, + sharpen: false, + fpsCounter: false, + cameraBobbing: true + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Information about the logged in account. Refer to Account module. + account: useStorage( + "userAccountSettings", + { + id: "UNKNOWN", + username: "Guest", + isLoggedIn: false, + accessToken: "UNKNOWN", + tokenType: "Bearer", + scope: "UNKNOWN", + isAdmin: false, + useAsAdmin: false, + images: { + hero: undefined as string | undefined, + tiny: undefined as string | undefined, + thumbnail: undefined as string | undefined + } + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: false } + ), + // Saved bookmarks. + bookmarks: useStorage( + "userBookmarks", + { + locations: [] as Array + }, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ), + // Controls. + controls: useStorage("userControlSettings", + defaultControls, + persistentStorageMedium, + { mergeDefaults: true, listenToStorageChanges: true } + ) + }), + + actions: { + /** + * Reset the state of the store to default. + */ + reset(): void { + this.$reset(); + }, + /** + * Update the stored account information for the current user. + * @param data + */ + updateAccountInfo(data: onAttributeChangePayload): void { + this.account.accessToken = data.accessToken; + this.account.isAdmin = data.isAdmin; + this.account.isLoggedIn = data.isLoggedIn; + this.account.scope = data.scope; + this.account.tokenType = data.accessTokenType; + this.account.username = data.accountInfo.username; + Object.assign(this.account.images, data.accountInfo.images ?? {}); + }, + /** + * Update the stored information for the local avatar. + * @param domain A reference to the domain server connection instance. + * @param domainAvatar `(Optional)` A reference to the local avatar instance. + * @param position `(Optional)` The new position of the local avatar in the world. + */ + updateLocalAvatarInfo(domain: Domain, domainAvatar?: DomainAvatarClient, position?: vec3): void { + const domainLocation = domain.DomainClient + ? domain.Location.protocol + "//" + domain.Location.host + : "Disconnected"; + if (domainAvatar) { + const myAvaInfo = domainAvatar.MyAvatar; + this.avatar.displayName = myAvaInfo?.displayName ?? myAvaInfo?.sessionDisplayName ?? "anonymous"; + this.avatar.location + = `${domainLocation}/${DataMapper.vec3ToString(myAvaInfo?.position)}/${DataMapper.quaternionToString(myAvaInfo?.orientation)}`; + this.avatar.position = myAvaInfo?.position ?? Vec3.ZERO; + } + // An optional update to just the avatar's position. + if (position) { + this.avatar.position = position; + this.avatar.location = `${domainLocation}/${DataMapper.vec3ToString(position)}/${DataMapper.quaternionToString(null)}`; + } + } + } +}); diff --git a/src/vircadia-world b/src/vircadia-world index b434d685..d3e21d33 160000 --- a/src/vircadia-world +++ b/src/vircadia-world @@ -1 +1 @@ -Subproject commit b434d685d94b72dcb42ee3ab0a9942223b514075 +Subproject commit d3e21d334eb56c416a6a1f6e406b1b7e9b169fdd From 974811a5bad34b073feef170cd5ce2f7b1e581cf Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Tue, 1 Oct 2024 02:24:12 +1000 Subject: [PATCH 14/14] Reorder script manager handling. --- src/modules/scene/ScriptManager.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/scene/ScriptManager.ts b/src/modules/scene/ScriptManager.ts index 64782bde..968f2445 100644 --- a/src/modules/scene/ScriptManager.ts +++ b/src/modules/scene/ScriptManager.ts @@ -26,6 +26,12 @@ export class ScriptManager { const meshMetadata = new MeshTypes.Metadata(metadataExtras as Partial); + const script: string = meshMetadata.vircadia_script ?? ''; + + if (!script) { + continue; + } + if (!scene.actionManager) { scene.actionManager = new ActionManager(scene); } @@ -34,12 +40,6 @@ export class ScriptManager { mesh.actionManager = new ActionManager(scene); } - const script: string = meshMetadata.vircadia_script ?? ''; - - if (!script) { - continue; - } - try { // Wrap the script execution in a setTimeout to ensure the mesh is fully initialized setTimeout(() => {