diff --git a/index.html b/index.html index cad6dfa..70ae5c3 100644 --- a/index.html +++ b/index.html @@ -56,10 +56,6 @@ href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&display=swap" rel="stylesheet" /> - AresRPG.world = 0.4" } }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5648,6 +5719,11 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -5783,6 +5859,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/iota-array": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", @@ -6170,10 +6254,9 @@ } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -6236,8 +6319,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6589,6 +6671,17 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6857,7 +6950,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7134,6 +7226,14 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7319,6 +7419,16 @@ "asap": "~2.0.3" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -7535,6 +7645,84 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-interactive": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-interactive/-/react-interactive-0.8.3.tgz", + "integrity": "sha512-mmRvA9aKP7zu9kVfP1AX1egX8tFlnE3DDXq92z0JTZezfOpmeQBzr77O1+mTV54OOmn+M2t6c5kFD5VnuFoM7A==", + "dependencies": { + "detect-it": "^3.0.3", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-router": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", + "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "dependencies": { + "history": "^4.7.2", + "hoist-non-react-statics": "^2.5.0", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", + "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "dependencies": { + "history": "^4.7.2", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.1", + "react-router": "^4.3.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=15" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7580,8 +7768,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -7663,7 +7850,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7694,6 +7880,11 @@ "node": ">=4" } }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -7847,6 +8038,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7893,6 +8090,15 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", "dev": true }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -8499,6 +8705,16 @@ "node": ">=0.6.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -8535,6 +8751,33 @@ "punycode": "^2.1.0" } }, + "node_modules/troika-three-text": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.0.tgz", + "integrity": "sha512-sn9BNC6eIX8OO3iAkPwjecJ7Pn21Ve8P1UNFMNeQzXx759rrqS4i4pSZs7FLMYdWyCKVXBFGimBySFwRKLjq/Q==", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.49.0", + "troika-worker-utils": "^0.49.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.49.0.tgz", + "integrity": "sha512-umitFL4cT+Fm/uONmaQEq4oZlyRHWwVClaS6ZrdcueRvwc2w+cpNQ47LlJKJswpqtMFWbEhOLy0TekmcPZOdYA==", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.49.0.tgz", + "integrity": "sha512-1xZHoJrG0HFfCvT/iyN41DvI/nRykiBtHqFkGaGgJwq5iXfIZFBiPPEHFpPpgyKM3Oo5ITHXP5wM2TNQszYdVg==" + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -8833,6 +9076,11 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "node_modules/vite": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", @@ -9123,6 +9371,19 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", diff --git a/package.json b/package.json index 8dca33f..d069a62 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@vueuse/core": "^10.7.0", "alea": "^1.0.1", "aresrpg-protocol": "git+https://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:x-oauth-basic@github.com/aresrpg/aresrpg-protocol.git", + "boxicons": "^2.1.4", "camera-controls": "^2.7.3", "dat.gui": "^0.7.9", "fast-merge-async-iterators": "^1.0.7", @@ -38,6 +39,7 @@ "stats.js": "^0.17.0", "three": "^0.159.0", "three-mesh-bvh": "^0.6.8", + "troika-three-text": "^0.49.0", "vue": "^3.3.11", "vue-router": "^4.2.5", "vuesax-alpha": "^0.2.0-beta.62", diff --git a/src/game.js b/src/game.js index 6bf5417..c35203d 100644 --- a/src/game.js +++ b/src/game.js @@ -206,8 +206,6 @@ async function create_context({ send_packet, connect_ws }) { const renderer = new WebGLRenderer() - const Pool = await create_pools({ scene, world }) - renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(window.innerWidth, window.innerHeight) renderer.setClearColor(0x263238 / 2, 1) @@ -226,6 +224,7 @@ async function create_context({ send_packet, connect_ws }) { 2000, // Far clipping plane ) + const Pool = await create_pools({ scene, world, camera }) const orthographic_camera = new OrthographicCamera() camera.far = 2000 @@ -358,7 +357,7 @@ export default async function create_game({ const state = get_state() const delta_seconds = game_delta / 1000 - world.step() + if (state.game_state === 'GAME') world.step() permanent_modules .map(({ tick }) => tick) diff --git a/src/interface/menu.vue b/src/interface/menu.vue index ef94b19..d65c15b 100644 --- a/src/interface/menu.vue +++ b/src/interface/menu.vue @@ -3,6 +3,12 @@ nav img.logo(:src="logo") .version build {{ pkg.version }} + .infos(v-if="menu_type === 'PLAY'") + vs-alert(color="#3498DB" type="gradient") + | We moved away from Minecraft as it was too limited for our needs. We are now using our own game engine, which allows us to do much more. We are still in early development, but we are working hard to bring you the best experience possible. Stay tuned! + | #[b If you own an early access key, you can already connect to the game and follow the development.] + template(#title) AresRPG is now a standalone game! + .menu_play(v-if="menu_type === 'PLAY'") img.logo(:src="text_logo") .btns @@ -172,6 +178,17 @@ a font-size .8em color #212121 + .infos + position absolute + top 1em + left 50% + transform translateX(-50%) + b + display flex + padding-top 1em + text-decoration underline + + .menu_characters position absolute background rgba(#212121, .5) diff --git a/src/main.js b/src/main.js index ea9b0aa..45c2367 100644 --- a/src/main.js +++ b/src/main.js @@ -4,6 +4,7 @@ import { BufferGeometry, Mesh } from 'three' import Vuesax from 'vuesax-alpha' import 'vuesax-alpha/dist/index.css' import 'vuesax-alpha/theme-chalk/dark/css-vars.css' +import 'boxicons' import { inject } from '@vercel/analytics' // @ts-ignore diff --git a/src/modules/game_nature.js b/src/modules/game_nature.js index c771429..98fc859 100644 --- a/src/modules/game_nature.js +++ b/src/modules/game_nature.js @@ -142,15 +142,24 @@ export default function () { day_time = time }) + function get_player_chunk_position() { + try { + const player = get_state()?.player + if (!player) return new Vector3() + return to_chunk_position(player.position()) + } catch (error) { + console.error(error) + return new Vector3() + } + } + aiter(abortable(setInterval(day_time_step, null, { signal }))).forEach( () => { // Update day_time and calculate day_ratio day_time = (day_time + day_time_step) % DAY_DURATION const day_ratio = day_time / DAY_DURATION - const chunk_position = to_chunk_position( - get_state()?.player?.position() ?? new Vector3(), - ) + const chunk_position = get_player_chunk_position() const light_base_position = new Vector3( chunk_position.x * CHUNK_SIZE, diff --git a/src/modules/game_world.js b/src/modules/game_world.js index d8c2414..456d283 100644 --- a/src/modules/game_world.js +++ b/src/modules/game_world.js @@ -4,7 +4,11 @@ import { Audio, AudioListener, AudioLoader, + BackSide, + Color, + Mesh, MeshBasicMaterial, + MeshPhongMaterial, Quaternion, Vector3, } from 'three' @@ -15,6 +19,7 @@ import { CHUNK_SIZE, } from 'aresrpg-protocol/src/chunk.js' import { aiter, iter } from 'iterator-helper' +import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' import main_theme from '../assets/sound/main_theme.mp3' import { PLAYER_ID } from '../game.js' @@ -22,13 +27,13 @@ import { compute_animation_state } from '../utils/animation.js' import log from '../utils/logger.js' import { request_chunk_load, - request_low_detail_chunk_load, + request_low_detail_chunks_load, } from '../utils/chunks' import { abortable } from '../utils/iterator' import { create_navmesh } from '../utils/navmesh' const make_chunk_key = ({ x, z }) => `${x}:${z}` -const from_chunk_key = key => { +export const from_chunk_key = key => { const [x, z] = key.split(':') return { x: +x, z: +z } } @@ -38,6 +43,7 @@ const sound = new Audio(listener) const audio_loader = new AudioLoader() const MOVE_UPDATE_INTERVAL = 0.1 +const MAX_TITLE_VIEW_DISTANCE = CHUNK_SIZE * 1.3 const audio_buffer = await audio_loader.loadAsync(main_theme) @@ -50,7 +56,7 @@ export default function () { /** @typedef {{ terrain: import("three").Mesh, collider: import("three").Mesh, enable_collisions: (x: boolean) => void}} chunk */ /** @type {Map} chunk position to chunk */ const loaded_chunks = new Map() - const low_detail_loaded_chunks = new Map() + let low_detail_plane = null const entities = new Map() return { @@ -200,22 +206,31 @@ export default function () { ) loaded_chunks.clear() - low_detail_loaded_chunks.forEach(({ terrain, dispose }) => { - scene.remove(terrain) - dispose() - }) - - low_detail_loaded_chunks.clear() + if (low_detail_plane) { + scene.remove(low_detail_plane) + low_detail_plane.geometry.dispose() + low_detail_plane.material.dispose() + low_detail_plane = null + } camera_controls.colliderMeshes = [] } - events.once('STATE_UPDATED', () => { + events.once('STATE_UPDATED', ({ selected_character_id, characters }) => { sound.play() - const player = Pool.guard.get({ add_rigid_body: true }) + const player = Pool.guard.get({ + add_rigid_body: true, + fixed_title_aspect: true, + }) player.three_body.position.setScalar(0) + const selected_character = characters.find( + ({ id }) => id === selected_character_id, + ) + + player.title.text = selected_character.name + dispatch('action/register_player', { ...player, id: PLAYER_ID, @@ -227,10 +242,13 @@ export default function () { }) }) - events.on('packet/entitySpawn', ({ id, position, type }) => { - if (type === 0) { + events.on('packet/entitySpawn', ({ id, position, type, name }) => { + if (entities.has(id)) return + + if (type === 'PLAYER') { const entity = Pool.guard.get() entity.id = id + entity.title.text = name entity.move(position) entities.set(id, entity) } @@ -247,7 +265,21 @@ export default function () { events.on('packet/entityMove', ({ id, position }) => { const entity = entities.get(id) const { x, y, z } = position - if (entity) entity.target_position = new Vector3(x, y, z) + const state = get_state() + if (entity) { + entity.target_position = new Vector3(x, y, z) + if (state.player) { + const position = state.player.position() + const distance = position.distanceTo(new Vector3(x, y, z)) + if (distance > MAX_TITLE_VIEW_DISTANCE && entity.title.visible) + entity.title.visible = false + else if ( + distance <= MAX_TITLE_VIEW_DISTANCE && + !entity.title.visible + ) + entity.title.visible = true + } + } }) events.on('CLEAR_CHUNKS', () => { @@ -329,7 +361,7 @@ export default function () { settings, world: { seed, biome }, } = get_state() - const chunks_with_collisions = square_array(current_chunk, 1).map( + const chunks_with_collisions = square_array(current_chunk, 2).map( make_chunk_key, ) const new_chunks = spiral_array( @@ -426,51 +458,38 @@ export default function () { settings.far_view_distance, ).map(make_chunk_key) - const chunks_to_load = new_chunks.filter( - key => !low_detail_loaded_chunks.has(key), - ) + const geometries = [] - // Load low-detail chunks - await Promise.all( - chunks_to_load.map(async key => { - const { x, z } = from_chunk_key(key) - // Calculate the Manhattan distance from the current chunk to the center - const distance_from_center = - Math.abs(current_chunk.x - x) + Math.abs(current_chunk.z - z) - // Calculate segments based on distance - const segments = Math.max( - 2, - // 16 >> (distance_from_center - settings.view_distance - 1), - 4, - ) - - const { terrain, dispose } = - await request_low_detail_chunk_load({ - chunk_x: x, - chunk_z: z, - biome, - seed, - segments, - }) - - low_detail_loaded_chunks.set(key, { - terrain, - dispose, - }) + const low_detail_plane_geometry = + await request_low_detail_chunks_load({ + chunks: new_chunks, + biome, + seed, + }) + + if (low_detail_plane) { + scene.remove(low_detail_plane) + low_detail_plane.geometry.dispose() + low_detail_plane.material.dispose() + } + + low_detail_plane = new Mesh( + low_detail_plane_geometry, + new MeshPhongMaterial({ + vertexColors: true, + color: new Color(0.4, 0.4, 0.4), // Darken the base color + emissive: new Color(0, 0, 0), // No additional light from the material itself + specular: new Color(0, 0, 0), // Low specular highlights + shininess: 10, // Adjust shininess for the size of the specular highlight + side: BackSide, }), ) - // Add new low-detail terrain to the scene - low_detail_loaded_chunks.forEach(({ terrain, dispose }, key) => { - if (new_chunks.includes(key)) scene.add(terrain) - else { - scene.remove(terrain) - dispose() - low_detail_loaded_chunks.delete(key) - } - }) + scene.add(low_detail_plane) } catch (error) { console.error(error) + } finally { + events.emit('CHUNKS_LOADED') } }, ) diff --git a/src/modules/player_movement.js b/src/modules/player_movement.js index 2700bb0..1b73372 100644 --- a/src/modules/player_movement.js +++ b/src/modules/player_movement.js @@ -113,6 +113,8 @@ export default function ({ world }) { let is_dancing = false + let chunks_loaded = false + return { name: 'player_movements', tick({ inputs, player }, { camera, send_packet, events }, delta) { @@ -134,7 +136,10 @@ export default function ({ world }) { if (player.target_position) { player.move(player.target_position) + events.emit('CHANGE_CHUNK', to_chunk_position(player.target_position)) + player.target_position = null + return } @@ -142,7 +147,7 @@ export default function ({ world }) { // TODO: tp to nether if falling to hell if (position.y <= -30) { velocity.setScalar(0) - player.move(new Vector3(0, 100, 0)) + player.move(new Vector3(position.x, 100, position.z)) return } @@ -207,8 +212,8 @@ export default function ({ world }) { break case jump_states.NONE: default: - // if not jumping, apply normal gravity - velocity.y -= GRAVITY * delta + // if not jumping, apply normal gravity as long as chunks are there + if (chunks_loaded) velocity.y -= GRAVITY * delta } movement.addScaledVector(velocity, delta) @@ -292,6 +297,10 @@ export default function ({ world }) { }, new Vector3(), ) + + events.on('CHUNKS_LOADED', () => { + if (!chunks_loaded) chunks_loaded = true + }) }, } } diff --git a/src/pool.js b/src/pool.js index ec8bb1a..fba9bb4 100644 --- a/src/pool.js +++ b/src/pool.js @@ -13,6 +13,8 @@ import { } from 'three' import { clone } from 'three/examples/jsm/utils/SkeletonUtils.js' import { RigidBodyDesc, ColliderDesc } from '@dimforge/rapier3d' +import { Text } from 'troika-three-text' +import { createDerivedMaterial } from 'troika-three-utils' import step1 from './assets/sound/step1.ogg' import step2 from './assets/sound/step2.ogg' @@ -43,6 +45,42 @@ const throttle = (action, interval) => { } } +function create_billboard_material(baseMaterial, keep_aspect) { + return createDerivedMaterial(baseMaterial, { + // Declaring custom uniforms + uniforms: { + uSize: { value: keep_aspect ? 0.1 : 0.15 }, + uScale: { value: 1 }, + }, + // Adding GLSL code to the vertex shader's top-level definitions + vertexDefs: ` +uniform float uSize; +uniform float uScale; +`, + // Adding GLSL code at the end of the vertex shader's main function + vertexMainOutro: keep_aspect + ? ` +vec4 mvPosition = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0); +float distance = length(-mvPosition.xyz); +float computedScale = uSize * uScale * distance; +mvPosition.xyz += position * computedScale; +gl_Position = projectionMatrix * mvPosition; +` + : ` +vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ); +vec3 scale = vec3( + length(modelViewMatrix[0].xyz), + length(modelViewMatrix[1].xyz), + length(modelViewMatrix[2].xyz) + ); +// size attenuation: scale *= -mvPosition.z * 0.2; +mvPosition.xyz += position * scale; +gl_Position = projectionMatrix * mvPosition; +`, + // No need to modify fragment shader for billboarding effect + }) +} + const MODEL_FORWARD = new Vector3(0, 0, 1) const step_audios = [ @@ -143,7 +181,7 @@ export default function create_pools({ scene, world }) { return { data, /** @type {(options: { add_rigid_body: boolean }) => Type.Entity} */ - get({ add_rigid_body } = {}) { + get({ add_rigid_body, fixed_title_aspect } = {}) { const body = data.find(object => !object.visible) if (!body) throw new Error('No more models available') @@ -165,14 +203,33 @@ export default function create_pools({ scene, world }) { current_animation.reset().play() + const title = new Text() + + title.fontSize = 0.2 + title.color = 'white' + title.anchorX = 'center' + title.outlineWidth = 0.02 + title.material = create_billboard_material( + new MeshBasicMaterial(), + fixed_title_aspect, + ) + + scene.add(title) + const base_entity = { + title, three_body: body, height, radius, move(position) { body.position.copy(position) + title.position.copy(position).add(new Vector3(0, height * 1.4, 0)) }, remove() { + scene.remove(title) + + title.geometry.dispose() + body.visible = false }, rotate(movement) { @@ -220,12 +277,12 @@ export default function create_pools({ scene, world }) { collider, move(position) { rigid_body.setNextKinematicTranslation(position) - body.position.copy(position) + base_entity.move(position) }, remove() { - body.visible = false world.removeCollider(collider, false) world.removeRigidBody(rigid_body) + base_entity.remove() }, position() { const { x, y, z } = rigid_body.translation() diff --git a/src/types.d.ts b/src/types.d.ts index f6a2ea1..bfa480a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -24,6 +24,7 @@ declare namespace Type { type Entity = { id: string + title: import('troika-three-text').Text three_body: import('three').Object3D rapier_body: { rigid_body: import('@dimforge/rapier3d').RigidBody @@ -86,6 +87,7 @@ declare namespace Type { SET_TIME: number // set the time of the day TIME_CHANGE: number // the time of the day has changed CLEAR_CHUNKS: void // clear all chunks + CHUNKS_LOADED: void // notify that the loading of new chunks is finished } & Packets > diff --git a/src/utils/chunks.js b/src/utils/chunks.js index 1e61c59..e47a4a9 100644 --- a/src/utils/chunks.js +++ b/src/utils/chunks.js @@ -16,6 +16,7 @@ import { MeshPhongMaterial, MeshStandardMaterial, MeshToonMaterial, + Uint32BufferAttribute, Vector3, } from 'three' import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' @@ -26,6 +27,7 @@ import { WORLD_HEIGHT } from 'aresrpg-protocol/src/chunk.js' import Biomes from '../world_gen/biomes.js' import greedy_mesh from '../world_gen/greedy_mesh.js' +import { from_chunk_key } from '../modules/game_world.js' const pool = workerpool.pool('src/world_gen/chunk_worker.js', { workerOpts: { @@ -33,49 +35,73 @@ const pool = workerpool.pool('src/world_gen/chunk_worker.js', { }, }) -export async function request_low_detail_chunk_load({ - chunk_x, - chunk_z, - seed, - biome, - segments = 16, -}) { - const { vertices, colors, indices } = await pool.exec( - 'create_low_detail_chunk_column', - [chunk_x, chunk_z, biome, seed, segments], - ) +const SEGMENTS = 16 - const geometry = new BufferGeometry() +export async function request_low_detail_chunks_load({ chunks, seed, biome }) { + const geometries_data = await Promise.all( + chunks.map(async key => { + const { x, z } = from_chunk_key(key) - // Convert vertices, colors, and indices to typed arrays - const verticesTypedArray = new Float32Array(vertices) - const colorsTypedArray = new Float32Array(colors) - const indicesTypedArray = new Uint32Array(indices) // assuming indices are of type number + const { vertices, colors, indices } = await pool.exec( + 'create_low_detail_chunk_column', + [x, z, biome, seed, SEGMENTS], + ) - // Add vertices, colors, and indices to geometry - geometry.setIndex(new BufferAttribute(indicesTypedArray, 1)) - geometry.setAttribute('position', new BufferAttribute(verticesTypedArray, 3)) - geometry.setAttribute('color', new BufferAttribute(colorsTypedArray, 3)) - geometry.computeVertexNormals() + // Convert vertices, colors, and indices to typed arrays + const vertices_array = new Float32Array(vertices) + const colors_array = new Float32Array(colors) + const indices_array = new Uint32Array(indices) // assuming indices are of type number - const material = new MeshPhongMaterial({ - vertexColors: true, - color: new Color(0.4, 0.4, 0.4), // Darken the base color - emissive: new Color(0, 0, 0), // No additional light from the material itself - specular: new Color(0, 0, 0), // Low specular highlights - shininess: 10, // Adjust shininess for the size of the specular highlight - side: BackSide, - }) + return { + vertices: vertices_array, + colors: colors_array, + indices: indices_array, + } + }), + ) + + const total_vertices = geometries_data.reduce( + (acc, data) => acc + data.vertices.length, + 0, + ) + const total_colors = geometries_data.reduce( + (acc, data) => acc + data.colors.length, + 0, + ) + const total_indices = geometries_data.reduce( + (acc, data) => acc + data.indices.length, + 0, + ) - const mesh = new Mesh(geometry, material) + // Create SharedArrayBuffers + const vertices_buffer = new SharedArrayBuffer(total_vertices * 4) // Float32 needs 4 bytes + const colors_buffer = new SharedArrayBuffer(total_colors * 4) // Float32 needs 4 bytes + const indices_buffer = new SharedArrayBuffer(total_indices * 4) // Assuming Uint32 - return { - terrain: mesh, - dispose() { - geometry.dispose() - material.dispose() + const shallow_geometry = await pool.exec('merge_geometries', [ + geometries_data, + { + vertices_buffer, + colors_buffer, + indices_buffer, }, - } + ]) + + const geometry = new BufferGeometry() + + geometry.setAttribute( + 'position', + new Float32BufferAttribute(new Float32Array(vertices_buffer), 3), + ) + geometry.setAttribute( + 'color', + new Float32BufferAttribute(new Float32Array(colors_buffer), 3), + ) + geometry.setIndex( + new Uint32BufferAttribute(new Uint32Array(indices_buffer), 1), + ) + + return geometry } /** @@ -171,6 +197,7 @@ export async function request_chunk_load({ new Float32BufferAttribute(collision_vertices, 3), ) collision_geometry.setIndex(collision_indices) + const collider_mesh = new Mesh( collision_geometry, new MeshStandardMaterial({ diff --git a/src/world_gen/biomes.js b/src/world_gen/biomes.js index 12256f1..3dc5350 100644 --- a/src/world_gen/biomes.js +++ b/src/world_gen/biomes.js @@ -1,10 +1,32 @@ export default { DEFAULT: { - scale: 550, - height: 35, - octaves: 6, - persistence: 0.24, - lacunarity: 3.24, - exponentiation: 3.24, + scale: 645, + height: 60, + octaves: 7, + persistence: 0.21, + lacunarity: 3.79, + exponentiation: 3.35, + painting: { + snow_cover_scale: 3, + snow_cover_threshold: 40, + full_snow_altitude: 90, + min_snow_altitude: 75, + full_snow_cover_threshold: 50, + snow_stone_mix_threshold: 45, + + stone_noise_scale: 0.2, + stone_threshold: 5, + stone_color_noise_threshold: 25, + + moisture_scale: 0.6, + moisture_threshold: 20, + + sand_noise_scale: 0.45, + sand_noise_threshold: 10, + + grass_noise_scale: 0.1, + grass_noise_threshold: 25, + dry_grass_noise_threshold: 35, + }, }, } diff --git a/src/world_gen/chunk_worker.js b/src/world_gen/chunk_worker.js index 898ac92..09ff56a 100644 --- a/src/world_gen/chunk_worker.js +++ b/src/world_gen/chunk_worker.js @@ -1,4 +1,6 @@ import workerpool from 'workerpool' +import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' +import { BufferAttribute, BufferGeometry } from 'three' import { create_chunk_column, @@ -8,4 +10,34 @@ import { workerpool.worker({ create_chunk_column, create_low_detail_chunk_column, + merge_geometries(geometries_data, shared_buffers) { + const { vertices_buffer, colors_buffer, indices_buffer } = shared_buffers + + let vertex_offset = 0 + let color_offset = 0 + let index_offset = 0 + + // Create views on the shared buffers + const vertices_view = new Float32Array(vertices_buffer) + const colors_view = new Float32Array(colors_buffer) + const indices_view = new Uint32Array(indices_buffer) + + geometries_data.forEach(({ vertices, colors, indices }) => { + // Copy vertices + vertices_view.set(new Float32Array(vertices), vertex_offset) + + // Copy colors + colors_view.set(new Float32Array(colors), color_offset) + color_offset += colors.length + + // Copy indices. Need to offset indices to match the merged vertex positions + const offset_indices = new Uint32Array(indices).map( + index => index + vertex_offset / 3, + ) + indices_view.set(offset_indices, index_offset) + index_offset += indices.length + + vertex_offset += vertices.length + }) + }, }) diff --git a/src/world_gen/create_chunk.js b/src/world_gen/create_chunk.js index 27004f0..a422e21 100644 --- a/src/world_gen/create_chunk.js +++ b/src/world_gen/create_chunk.js @@ -32,13 +32,80 @@ function is_voxel_exposed({ x, y, z, heightfield }) { ) } -function get_voxel_data({ x, y, z }) { - const color = - y < 15 ? 0x29b6f6 : y < 20 ? 0xc2b280 : y < 40 ? 0x43a047 : 0xffffff +function get_voxel_data({ x, y, z, heightfield, biome: { painting } }) { + // Calculate noise values for snow, stone, sand, and grass + const snow_noise = heightfield( + x * painting.snow_cover_scale, + z * painting.snow_cover_scale, + ) + const stone_noise = heightfield( + x * painting.stone_noise_scale, + z * painting.stone_noise_scale, + ) + const sand_noise = heightfield( + x * painting.sand_noise_scale, + z * painting.sand_noise_scale, + ) + const grass_noise = heightfield( + x * painting.grass_noise_scale, + z * painting.grass_noise_scale, + ) - return { - color, + // Determine the base terrain height using heightfield + const terrain_height = heightfield(x, z) + + // Calculate moisture for grass color variation + const moisture = heightfield( + x * painting.moisture_scale, + z * painting.moisture_scale, + ) + + // Determine if the voxel is stone based on noise and height + const is_stone = terrain_height > y && stone_noise > painting.stone_threshold + + // Check for snow coverage based on altitude and noise + const is_snow_covered = + y >= painting.min_snow_altitude && + snow_noise > painting.snow_cover_threshold + const is_fully_snow_covered = y >= painting.full_snow_altitude + + // Grass and stone color variations + const grass_color_variation = + grass_noise > painting.grass_noise_threshold ? 0x679436 : 0x43a047 // Additional grass color + const dry_grass_color_variation = + grass_noise > painting.dry_grass_noise_threshold ? 0x8c8a55 : 0x85c17e // Additional dry grass color + const stone_color_variation = + stone_noise > painting.stone_color_noise_threshold ? 0x757575 : 0x8e8e8e // Additional stone color + const sand_color_variation = + sand_noise > painting.sand_color_noise_threshold ? 0xe9c2a6 : 0xc2b280 // Additional sand color + + // Initialize the color variable + let color = null + + // Set the voxel color based on the terrain type + if (is_fully_snow_covered) { + color = 0xffffff // Full snow color + } else if (is_stone) { + // Stone color with variation + color = stone_color_variation + } else if (is_snow_covered) { + // Blend stone and snow based on noise, with additional stone color variation + color = + stone_noise > painting.snow_stone_mix_threshold + ? stone_color_variation + : 0xffffff + } else if (y < 25) { + // Sand color with variation + color = sand_color_variation + } else { + // Grass color varies based on moisture, with additional grass color variation + color = + moisture > painting.moisture_threshold + ? grass_color_variation + : dry_grass_color_variation } + + return { color } } function* iterate_chunk() { @@ -51,7 +118,7 @@ function* iterate_chunk() { } } -function create_chunk({ chunk_x, chunk_z, row, heightfield, column }) { +function create_chunk({ chunk_x, chunk_z, row, heightfield, column, biome }) { for (const [offset_x, offset_y, offset_z] of iterate_chunk()) { const x = chunk_x * CHUNK_SIZE + offset_x const z = chunk_z * CHUNK_SIZE + offset_z @@ -71,7 +138,7 @@ function create_chunk({ chunk_x, chunk_z, row, heightfield, column }) { heightfield, }) ) { - const data = get_voxel_data({ x, y, z }) + const data = get_voxel_data({ x, y, z, heightfield, biome }) column.set(offset_x, y, offset_z, data) } } @@ -95,6 +162,7 @@ export function create_chunk_column(chunk_x, chunk_z, biome, seed) { row, heightfield, column, + biome, }) } @@ -134,7 +202,7 @@ export function create_low_detail_chunk_column( vertices.push(x, y, z) // Get color data for the vertex - const { color } = get_voxel_data({ x, y, z }) + const { color } = get_voxel_data({ x, y, z, heightfield, biome }) colors.push(...unpack_color(color)) } } diff --git a/src/world_gen/noise.js b/src/world_gen/noise.js index 8cbab43..b500fc4 100644 --- a/src/world_gen/noise.js +++ b/src/world_gen/noise.js @@ -11,9 +11,9 @@ export function create_fractionnal_brownian(biome, seed) { const noise_pass_2 = createNoise2D(alea(`${seed}_2`)) const noise_pass_3 = createNoise2D(alea(`${seed}_3`)) return (x, y) => { - const key = `${x}:${y}:${JSON.stringify(biome)}:${seed}` + // const key = `${x}:${y}:${JSON.stringify(biome)}:${seed}` - if (memoized.has(key)) return memoized.get(key) + // if (memoized.has(key)) return memoized.get(key) const xs = x / scale const ys = y / scale @@ -40,7 +40,7 @@ export function create_fractionnal_brownian(biome, seed) { total *= height total = Math.floor(total) - memoized.set(key, total) + // memoized.set(key, total) return total } diff --git a/vite.config.js b/vite.config.js index 20376f6..18bf278 100644 --- a/vite.config.js +++ b/vite.config.js @@ -12,6 +12,13 @@ export default defineConfig({ events: 'events-polyfill', }, }, + server: { + port: 5174, + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }, + }, build: { target: 'esnext', minify: true,