From a31b1701bbb2d0410dabc97072b993014f91b5c8 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 17:16:46 +0300 Subject: [PATCH 001/318] [pick pr] fix white strip on buttons --- lib/menus/components/button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/menus/components/button.js b/lib/menus/components/button.js index 12b0b8ec9..e03b54a10 100644 --- a/lib/menus/components/button.js +++ b/lib/menus/components/button.js @@ -63,7 +63,7 @@ class Button extends LitElement { position: absolute; top: 0; left: 0; - width: 50%; + width: calc(50% + 1px); height: 20px; background: url('textures/1.17.1/gui/widgets.png'); background-size: 256px; From a67d05ae994e879454987f0ccf73d2ad75abcf13 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 18:36:27 +0300 Subject: [PATCH 002/318] speed webpack builds --- build.js | 20 ++++++++++++++++++++ package.json | 4 ++-- webpack.common.js | 32 ++++++++++++-------------------- webpack.dev.js | 15 +++++++++++---- webpack.prod.js | 10 +++++++++- 5 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 build.js diff --git a/build.js b/build.js new file mode 100644 index 000000000..0b9496f0b --- /dev/null +++ b/build.js @@ -0,0 +1,20 @@ +const fsExtra = require('fs-extra') + +exports.copyFiles = () => { + fsExtra.copySync('node_modules/prismarine-viewer/public/blocksStates/', 'public/blocksStates/') + fsExtra.copySync('node_modules/prismarine-viewer/public/textures/', 'public/textures/') + fsExtra.copySync('node_modules/prismarine-viewer/public/worker.js', 'public/worker.js') + fsExtra.copySync('node_modules/prismarine-viewer/public/supportedVersions.json', 'public/supportedVersions.json') + fsExtra.copySync('assets/', 'public/') + fsExtra.copySync('extra-textures/', 'public/extra-textures/') + fsExtra.copySync('config.json', 'public/config.json') +} + +exports.copyFilesDev = () => { + if (fsExtra.existsSync('public/config.json')) return + exports.copyFiles() +} + +const fn = exports[process.argv[2]] + +if (fn) fn() diff --git a/package.json b/package.json index 9700a30ad..338413c2c 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "A minecraft client running in a browser", "main": "index.js", "scripts": { - "build": "webpack --config webpack.prod.js", - "build-dev": "webpack --config webpack.dev.js", + "build": "node build.js copyFiles && webpack --config webpack.prod.js", + "build-dev": "node build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", "start": "node --max-old-space-size=8192 server.js 8080 dev", "prod-start": "node server.js", "build-dev-start": "npm run build-dev && npm run prod-start", diff --git a/webpack.common.js b/webpack.common.js index 98f813c7b..5494e791d 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -2,7 +2,6 @@ const webpack = require('webpack') const path = require('path') const CopyPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') -const WorkboxPlugin = require('workbox-webpack-plugin') // https://webpack.js.org/guides/production/ const config = { @@ -61,25 +60,18 @@ const config = { /prismarine-viewer[/|\\]viewer[/|\\]lib[/|\\]utils/, './utils.web.js' ), - new WorkboxPlugin.GenerateSW({ - // these options encourage the ServiceWorkers to get in there fast - // and not allow any straggling "old" SWs to hang around - clientsClaim: true, - skipWaiting: true, - include: ['index.html', 'manifest.json'] // not caching a lot as anyway this works only online - }), - new CopyPlugin({ - patterns: [ - { from: path.join(__dirname, '/styles.css'), to: './styles.css' }, - { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/blocksStates/'), to: './blocksStates/' }, - { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/textures/'), to: './textures/' }, - { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/worker.js'), to: './' }, - { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/supportedVersions.json'), to: './' }, - { from: path.join(__dirname, 'assets/'), to: './' }, - { from: path.join(__dirname, 'extra-textures/'), to: './extra-textures/' }, - { from: path.join(__dirname, 'config.json'), to: './config.json' } - ] - }) + // new CopyPlugin({ + // patterns: [ + // { from: path.join(__dirname, '/styles.css'), to: './styles.css' }, + // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/blocksStates/'), to: './blocksStates/' }, + // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/textures/'), to: './textures/' }, + // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/worker.js'), to: './' }, + // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/supportedVersions.json'), to: './' }, + // { from: path.join(__dirname, 'assets/'), to: './' }, + // { from: path.join(__dirname, 'extra-textures/'), to: './extra-textures/' }, + // { from: path.join(__dirname, 'config.json'), to: './config.json' } + // ] + // }) ] } diff --git a/webpack.dev.js b/webpack.dev.js index c8aa4be07..dbfbae6c5 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -2,15 +2,22 @@ const { merge } = require('webpack-merge') const common = require('./webpack.common.js') const path = require('path') -module.exports = merge(common, { +/** @type {import('webpack-dev-server').Configuration['rel']} */ +module.exports = merge(common, + /** @type {import('webpack').Configuration} */ + { mode: 'development', devtool: 'inline-source-map', cache: true, devServer: { - contentBase: path.resolve(__dirname, './public'), + // contentBase: path.resolve(__dirname, './public'), compress: true, - inline: true, + // inline: true, // open: true, - hot: true + hot: true, + // liveReload: true, + devMiddleware: { + writeToDisk: true, + }, } }) diff --git a/webpack.prod.js b/webpack.prod.js index 5c00efb2d..4f5f6c820 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -3,6 +3,7 @@ const common = require('./webpack.common.js') const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') const { CleanWebpackPlugin } = require('clean-webpack-plugin') +const WorkboxPlugin = require('workbox-webpack-plugin') const webpack = require('webpack') module.exports = merge(common, { @@ -10,6 +11,13 @@ module.exports = merge(common, { plugins: [ new CleanWebpackPlugin(), new webpack.optimize.ModuleConcatenationPlugin(), - new LodashModuleReplacementPlugin() + new LodashModuleReplacementPlugin(), + new WorkboxPlugin.GenerateSW({ + // these options encourage the ServiceWorkers to get in there fast + // and not allow any straggling "old" SWs to hang around + clientsClaim: true, + skipWaiting: true, + include: ['index.html', 'manifest.json'] // not caching a lot as anyway this works only online + }), ] }) From ddf53151083d4a51a2f414d60a5f46cf22460832 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 19:33:24 +0300 Subject: [PATCH 003/318] [pick pr] [wip] mouse raw input. Doesn't work for now for some reason :( --- index.js | 18 +++++++++++++++++- lib/menus/options_screen.js | 12 +++++++++++- styles.css | 3 ++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 0339782bc..860920d2a 100644 --- a/index.js +++ b/index.js @@ -310,9 +310,25 @@ async function connect (options) { lastTouch = undefined }, { passive: false }) - renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock || + const requestPointerLock = renderer.domElement.requestPointerLock || renderer.domElement.mozRequestPointerLock || renderer.domElement.webkitRequestPointerLock + renderer.domElement.requestPointerLock = async () => { + // request full keyboard access + // await renderer.domElement.requestFullscreen() + // // navigator.keyboard.lock(['KeyW']) + // const promise = requestPointerLock.apply(renderer.domElement, { + // unadjustedMovement: window.localStorage.getItem('mouseRawInput') === 'true' + // }); + // promise?.catch((error) => { + // if (error.name === "NotSupportedError") { + // // Some platforms may not support unadjusted movement, request again a regular pointer lock. + // requestPointerLock.apply(renderer.domElement) + // } else { + // console.error(error) + // } + // }) + // } document.addEventListener('mousedown', (e) => { if (!chat.inChat && !gameMenu.inMenu) { renderer.domElement.requestPointerLock() diff --git a/lib/menus/options_screen.js b/lib/menus/options_screen.js index a6664d247..117cd1462 100644 --- a/lib/menus/options_screen.js +++ b/lib/menus/options_screen.js @@ -63,6 +63,7 @@ class OptionsScreen extends LitElement { this.fov = getValue('fov', 75, (v) => Number(v)) this.guiScale = getValue('guiScale', 3, (v) => Number(v)) this.forceMobileControls = getValue('forceMobileControls', false, (v) => v === 'true') + this.mouseRawInput = getValue('mouseRawInput', false, (v) => v === 'true') document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) @@ -134,7 +135,7 @@ class OptionsScreen extends LitElement { }}> `} - +
{ this.forceMobileControls = !this.forceMobileControls @@ -149,6 +150,15 @@ class OptionsScreen extends LitElement { }>
+
+ { + this.mouseRawInput = !this.mouseRawInput + window.localStorage.setItem('mouseRawInput', `${this.mouseRawInput}`) + this.requestUpdate() + } + }> +
+ displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}> ` diff --git a/styles.css b/styles.css index 2bf62eba7..bd8bf6800 100644 --- a/styles.css +++ b/styles.css @@ -15,6 +15,7 @@ html { height: 100vh; overflow: hidden; + touch-action: none; } .dirt-bg { @@ -117,4 +118,4 @@ canvas { width: calc(100% / 1); height: calc(100% / 1); } -} \ No newline at end of file +} From c56c98f50189c9f225ca8e446451bef6fd518af1 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 19:34:09 +0300 Subject: [PATCH 004/318] [pick pr?] fix some bugs? --- index.js | 4 ++-- lib/menus/components/bossbars_overlay.js | 2 +- lib/menus/components/hotbar.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 860920d2a..b3171ceae 100644 --- a/index.js +++ b/index.js @@ -361,10 +361,10 @@ async function connect (options) { bot.setControlState('jump', true) break case 'KeyD': - bot.setControlState('left', true) + bot.setControlState('right', true) break case 'KeyA': - bot.setControlState('right', true) + bot.setControlState('left', true) break case 'KeyS': bot.setControlState('back', true) diff --git a/lib/menus/components/bossbars_overlay.js b/lib/menus/components/bossbars_overlay.js index ab714bb2b..213fe1409 100644 --- a/lib/menus/components/bossbars_overlay.js +++ b/lib/menus/components/bossbars_overlay.js @@ -69,7 +69,7 @@ class BossBar extends LitElement { setTitle (bar) { if (bar._title.text) this.title = bar._title.text - else this.title = translations[this._title.translate] || 'Unkown Entity' + else this.title = translations[this._title.translate] || 'Unknown Entity' } setColor (bar) { diff --git a/lib/menus/components/hotbar.js b/lib/menus/components/hotbar.js index c148f1642..cd07debf6 100644 --- a/lib/menus/components/hotbar.js +++ b/lib/menus/components/hotbar.js @@ -116,7 +116,7 @@ class Hotbar extends LitElement { }) document.addEventListener('keydown', (e) => { - const numPressed = e.code.substr(5) + const numPressed = +(e.code.match(/Digit(\d)/)?.[1] ?? -1) if (numPressed < 1 || numPressed > 9) return this.reloadHotbarSelected(numPressed - 1) }) From 5bef43031fb72ba2642f92fd0276b99f42e834aa Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 19:36:06 +0300 Subject: [PATCH 005/318] [pick pr] display user gpu in debug overlay. Useful for laptops with iGPU and a dedicated one --- index.js | 8 ++++++++ lib/menus/components/debug_overlay.js | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b3171ceae..f2474d429 100644 --- a/index.js +++ b/index.js @@ -258,6 +258,14 @@ async function connect (options) { renderer.render(viewer.scene, viewer.camera) } + try { + const gl = renderer.getContext(); + debugMenu.rendererDevice = gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL) + } catch (err) { + console.error(err) + debugMenu.rendererDevice = '???' + } + // Link WorldView and Viewer viewer.listen(worldView) worldView.listenToBot(bot) diff --git a/lib/menus/components/debug_overlay.js b/lib/menus/components/debug_overlay.js index 463367c4d..aa3755434 100644 --- a/lib/menus/components/debug_overlay.js +++ b/lib/menus/components/debug_overlay.js @@ -19,6 +19,8 @@ class DebugOverlay extends LitElement { .debug-right-side { top: 1px; right: 1px; + /* limit renderer long text width */ + width: 50%; } p { @@ -26,7 +28,7 @@ class DebugOverlay extends LitElement { color: white; font-size: 10px; width: fit-content; - height: 9px; + line-height: 9px; margin: 0; padding: 0; padding-bottom: 1px; @@ -48,6 +50,7 @@ class DebugOverlay extends LitElement { return { showOverlay: { type: Boolean }, cursorBlock: { type: Object }, + rendererDevice: { type: String }, bot: { type: Object }, customEntries: { type: Object } } @@ -134,7 +137,7 @@ class DebugOverlay extends LitElement {
-

Renderer: three.js r${global.THREE.REVISION}

+

Renderer: ${this.rendererDevice} powered by three.js r${global.THREE.REVISION}

${targetDiggable ? html`

${target.name}

${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}` : ''} ${targetDiggable ? html`

Looking at: ${target.position.x} ${target.position.y} ${target.position.z}

` : ''} From 6f02c10483cdeb9ac494a951090741a264ed05bd Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 19:41:20 +0300 Subject: [PATCH 006/318] actually commit mouse raw input impl --- index.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index f2474d429..28fabf49a 100644 --- a/index.js +++ b/index.js @@ -322,21 +322,21 @@ async function connect (options) { renderer.domElement.mozRequestPointerLock || renderer.domElement.webkitRequestPointerLock renderer.domElement.requestPointerLock = async () => { - // request full keyboard access // await renderer.domElement.requestFullscreen() - // // navigator.keyboard.lock(['KeyW']) - // const promise = requestPointerLock.apply(renderer.domElement, { - // unadjustedMovement: window.localStorage.getItem('mouseRawInput') === 'true' - // }); - // promise?.catch((error) => { - // if (error.name === "NotSupportedError") { - // // Some platforms may not support unadjusted movement, request again a regular pointer lock. - // requestPointerLock.apply(renderer.domElement) - // } else { - // console.error(error) - // } - // }) - // } + // request full keyboard access + // navigator.keyboard.lock(['KeyW']) + const promise = requestPointerLock.apply(renderer.domElement, { + unadjustedMovement: window.localStorage.getItem('mouseRawInput') === 'true' + }); + promise?.catch((error) => { + if (error.name === "NotSupportedError") { + // Some platforms may not support unadjusted movement, request again a regular pointer lock. + requestPointerLock.apply(renderer.domElement) + } else { + console.error(error) + } + }) + } document.addEventListener('mousedown', (e) => { if (!chat.inChat && !gameMenu.inMenu) { renderer.domElement.requestPointerLock() From 45ea9aed1908bbfa88f931098692793b53d5a8e0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 20:38:03 +0300 Subject: [PATCH 007/318] minor fixes... --- build.js | 1 + index.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.js b/build.js index 0b9496f0b..399476435 100644 --- a/build.js +++ b/build.js @@ -1,6 +1,7 @@ const fsExtra = require('fs-extra') exports.copyFiles = () => { + fsExtra.copySync('styles.css', 'public/styles.css') fsExtra.copySync('node_modules/prismarine-viewer/public/blocksStates/', 'public/blocksStates/') fsExtra.copySync('node_modules/prismarine-viewer/public/textures/', 'public/textures/') fsExtra.copySync('node_modules/prismarine-viewer/public/worker.js', 'public/worker.js') diff --git a/index.js b/index.js index 28fabf49a..f65257b59 100644 --- a/index.js +++ b/index.js @@ -399,10 +399,10 @@ async function connect (options) { bot.setControlState('jump', false) break case 'KeyD': - bot.setControlState('left', false) + bot.setControlState('right', false) break case 'KeyA': - bot.setControlState('right', false) + bot.setControlState('left', false) break case 'KeyS': bot.setControlState('back', false) From f24c04a94ba0a446fe3bcc2bad577e2227650395 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 20:48:12 +0300 Subject: [PATCH 008/318] update deps & use PNPM! --- .npmrc | 1 + package.json | 43 +++++++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.npmrc b/.npmrc index 43c97e719..8124febb2 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +public-hoist-pattern=* diff --git a/package.json b/package.json index 338413c2c..20f33d8dd 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,14 @@ "homepage": "https://github.com/PrismarineJS/prismarine-web-client#readme", "dependencies": { "compression": "^1.7.4", - "express": "^4.17.1", + "express": "^4.18.2", + "fs-extra": "^11.1.1", "ismobilejs": "^1.1.1", - "lit": "^2.0.2", - "net-browserify": "PrismarineJS/net-browserify", + "lit": "^2.8.0", + "net-browserify": "github:PrismarineJS/net-browserify", "querystring": "^0.2.1", - "url": "^0.11.0" + "stats.js": "^0.17.0", + "url": "^0.11.1" }, "devDependencies": { "assert": "^2.0.0", @@ -50,29 +52,30 @@ "constants-browserify": "^1.0.0", "copy-webpack-plugin": "^11.0.0", "crypto-browserify": "^3.12.0", - "events": "^3.2.0", - "html-webpack-plugin": "^5.3.1", + "events": "^3.3.0", + "html-webpack-plugin": "^5.5.3", "http-browserify": "^1.7.0", - "http-server": "^14.1.0", + "http-server": "^14.1.1", "https-browserify": "^1.0.0", "lodash-webpack-plugin": "^0.11.6", - "memfs": "^3.2.0", - "mineflayer": "^4.1.0", - "mineflayer-pathfinder": "^2.0.0", - "mocha": "^10.0.0", + "memfs": "^3.5.3", + "mineflayer": "^4.10.1", + "mineflayer-pathfinder": "^2.4.4", + "mocha": "^10.2.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", - "prismarine-viewer": "^1.22.0", - "process": "PrismarineJS/node-process", - "standard": "^17.0.0", + "prismarine-viewer": "github:PrismarineJS/prismarine-viewer", + "process": "github:PrismarineJS/node-process", + "standard": "^17.1.0", "stream-browserify": "^3.0.0", "three": "0.127.0", "timers-browserify": "^2.0.12", - "webpack": "^5.11.0", - "webpack-cli": "^5.0.0", - "webpack-dev-middleware": "^6.0.0", - "webpack-dev-server": "^4.0.0", - "webpack-merge": "^5.7.3", - "workbox-webpack-plugin": "^6.1.2" + "vite": "^4.4.9", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-middleware": "^6.1.1", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0", + "workbox-webpack-plugin": "^6.6.0" } } From dd705b466286ac580a1b26a2053a549a136350ae Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 20:58:53 +0300 Subject: [PATCH 009/318] enable vr in chrome [openXR] (to be tested actually) --- lib/vr.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vr.js b/lib/vr.js index 1f2a1a3fb..72d23cb13 100644 --- a/lib/vr.js +++ b/lib/vr.js @@ -5,9 +5,10 @@ const { GLTFLoader } = require('three/examples/jsm/loaders/GLTFLoader.js') const { XRControllerModelFactory } = require('three/examples/jsm/webxr/XRControllerModelFactory.js') const TWEEN = require('@tweenjs/tween.js') -function initVR (bot, renderer, viewer) { +async function initVR (bot, renderer, viewer) { if (!('xr' in navigator)) return - if (!navigator.userAgent.includes('OculusBrowser')) return + const isSupported = await navigator.xr.isSessionSupported('immersive-vr') + if (!isSupported) return // VR document.body.appendChild(VRButton.createButton(renderer)) From cd3c1f397372dc16e702042b12069c40d40d0acc Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 20:59:02 +0300 Subject: [PATCH 010/318] fix build agan... --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20f33d8dd..9c583ceeb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A minecraft client running in a browser", "main": "index.js", "scripts": { - "build": "node build.js copyFiles && webpack --config webpack.prod.js", + "build": "webpack --config webpack.prod.js --progress && node build.js copyFiles", "build-dev": "node build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", "start": "node --max-old-space-size=8192 server.js 8080 dev", "prod-start": "node server.js", From a9774410469b1a6d61059956b75e385815ecdc31 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 21:00:50 +0300 Subject: [PATCH 011/318] Add debug config! --- .vscode/launch.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..cd96259cd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "type": "msedge", + "name": "https://localhost:8080/", + "request": "launch", + "url": "https://localhost:8080/", + "outFiles": [ + "${workspaceFolder}/public/**/*.js", + "!${workspaceFolder}/public/**/*vendors*", + "!**/node_modules/**" + ], + "skipFiles": [ + "/**/*vendors-*" + ], + }, + ] +} From c6936407b2a889682a97f708e19f4b47471efc46 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 21:01:06 +0300 Subject: [PATCH 012/318] use chunks in dev --- webpack.common.js | 5 +++-- webpack.dev.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/webpack.common.js b/webpack.common.js index 5494e791d..7c0392c32 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -8,7 +8,7 @@ const config = { entry: path.resolve(__dirname, './index.js'), output: { path: path.resolve(__dirname, './public'), - filename: './index.js', + filename: './[name]-[chunkhash].js', publicPath: './' }, resolve: { @@ -47,7 +47,8 @@ const config = { new HtmlWebpackPlugin({ template: 'index.html', hash: true, - minify: false + minify: false, + chunks: ['main', 'vendors'], }), // fix "process is not defined" error: new webpack.ProvidePlugin({ diff --git a/webpack.dev.js b/webpack.dev.js index dbfbae6c5..c55939797 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -19,5 +19,19 @@ module.exports = merge(common, devMiddleware: { writeToDisk: true, }, + }, + optimization: { + splitChunks: { + maxAsyncRequests: 10, + maxInitialRequests: 10, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name: "vendors", + priority: 10, + chunks: 'all' + } + } + } } }) From 341ce21329f5cdd2a41b4c49e57a907a263d7f6a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 21:36:39 +0300 Subject: [PATCH 013/318] use temporarily github pages as vercel cant fetch some git repos? --- .github/workflows/publish.yml | 41 +++++++++++------------------------ 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 061457c86..fb0353795 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,33 +1,18 @@ -name: npm-publish +name: Deploy to GitHub pages on: push: - branches: - - master # Change this to your default branch + branches: [main] jobs: - npm-publish: - name: npm-publish + build-and-deploy: runs-on: ubuntu-latest + permissions: write-all steps: - - name: Checkout repository - uses: actions/checkout@master - - name: Set up Node.js - uses: actions/setup-node@master - with: - node-version: 18.0.0 - - run: npm install - - id: publish - uses: JS-DevTools/npm-publish@v1 - with: - token: ${{ secrets.NPM_AUTH_TOKEN }} - - name: Create Release - if: steps.publish.outputs.type != 'none' - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.publish.outputs.version }} - release_name: Release ${{ steps.publish.outputs.version }} - body: ${{ steps.publish.outputs.version }} - draft: false - prerelease: false + - name: Checkout repository + uses: actions/checkout@master + - run: pnpm install + - run: pnpm build + - uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + force_orphan: true From b77f00bc6d97bd97e634c0345a6bdd23cdcbaf2b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 21:37:07 +0300 Subject: [PATCH 014/318] oops. install pnpm first. --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fb0353795..e3cd5aec4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@master + - name: Install pnpm + run: npm i -g pnpm - run: pnpm install - run: pnpm build - uses: peaceiris/actions-gh-pages@v3 From e1b1938210b3c0da7d07efb79d8001fdbf51369b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 10 Aug 2023 14:09:31 +0300 Subject: [PATCH 015/318] some basic type-checking change start script --- globals.d.ts | 9 +++++++++ index.js | 5 +++++ package.json | 2 +- tsconfig.json | 12 ++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 globals.d.ts create mode 100644 tsconfig.json diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 000000000..34d5ea017 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1,9 @@ +declare const THREE: any; + +declare interface Document { + getElementById(id): any +} + +declare interface Window extends Record { + +} diff --git a/index.js b/index.js index f65257b59..abab009bb 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +//@ts-check /* global THREE */ require('./lib/chat') @@ -29,6 +30,7 @@ const mineflayer = require('mineflayer') const { WorldView, Viewer } = require('prismarine-viewer/viewer') const pathfinder = require('mineflayer-pathfinder') const { Vec3 } = require('vec3') +//@ts-ignore global.THREE = require('three') const { initVR } = require('./lib/vr') @@ -171,6 +173,7 @@ async function connect (options) { if (proxy) { console.log(`using proxy ${proxy} ${proxyport}`) + //@ts-ignore net.setProxy({ hostname: proxy, port: proxyport }) } @@ -291,7 +294,9 @@ async function connect (options) { function changeCallback () { if (document.pointerLockElement === renderer.domElement || + // @ts-ignore document.mozPointerLockElement === renderer.domElement || + // @ts-ignore document.webkitPointerLockElement === renderer.domElement) { document.addEventListener('mousemove', moveCallback, false) } else { diff --git a/package.json b/package.json index 9c583ceeb..98b5a5ea3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "webpack --config webpack.prod.js --progress && node build.js copyFiles", "build-dev": "node build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", - "start": "node --max-old-space-size=8192 server.js 8080 dev", + "start": "NODE_OPTIONS=--max-old-space-size=8192 pnpm build-dev", "prod-start": "node server.js", "build-dev-start": "npm run build-dev && npm run prod-start", "build-start": "npm run build && npm run prod-start", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..ee49561e4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "noEmit": true + }, + "include": [ + "index.js", + "globals.d.ts", + "lib" + ] +} From cdbb551a786ce295f531bfd1efe00430ed3bbc49 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 10 Aug 2023 22:59:28 +0300 Subject: [PATCH 016/318] make all inputs user-friendly. add server history autocomplete (temporary solution) --- index.js | 5 +++++ lib/menus/components/edit_box.js | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index abab009bb..55c2a4348 100644 --- a/index.js +++ b/index.js @@ -214,6 +214,11 @@ async function connect (options) { }) bot.once('login', () => { + // server is ok, add it to the history + /** @type {string[]} */ + const serverHistory = JSON.parse(localStorage.getItem('serverHistory') || '[]') + serverHistory.unshift(options.server) + localStorage.setItem('serverHistory', JSON.stringify([...new Set(serverHistory)])) loadingScreen.status = 'Loading world' }) diff --git a/lib/menus/components/edit_box.js b/lib/menus/components/edit_box.js index a8ac38fb1..934f541c4 100644 --- a/lib/menus/components/edit_box.js +++ b/lib/menus/components/edit_box.js @@ -68,6 +68,17 @@ class EditBox extends LitElement { value: { type: String, attribute: 'pmui-value' + }, + autocompleteValues: { + type: Array, + }, + type: { + type: String, + attribute: 'pmui-type' + }, + inputMode: { + type: String, + attribute: 'pmui-inputmode' } } } @@ -79,14 +90,24 @@ class EditBox extends LitElement { style="width: ${this.width};" > + ${this.autocompleteValues ? html` + + ${this.autocompleteValues.map(value => html` + + `)} + + ` : ''} { this.value = e.target.value }} class="edit-box">
From fa5f8568c614bdfeb295b9dd30a4551a21aafa44 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 10 Aug 2023 23:02:07 +0300 Subject: [PATCH 017/318] load click sound on page load as bonus resources num in devtools is more realistic right after page load --- index.js | 1 + lib/menus/components/button.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 55c2a4348..7772d12d2 100644 --- a/index.js +++ b/index.js @@ -219,6 +219,7 @@ async function connect (options) { const serverHistory = JSON.parse(localStorage.getItem('serverHistory') || '[]') serverHistory.unshift(options.server) localStorage.setItem('serverHistory', JSON.stringify([...new Set(serverHistory)])) + loadingScreen.status = 'Loading world' }) diff --git a/lib/menus/components/button.js b/lib/menus/components/button.js index e03b54a10..eae2de7db 100644 --- a/lib/menus/components/button.js +++ b/lib/menus/components/button.js @@ -3,6 +3,14 @@ const { LitElement, html, css } = require('lit') const audioContext = new window.AudioContext() const sounds = {} +// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded +async function loadSound(path) { + const res = await window.fetch(path) + const data = await res.arrayBuffer() + + sounds[path] = await audioContext.decodeAudioData(data) +} + async function playSound (path) { let volume = 1 const options = document.getElementById('options-screen') @@ -11,14 +19,7 @@ async function playSound (path) { } let soundBuffer = sounds[path] - - if (!soundBuffer) { - const res = await window.fetch(path) - const data = await res.arrayBuffer() - - soundBuffer = await audioContext.decodeAudioData(data) - sounds[path] = soundBuffer - } + if (!soundBuffer) throw new Error(`Sound ${path} not loaded`) const gainNode = audioContext.createGain() const source = audioContext.createBufferSource() @@ -135,6 +136,7 @@ class Button extends LitElement { } } +loadSound('click_stereo.ogg') window.customElements.define('pmui-button', Button) const _playSound = playSound export { _playSound as playSound } From 95a54ef09ca4257fd59041dc452b6958afca52b3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 10 Aug 2023 23:59:42 +0300 Subject: [PATCH 018/318] [fixup] make all inputs mobile-friendly --- lib/menus/play_screen.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js index fb9aa07e2..412151cca 100644 --- a/lib/menus/play_screen.js +++ b/lib/menus/play_screen.js @@ -87,6 +87,8 @@ class PlayScreen extends LitElement { pmui-label="Server IP" pmui-id="serverip" pmui-value="${this.server}" + pmui-type="url" + .autocompleteValues=${JSON.parse(localStorage.getItem('serverHistory') || '[]')} @input=${e => { this.server = e.target.value }} > { this.serverport = e.target.value }} > @@ -103,6 +106,7 @@ class PlayScreen extends LitElement { pmui-label="Proxy" pmui-id="proxy" pmui-value="${this.proxy}" + pmui-type="url" @input=${e => { this.proxy = e.target.value }} > { this.proxyport = e.target.value }} > @@ -126,6 +131,7 @@ class PlayScreen extends LitElement { pmui-label="Bot Version" pmui-id="botversion" pmui-value="${this.version}" + pmui-inputmode="decimal" @input=${e => { this.version = e.target.value }} > From f1f95af918ac87638bad1623be90563595bf9086 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 11 Aug 2023 01:16:25 +0300 Subject: [PATCH 019/318] feat: allow to pass connect config in search params and auto reconnect for faster development! remove old impl, which was duplicating & bloading code a didnt allow to particially pass data to join screen such as username --- index.js | 51 ++-------------------------------------- lib/menus/play_screen.js | 34 ++++++++++++++++++++------- 2 files changed, 28 insertions(+), 57 deletions(-) diff --git a/index.js b/index.js index 7772d12d2..b2f4dbe9a 100644 --- a/index.js +++ b/index.js @@ -439,52 +439,5 @@ async function connect (options) { }) } -/** - * @param {URLSearchParams} params - */ -async function fromTheOutside (params, addr) { - const opts = {} - const dfltConfig = await (await window.fetch('config.json')).json() - - let server, port, proxy, proxyPort - - if (address.includes(':')) { - const s = address.split(':') - server = s[0] - port = Number(s[1]) || 25565 - } else { - server = address - port = Number(params.get('port')) || 25565 - } - - const proxyAddr = params.get('proxy') - if (proxyAddr) { - const s = proxyAddr.split(':') - proxy = s[0] - proxyPort = Number(s[1] ?? 'NaN') || 22 - } else { - proxy = dfltConfig.defaultProxy - proxyPort = !dfltConfig.defaultProxy && !dfltConfig.defaultProxyPort ? '' : dfltConfig.defaultProxyPort ?? 443 - } - - opts.server = `${server}:${port}` - opts.proxy = `${proxy}:${proxyPort}` - opts.username = params.get('username') ?? `pviewer${Math.floor(Math.random() * 1000)}` - opts.password = params.get('password') ?? '' - opts.botVersion = params.get('version') ?? false - - console.log(opts) - - showEl('loading-screen') - removePanorama() - connect(opts) -} - -const params = new URLSearchParams(window.location.search) -let address -if ((address = params.get('address'))) { - fromTheOutside(params, address) -} else { - showEl('title-screen') - main() -} +showEl('title-screen') +main() diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js index 412151cca..ac0326445 100644 --- a/lib/menus/play_screen.js +++ b/lib/menus/play_screen.js @@ -63,14 +63,31 @@ class PlayScreen extends LitElement { constructor () { super() - this.username = window.localStorage.getItem('username') ?? 'pviewer' + (Math.floor(Math.random() * 1000)) - this.password = window.localStorage.getItem('password') ?? '' - window.fetch('config.json').then(res => res.json()).then(config => { - this.server = window.localStorage.getItem('server') ?? config.defaultHost - this.serverport = window.localStorage.getItem('serverport') ?? config.defaultHostPort ?? 25565 - this.proxy = window.localStorage.getItem('proxy') ?? config.defaultProxy - this.proxyport = window.localStorage.getItem('proxyport') ?? (!config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443) - this.version = window.localStorage.getItem('version') ?? config.defaultVersion + window.fetch('config.json').then(res => res.json()).then(c => c, (error) => { + console.error('Failed to load config.json', error) + return {} + }).then(config => { + const params = new URLSearchParams(window.location.search) + + const getParam = (localStorageKey, qs = localStorageKey) => { + const qsValue = qs ? params.get(qs) : undefined + if (qsValue) { + document.getElementById('title-screen').style.display = 'none' + this.style.display = 'block' + } + return qsValue || window.localStorage.getItem(localStorageKey) + } + + this.server = getParam('server', 'ip') ?? config.defaultHost + this.serverport = getParam('serverport', false) ?? config.defaultHostPort ?? 25565 + this.proxy = getParam('proxy') ?? config.defaultProxy + this.proxyport = getParam('proxyport', false) ?? (!config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443) + this.version = getParam('version') || (window.localStorage.getItem('version') ?? config.defaultVersion) + this.username = getParam('username') || 'pviewer' + (Math.floor(Math.random() * 1000)) + this.password = getParam('password') || '' + if (process.env.NODE_ENV === 'development' && params.get('reconnect') && this.server && this.username) { + this.onConnectPress() + } }) } @@ -146,6 +163,7 @@ class PlayScreen extends LitElement { } onConnectPress () { + document.getElementById('title-screen').style.display = 'none' window.localStorage.setItem('username', this.username) window.localStorage.setItem('password', this.password) window.localStorage.setItem('server', this.server) From c13585509517700a7d6ec1d71a3eb093b9add396 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 11 Aug 2023 04:19:12 +0300 Subject: [PATCH 020/318] NEW escape stack management! Super scalable impl that shouldnt have bugs at all! (already debugged some) remove displayScreen in favor of new fns --- index.js | 64 +++++++++++--------- lib/chat.js | 57 +++++++----------- lib/globalState.js | 49 +++++++++++++++ lib/menus/components/common.js | 10 ---- lib/menus/hud.js | 25 ++++---- lib/menus/keybinds_screen.js | 21 +++---- lib/menus/loading_screen.js | 7 ++- lib/menus/options_screen.js | 105 +++++++++++++++++---------------- lib/menus/pause_screen.js | 51 ++++------------ lib/menus/play_screen.js | 7 ++- lib/menus/title_screen.js | 11 +++- lib/utils.js | 5 ++ 12 files changed, 218 insertions(+), 194 deletions(-) create mode 100644 lib/globalState.js create mode 100644 lib/utils.js diff --git a/index.js b/index.js index b2f4dbe9a..f319cf5b1 100644 --- a/index.js +++ b/index.js @@ -124,6 +124,13 @@ window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight) }) +const loadingScreen = document.getElementById('loading-screen') + +const hud = document.getElementById('hud') +const optionsScrn = document.getElementById('options-screen') +const keyBindScrn = document.getElementById('keybinds-screen') +const pauseMenu = document.getElementById('pause-screen') + const showEl = (str) => { document.getElementById(str).style = 'display:block' } async function main () { const menu = document.getElementById('play-screen') @@ -131,21 +138,13 @@ async function main () { menu.addEventListener('connect', e => { const options = e.detail menu.style = 'display: none;' - showEl('loading-screen') removePanorama() connect(options) }) } async function connect (options) { - const loadingScreen = document.getElementById('loading-screen') - - const hud = document.getElementById('hud') - const chat = hud.shadowRoot.querySelector('#chat') const debugMenu = hud.shadowRoot.querySelector('#debug-overlay') - const optionsScrn = document.getElementById('options-screen') - const keyBindScrn = document.getElementById('keybinds-screen') - const gameMenu = document.getElementById('pause-screen') const viewDistance = optionsScrn.renderDistance const hostprompt = options.server @@ -194,22 +193,22 @@ async function connect (options) { bot.on('error', (err) => { console.log('Encountered error!', err) + showModal(loadingScreen) loadingScreen.status = `Error encountered. Error message: ${err}. Please reload the page` - loadingScreen.style = 'display: block;' loadingScreen.hasError = true }) bot.on('kicked', (kickReason) => { console.log('User was kicked!', kickReason) + showModal(loadingScreen) loadingScreen.status = `The Minecraft server kicked you. Kick reason: ${kickReason}. Please reload the page to rejoin` - loadingScreen.style = 'display: block;' loadingScreen.hasError = true }) bot.on('end', (endReason) => { console.log('disconnected for', endReason) + showModal(loadingScreen) loadingScreen.status = `You have been disconnected from the server. End reason: ${endReason}. Please reload the page to rejoin` - loadingScreen.style = 'display: block;' loadingScreen.hasError = true }) @@ -234,10 +233,8 @@ async function connect (options) { const center = bot.entity.position - console.log(viewDistance) const worldView = new WorldView(bot.world, viewDistance, center) - gameMenu.init(renderer) optionsScrn.isInsideWorld = true optionsScrn.addEventListener('fov_changed', (e) => { viewer.camera.fov = e.detail.fov @@ -268,7 +265,7 @@ async function connect (options) { } try { - const gl = renderer.getContext(); + const gl = renderer.getContext() debugMenu.rendererDevice = gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL) } catch (err) { console.error(err) @@ -291,6 +288,7 @@ async function connect (options) { loadingScreen.status = 'Setting callbacks' function moveCallback (e) { + if (!pointerLock.hasPointerLock) return bot.entity.pitch -= e.movementY * optionsScrn.mouseSensitivityY * 0.0001 bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch)) bot.entity.yaw -= e.movementX * optionsScrn.mouseSensitivityX * 0.0001 @@ -299,17 +297,12 @@ async function connect (options) { } function changeCallback () { - if (document.pointerLockElement === renderer.domElement || - // @ts-ignore - document.mozPointerLockElement === renderer.domElement || - // @ts-ignore - document.webkitPointerLockElement === renderer.domElement) { - document.addEventListener('mousemove', moveCallback, false) - } else { - document.removeEventListener('mousemove', moveCallback, false) + if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { + showModal(pauseMenu) } } + document.addEventListener('mousemove', moveCallback, false) document.addEventListener('pointerlockchange', changeCallback, false) document.addEventListener('mozpointerlockchange', changeCallback, false) document.addEventListener('webkitpointerlockchange', changeCallback, false) @@ -338,7 +331,7 @@ async function connect (options) { // navigator.keyboard.lock(['KeyW']) const promise = requestPointerLock.apply(renderer.domElement, { unadjustedMovement: window.localStorage.getItem('mouseRawInput') === 'true' - }); + }) promise?.catch((error) => { if (error.name === "NotSupportedError") { // Some platforms may not support unadjusted movement, request again a regular pointer lock. @@ -361,8 +354,7 @@ async function connect (options) { }, false) document.addEventListener('keydown', (e) => { - if (chat.inChat) return - if (gameMenu.inMenu) return + if (activeModalStack.length) return keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { @@ -427,17 +419,35 @@ async function connect (options) { }, false) loadingScreen.status = 'Done!' - console.log(loadingScreen.status) // only do that because it's read in index.html and npm run fix complains. + console.log('Done!') hud.init(renderer, bot, host) hud.style.display = 'block' setTimeout(function () { + if (loadingScreen.hasError) return // remove loading screen, wait a second to make sure a frame has properly rendered - loadingScreen.style = 'display: none;' + hideModal(loadingScreen) }, 2500) }) } +const requestPointerLock = () => { + if (hud.style.display === 'none' || activeModalStack.length) return + renderer.domElement.requestPointerLock() +} + +window.addEventListener('mousedown', (e) => { + requestPointerLock() +}) + +window.addEventListener('keydown', (e) => { + if (e.code === 'Escape') { + hideModal(undefined, { event: e, renderer }, () => { + requestPointerLock() // if no modals left + }) + } +}) + showEl('title-screen') main() diff --git a/lib/chat.js b/lib/chat.js index fb4bc562c..38d94b2ef 100644 --- a/lib/chat.js +++ b/lib/chat.js @@ -1,5 +1,6 @@ const { LitElement, html, css } = require('lit') const { isMobile } = require('./menus/components/common') +const { activeModalStack, hideCurrentModal } = require('./globalState') const styles = { black: 'color:#000000', @@ -93,7 +94,7 @@ class ChatBox extends LitElement { #chatinput:focus { border-color: white; } - + .chat-message { display: block; padding-left: 4px; @@ -148,8 +149,8 @@ class ChatBox extends LitElement { this.shadowRoot.querySelector('#chat-wrapper2').classList.toggle('input-mobile', isMobile()) this.shadowRoot.querySelector('#chat-wrapper').classList.toggle('display-mobile', isMobile()) - // Set inChat value - this.inChat = true + activeModalStack.push(this) + // Exit the pointer lock document.exitPointerLock() // Show chat input @@ -166,31 +167,25 @@ class ChatBox extends LitElement { document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened')) } + get inChat () { + return activeModalStack.includes(this) + } + /** - * @param {globalThis.THREE.Renderer} client - * @param {import('minecraft-protocol').Client} renderer + * @param {import('minecraft-protocol').Client} client */ - init (client, renderer) { - this.inChat = false + init (client) { const chat = this.shadowRoot.querySelector('#chat') - const gameMenu = document.getElementById('pause-screen') const chatInput = this.shadowRoot.querySelector('#chatinput') - renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock || - renderer.domElement.mozRequestPointerLock || - renderer.domElement.webkitRequestPointerLock - // Show chat chat.style.display = 'block' - // Esc event - Doesnt work with onkeypress?! - keypressed is deprecated uk + // Chat events document.addEventListener('keydown', e => { - if (gameMenu.inMenu) return - if (!this.inChat) return + if (activeModalStack.slice(-1)[0] !== this) return e = e || window.event - if (e.code === 'Escape') { - disableChat() - } else if (e.code === 'ArrowUp') { + if (e.code === 'ArrowUp') { if (this.chatHistoryPos === 0) return chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : '' setTimeout(() => { chatInput.setSelectionRange(-1, -1) }, 0) @@ -203,11 +198,9 @@ class ChatBox extends LitElement { const keyBindScrn = document.getElementById('keybinds-screen') - // Chat events document.addEventListener('keypress', e => { - if (gameMenu.inMenu) return e = e || window.event - if (this.inChat === false) { + if (!this.inChat && activeModalStack.length === 0) { keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { switch (km.defaultKey) { @@ -231,17 +224,11 @@ class ChatBox extends LitElement { if (e.code === 'Enter') { this.chatHistory.push(chatInput.value) client.write('chat', { message: chatInput.value }) - disableChat() + hideCurrentModal() } }) - const disableChat = () => { - this.inChat = false - hideChat() - renderer.domElement.requestPointerLock() - } - - const hideChat = () => { + this.hide = () => { // Clear chat input chatInput.value = '' // Unfocus it @@ -252,7 +239,9 @@ class ChatBox extends LitElement { chat.style.maxHeight = 'var(--chatHeight)' chat.scrollTop = chat.scrollHeight // Stay bottom of the list document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened')) + return 'custom' // custom hide } + this.hide() client.on('chat', (packet) => { // Reading of chat message @@ -330,11 +319,9 @@ class ChatBox extends LitElement { span.appendChild(document.createTextNode(msg.text)) span.setAttribute( 'style', - `${msg.color ? colorF(msg.color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(msg.color.toLowerCase()).replace('color:', ''))}` : styles.white}; ${ - msg.bold ? styles.bold + ';' : '' - }${msg.italic ? styles.italic + ';' : ''}${ - msg.strikethrough ? styles.strikethrough + ';' : '' - }${msg.underlined ? styles.underlined + ';' : ''}` + `${msg.color ? colorF(msg.color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(msg.color.toLowerCase()).replace('color:', ''))}` : styles.white}; ${msg.bold ? styles.bold + ';' : '' + }${msg.italic ? styles.italic + ';' : ''}${msg.strikethrough ? styles.strikethrough + ';' : '' + }${msg.underlined ? styles.underlined + ';' : ''}` ) li.appendChild(span) }) @@ -353,8 +340,6 @@ class ChatBox extends LitElement { }, 3000) }, 5000) }) - - hideChat() } } diff --git a/lib/globalState.js b/lib/globalState.js new file mode 100644 index 000000000..046aa78e0 --- /dev/null +++ b/lib/globalState.js @@ -0,0 +1,49 @@ +//@ts-check + +/** + * @typedef {(HTMLElement & Record)} Modal + */ + +/** @type {Modal[]} */ +export const activeModalStack = [] + +window.activeModalStack = activeModalStack + +export const customDisplayManageKeyword = 'custom' + +const showModalInner = (/** @type {Modal} */ modal) => { + const cancel = modal.show?.() + if (cancel && cancel !== customDisplayManageKeyword) return false + if (cancel !== 'custom') modal.style.display = 'block' + return true +} + +export const showModal = (/** @type {Modal} */ modal) => { + const curModal = activeModalStack.slice(-1)[0] + if (modal === curModal || !showModalInner(modal)) return + if (curModal) curModal.style.display = 'none' + activeModalStack.push(modal) +} + +export const hideModal = (modal = activeModalStack.slice(-1)[0], data, postActions) => { + if (modal) { + const cancel = modal.hide?.(data) + if (!cancel || cancel === customDisplayManageKeyword) { + if (cancel !== customDisplayManageKeyword) modal.style.display = 'none' + activeModalStack.pop() + const newModal = activeModalStack.slice(-1)[0] + if (newModal) { + // would be great to ignore cancel I guess? + showModalInner(newModal) + } + postActions?.() + } + } else { + document.exitPointerLock() + } +} + +export const hideCurrentModal = () => { + // todo this might be tricky + window.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape', })) // trigger handler in index.js +} diff --git a/lib/menus/components/common.js b/lib/menus/components/common.js index 5be48a78c..e84fe4304 100644 --- a/lib/menus/components/common.js +++ b/lib/menus/components/common.js @@ -55,18 +55,8 @@ function openURL (url) { window.open(url, '_blank', 'noopener,noreferrer') } -/** - * @param {HTMLElement} prev - * @param {HTMLElement} next - */ -function displayScreen (prev, next) { - prev.style.display = 'none' - next.style.display = 'block' -} - export { commonCss, isMobile, openURL, - displayScreen } diff --git a/lib/menus/hud.js b/lib/menus/hud.js index 48bcdd1e9..659d22df7 100644 --- a/lib/menus/hud.js +++ b/lib/menus/hud.js @@ -1,5 +1,6 @@ const { LitElement, html, css } = require('lit') const { isMobile } = require('./components/common') +const { showModal } = require('../globalState') class Hud extends LitElement { static get styles () { @@ -222,7 +223,7 @@ class Hud extends LitElement { debugMenu.bot = bot hotbar.init() - chat.init(bot._client, renderer) + chat.init(bot._client) bot.on('spawn', () => playerList.init(bot, host)) bot.on('entityHurt', (entity) => { @@ -300,13 +301,13 @@ class Hud extends LitElement { return html`
+ e.stopPropagation() + this.shadowRoot.querySelector('#chat').enableChat(false) + }}> + e.stopPropagation() + showModal(document.getElementById('pause-screen')) + }}>
+ e.stopPropagation() + const b = e.target.classList.toggle('is-down') + this.bot.setControlState('sneak', b) + }}>
-
`)} @@ -156,7 +157,7 @@ class KeyBindsScreen extends LitElement {
v.key !== v.defaultKey)} @pmui-click=${this.onResetAllPress}> - displayScreen(this, document.getElementById('options-screen'))}> + hideCurrentModal()}>
` } diff --git a/lib/menus/loading_screen.js b/lib/menus/loading_screen.js index 845111b65..d86dbdb3c 100644 --- a/lib/menus/loading_screen.js +++ b/lib/menus/loading_screen.js @@ -50,12 +50,17 @@ class LoadingScreen extends LitElement { load() } + hide () { + // cancel hiding + return true + } + render () { return html`

${this.hasError ? this.status : this.loadingText}

- + ${this.hasError ? html` window.location.reload()}>` : '' diff --git a/lib/menus/options_screen.js b/lib/menus/options_screen.js index 117cd1462..da16dd225 100644 --- a/lib/menus/options_screen.js +++ b/lib/menus/options_screen.js @@ -1,5 +1,6 @@ const { LitElement, html, css } = require('lit') -const { commonCss, displayScreen, isMobile } = require('./components/common') +const { commonCss, isMobile } = require('./components/common') +const { showModal, hideCurrentModal } = require('../globalState') class OptionsScreen extends LitElement { static get styles () { @@ -80,86 +81,86 @@ class OptionsScreen extends LitElement {
{ - this.mouseSensitivityX = Number(e.target.value) - window.localStorage.setItem('mouseSensX', this.mouseSensitivityX * 0.0001) - }}> + this.mouseSensitivityX = Number(e.target.value) + window.localStorage.setItem('mouseSensX', this.mouseSensitivityX * 0.0001) + }}> { - this.mouseSensitivityY = Number(e.target.value) - window.localStorage.setItem('mouseSensY', this.mouseSensitivityY * 0.0001) - }}> + this.mouseSensitivityY = Number(e.target.value) + window.localStorage.setItem('mouseSensY', this.mouseSensitivityY * 0.0001) + }}>
{ - this.chatWidth = Number(e.target.value) - window.localStorage.setItem('chatWidth', `${this.chatWidth}`) - document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) - }}> + this.chatWidth = Number(e.target.value) + window.localStorage.setItem('chatWidth', `${this.chatWidth}`) + document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) + }}> { - this.chatHeight = Number(e.target.value) - window.localStorage.setItem('chatHeight', `${this.chatHeight}`) - document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`) - }}> + this.chatHeight = Number(e.target.value) + window.localStorage.setItem('chatHeight', `${this.chatHeight}`) + document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`) + }}>
{ - this.chatScale = Number(e.target.value) - window.localStorage.setItem('chatScale', `${this.chatScale}`) - document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) - }}> + this.chatScale = Number(e.target.value) + window.localStorage.setItem('chatScale', `${this.chatScale}`) + document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) + }}> { - this.sound = Number(e.target.value) - window.localStorage.setItem('sound', `${this.sound}`) - }}> + this.sound = Number(e.target.value) + window.localStorage.setItem('sound', `${this.sound}`) + }}>
- displayScreen(this, document.getElementById('keybinds-screen'))}> + showModal(document.getElementById('keybinds-screen'))}> { - this.guiScale = Number(e.target.value) - window.localStorage.setItem('guiScale', `${this.guiScale}`) - document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) - }}> + this.guiScale = Number(e.target.value) + window.localStorage.setItem('guiScale', `${this.guiScale}`) + document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) + }}>
${this.isInsideWorld -? '' -: html` + ? '' + : html`
{ - this.renderDistance = Number(e.target.value) - window.localStorage.setItem('renderDistance', `${this.renderDistance}`) - }}> + this.renderDistance = Number(e.target.value) + window.localStorage.setItem('renderDistance', `${this.renderDistance}`) + }}> { - this.fov = Number(e.target.value) - window.localStorage.setItem('fov', `${this.fov}`) + this.fov = Number(e.target.value) + window.localStorage.setItem('fov', `${this.fov}`) - this.dispatchEvent(new window.CustomEvent('fov_changed', { fov: this.fov })) - }}> + this.dispatchEvent(new window.CustomEvent('fov_changed', { fov: this.fov })) + }}>
`}
{ - this.forceMobileControls = !this.forceMobileControls - window.localStorage.setItem('forceMobileControls', `${this.forceMobileControls}`) - if (this.forceMobileControls || isMobile()) { - document.getElementById('hud').showMobileControls(true) - } else { - document.getElementById('hud').showMobileControls(false) - } - this.requestUpdate() - } - }> + this.forceMobileControls = !this.forceMobileControls + window.localStorage.setItem('forceMobileControls', `${this.forceMobileControls}`) + if (this.forceMobileControls || isMobile()) { + document.getElementById('hud').showMobileControls(true) + } else { + document.getElementById('hud').showMobileControls(false) + } + this.requestUpdate() + } + }>
{ - this.mouseRawInput = !this.mouseRawInput - window.localStorage.setItem('mouseRawInput', `${this.mouseRawInput}`) - this.requestUpdate() - } - }> + this.mouseRawInput = !this.mouseRawInput + window.localStorage.setItem('mouseRawInput', `${this.mouseRawInput}`) + this.requestUpdate() + } + }>
- displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}> + hideCurrentModal()}>
` } diff --git a/lib/menus/pause_screen.js b/lib/menus/pause_screen.js index 07ad849ab..a4fce77da 100644 --- a/lib/menus/pause_screen.js +++ b/lib/menus/pause_screen.js @@ -1,5 +1,6 @@ const { LitElement, html, css } = require('lit') -const { openURL, displayScreen } = require('./components/common') +const { openURL } = require('./components/common') +const { hideCurrentModal, showModal } = require('../globalState') class PauseScreen extends LitElement { static get styles () { @@ -45,24 +46,6 @@ class PauseScreen extends LitElement { constructor () { super() - this.inMenu = false - } - - init (renderer) { - const chat = document.getElementById('hud').shadowRoot.querySelector('#chat') - const self = this - - document.addEventListener('keydown', e => { - if (chat.inChat) return - e = e || window.event - if (e.keyCode === 27 || e.key === 'Escape' || e.key === 'Esc') { - if (self.inMenu) { - self.disableGameMenu(renderer) - } else { - self.enableGameMenu() - } - } - }) } render () { @@ -70,40 +53,28 @@ class PauseScreen extends LitElement {

Game Menu

- +
openURL('https://github.com/PrismarineJS/prismarine-web-client')}> openURL('https://discord.gg/4Ucm684Fq3')}>
- displayScreen(this, document.getElementById('options-screen'))}> - window.location.reload()}> + showModal(document.getElementById('options-screen'))}> + { + window.location.search = '' + window.location.reload() + }}>
` } - disableGameMenu (renderer = false) { - this.inMenu = false - this.style.display = 'none' - if (renderer) { - renderer.domElement.requestPointerLock() - } - } - - enableGameMenu () { - this.inMenu = true - document.exitPointerLock() - this.style.display = 'block' + show () { this.focus() } - onReturnPress (renderer = false) { - this.inMenu = false - this.style.display = 'none' - if (renderer) { - renderer.domElement.requestPointerLock() - } + onReturnPress () { + hideCurrentModal() } } diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js index ac0326445..a0f33fd14 100644 --- a/lib/menus/play_screen.js +++ b/lib/menus/play_screen.js @@ -1,5 +1,6 @@ const { LitElement, html, css } = require('lit') -const { commonCss, displayScreen } = require('./components/common') +const { commonCss } = require('./components/common') +const { hideCurrentModal } = require('../globalState') class PlayScreen extends LitElement { static get styles () { @@ -63,7 +64,7 @@ class PlayScreen extends LitElement { constructor () { super() - window.fetch('config.json').then(res => res.json()).then(c => c, (error) => { + window.fetch('config.json').then(res => res.json()).then(c => c, (error) => { console.error('Failed to load config.json', error) return {} }).then(config => { @@ -157,7 +158,7 @@ class PlayScreen extends LitElement {
- displayScreen(this, document.getElementById('title-screen'))}> + hideCurrentModal()}>
` } diff --git a/lib/menus/title_screen.js b/lib/menus/title_screen.js index 611d5dce4..b95b2b761 100644 --- a/lib/menus/title_screen.js +++ b/lib/menus/title_screen.js @@ -1,4 +1,5 @@ -const { openURL, displayScreen } = require('./components/common') +const { showModal } = require('../globalState') +const { openURL } = require('./components/common') const { LitElement, html, css } = require('lit') class TitleScreen extends LitElement { @@ -105,8 +106,12 @@ class TitleScreen extends LitElement { ` diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js index a0f33fd14..161ffd08b 100644 --- a/lib/menus/play_screen.js +++ b/lib/menus/play_screen.js @@ -39,7 +39,7 @@ class PlayScreen extends LitElement { width: 310px; } - .extra-info-bv { + .extra-info-version { font-size: 10px; color: rgb(206, 206, 206); text-shadow: 1px 1px black; @@ -47,6 +47,18 @@ class PlayScreen extends LitElement { left: calc(50% + 2px); bottom: -34px; } + + .extra-info-proxy { + font-size: 8px; + color: rgb(206, 206, 206); + text-shadow: 1px 1px black; + margin:0; + margin-top:-12px; + } + + a { + color: white; + } ` } @@ -121,7 +133,7 @@ class PlayScreen extends LitElement {
{ this.proxyport = e.target.value }} >
+
+

Enter proxy url you want to use. Learn more.

+
{ this.version = e.target.value }} >
-

Leave blank and it will be chosen automatically

+

Leave blank and it will be chosen automatically

diff --git a/styles.css b/styles.css index bd8bf6800..38efac0ae 100644 --- a/styles.css +++ b/styles.css @@ -12,6 +12,10 @@ box-sizing: border-box; } +a { + color: white; +} + html { height: 100vh; overflow: hidden; diff --git a/webpack.prod.js b/webpack.prod.js index 3b95ec01d..d0a2b1527 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -22,7 +22,7 @@ module.exports = merge(common, { }), new webpack.ProvidePlugin({ // get from github actions or vercel env - GITHUB_URL: process.env.VERCEL_GIT_REPO_OWNER + 'process.env.GITHUB_URL': process.env.VERCEL_GIT_REPO_OWNER ? `https://github.com/${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` : process.env.GITHUB_REPOSITORY }) From d177ba10a65f920bdcc7df442866275d7b121e45 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 12 Aug 2023 20:55:55 +0300 Subject: [PATCH 034/318] experimental way to handle reconnect without page reload, which doesn't work only when player is joined (though R should work!) --- index.html | 2 +- index.js | 176 ++++++++++++++------------- lib/globalState.js | 30 ++++- lib/menus/components/button.js | 5 + lib/menus/loading_or_error_screen.js | 95 +++++++++++++++ lib/menus/loading_screen.js | 72 ----------- lib/panorama.js | 65 ++++++++++ lib/utilsTs.ts | 33 +++++ 8 files changed, 314 insertions(+), 164 deletions(-) create mode 100644 lib/menus/loading_or_error_screen.js delete mode 100644 lib/menus/loading_screen.js create mode 100644 lib/panorama.js create mode 100644 lib/utilsTs.ts diff --git a/index.html b/index.html index 251cd346a..cac5dc561 100644 --- a/index.html +++ b/index.html @@ -20,7 +20,7 @@
- + diff --git a/index.js b/index.js index 482629bce..638ba05ea 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ require('./lib/menus/components/bossbars_overlay') require('./lib/menus/hud') require('./lib/menus/play_screen') require('./lib/menus/pause_screen') -require('./lib/menus/loading_screen') +require('./lib/menus/loading_or_error_screen') require('./lib/menus/keybinds_screen') require('./lib/menus/options_screen') require('./lib/menus/advanced_options_screen') @@ -39,9 +39,10 @@ const Cursor = require('./lib/cursor') //@ts-ignore global.THREE = require('three') const { initVR } = require('./lib/vr') -const { activeModalStack, showModal, hideModal, hideCurrentModal } = require('./lib/globalState') +const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalStacks, replaceActiveModalStack } = require('./lib/globalState') const { pointerLock, goFullscreen, toNumber } = require('./lib/utils') const { notification } = require('./lib/menus/notification') +const { removePanorama, addPanoramaCubeMap, initPanoramaOptions } = require('./lib/panorama') if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -83,61 +84,7 @@ document.body.appendChild(renderer.domElement) // Create viewer const viewer = new Viewer(renderer) - -// Menu panorama background -function addPanoramaCubeMap () { - let time = 0 - viewer.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000) - viewer.camera.updateProjectionMatrix() - viewer.camera.position.set(0, 0, 0) - viewer.camera.rotation.set(0, 0, 0) - const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) - - const loader = new THREE.TextureLoader() - const panorMaterials = [ - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_1.png'), transparent: true, side: THREE.DoubleSide }), // WS - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_3.png'), transparent: true, side: THREE.DoubleSide }), // ES - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_4.png'), transparent: true, side: THREE.DoubleSide }), // Up - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_5.png'), transparent: true, side: THREE.DoubleSide }), // Down - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_0.png'), transparent: true, side: THREE.DoubleSide }), // NS - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_2.png'), transparent: true, side: THREE.DoubleSide }) // SS - ] - - const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) - - panoramaBox.onBeforeRender = () => { - time += 0.01 - panoramaBox.rotation.y = Math.PI + time * 0.01 - panoramaBox.rotation.z = Math.sin(-time * 0.001) * 0.001 - } - - const group = new THREE.Object3D() - group.add(panoramaBox) - - const Entity = require('prismarine-viewer/viewer/lib/entity/Entity') - for (let i = 0; i < 42; i++) { - const m = new Entity('1.16.4', 'squid').mesh - m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) - m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') - const v = Math.random() * 0.01 - m.children[0].onBeforeRender = () => { - m.rotation.y += v - m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2 - } - group.add(m) - } - - viewer.scene.add(group) - return group -} - -const panoramaCubeMap = addPanoramaCubeMap() - -function removePanorama () { - viewer.camera = new THREE.PerspectiveCamera(document.getElementById('options-screen').fov, window.innerWidth / window.innerHeight, 0.1, 1000) - viewer.camera.updateProjectionMatrix() - viewer.scene.remove(panoramaCubeMap) -} +initPanoramaOptions(viewer) const frameLimit = toNumber(localStorage.frameLimit) let interval = frameLimit && 1000 / frameLimit @@ -178,7 +125,7 @@ window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight) }) -const loadingScreen = document.getElementById('loading-screen') +const loadingScreen = document.getElementById('loading-error-screen') const hud = document.getElementById('hud') const optionsScrn = document.getElementById('options-screen') @@ -188,8 +135,8 @@ const pauseMenu = document.getElementById('pause-screen') function setLoadingScreenStatus (status, isError = false) { showModal(loadingScreen) if (loadingScreen.hasError) return - loadingScreen.status = status loadingScreen.hasError = isError + loadingScreen.status = status } async function main () { @@ -204,6 +151,21 @@ async function main () { } async function connect (options) { + loadingScreen.maybeRecoverable = true + const oldSetInterval = window.setInterval + // @ts-ignore + window.setInterval = (callback, ms) => { + const id = oldSetInterval.call(window, callback, ms) + timeouts.push(id) + return id + } + const oldSetTimeout = window.setTimeout + //@ts-ignore + window.setTimeout = (callback, ms) => { + const id = oldSetTimeout.call(window, callback, ms) + timeouts.push(id) + return id + } const debugMenu = hud.shadowRoot.querySelector('#debug-overlay') const viewDistance = optionsScrn.renderDistance @@ -238,21 +200,31 @@ async function connect (options) { setLoadingScreenStatus('Logging in') - const bot = mineflayer.createBot({ - host, - port, - version: options.botVersion === '' ? false : options.botVersion, - username, - password, - viewDistance: 'tiny', - checkTimeoutInterval: 240 * 1000, - noPongTimeout: 240 * 1000, - closeTimeout: 240 * 1000 - }) - hud.preload(bot) - - bot.on('error', (err) => { + /** @type {mineflayer.Bot} */ + let bot + const destroy = () => { + // simple variant, still buggy + postRenderFrameFn = () => { } + if (bot) { + bot.removeAllListeners() + bot._client.removeAllListeners() + bot._client = undefined + bot = undefined + } + removeAllListeners() + for (const timeout of timeouts) { + clearTimeout(timeout) + } + timeouts = [] + for (const interval of intervals) { + clearInterval(interval) + } + intervals = [] + } + const handleError = (err) => { console.log('Encountered error!', err) + + // #region rejoin key const controller = new AbortController() window.addEventListener('keydown', (e) => { if (e.code !== 'KeyR') return @@ -260,17 +232,46 @@ async function connect (options) { connect(options) loadingScreen.hasError = false }, { signal: controller.signal }) - setLoadingScreenStatus(`Error encountered. Error message: ${err}. Please reload the page`, true) - }) + // #endregion + + setLoadingScreenStatus(`Error encountered. Error message: ${err}`, true) + destroy() + } + + try { + bot = mineflayer.createBot({ + host, + port, + version: options.botVersion === '' ? false : options.botVersion, + username, + password, + viewDistance: 'tiny', + checkTimeoutInterval: 240 * 1000, + noPongTimeout: 240 * 1000, + closeTimeout: 240 * 1000 + }) + } catch (err) { + handleError(err) + } + if (!bot) return + hud.preload(bot) + + // bot.on('inject_allowed', () => { + // loadingScreen.maybeRecoverable = false + // }) + + bot.on('error', handleError) bot.on('kicked', (kickReason) => { console.log('User was kicked!', kickReason) - setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReason}. Please reload the page to rejoin`, true) + setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReason}`, true) + destroy() }) bot.on('end', (endReason) => { console.log('disconnected for', endReason) - setLoadingScreenStatus(`You have been disconnected from the server. End reason: ${endReason}. Please reload the page to rejoin`, true) + destroy() + setLoadingScreenStatus(`You have been disconnected from the server. End reason: ${endReason}`, true) }) bot.once('login', () => { @@ -370,11 +371,11 @@ async function connect (options) { } } - window.addEventListener('mousemove', moveCallback, { capture: true }) - document.addEventListener('pointerlockchange', changeCallback, false) + registerListener(window, 'mousemove', moveCallback, { capture: true }) + registerListener(document, 'pointerlockchange', changeCallback, false) let lastTouch - document.addEventListener('touchmove', (e) => { + registerListener(document, 'touchmove', (e) => { window.scrollTo(0, 0) e.preventDefault() e.stopPropagation() @@ -384,17 +385,17 @@ async function connect (options) { lastTouch = e.touches[0] }, { passive: false }) - document.addEventListener('touchend', (e) => { + registerListener(document, 'touchend', (e) => { lastTouch = undefined }, { passive: false }) - document.addEventListener('contextmenu', (e) => e.preventDefault(), false) + registerListener(document, 'contextmenu', (e) => e.preventDefault(), false) - window.addEventListener('blur', (e) => { + registerListener(document, 'blur', (e) => { bot.clearControlStates() }, false) - document.addEventListener('keydown', (e) => { + registerListener(document, 'keydown', (e) => { if (activeModalStack.length) return keyBindScrn.keymaps.forEach(km => { @@ -429,7 +430,7 @@ async function connect (options) { }) }, false) - document.addEventListener('keyup', (e) => { + registerListener(document, 'keyup', (e) => { keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { switch (km.defaultKey) { @@ -468,8 +469,8 @@ async function connect (options) { setTimeout(function () { if (loadingScreen.hasError) return // remove loading screen, wait a second to make sure a frame has properly rendered - loadingScreen.style.display = 'none' - activeModalStack.splice(0, activeModalStack.length) + activeModalStacks['main-menu'] = activeModalStack + replaceActiveModalStack('', []) }, 2500) }) } @@ -508,5 +509,6 @@ window.addEventListener('keydown', (e) => { // } }) +addPanoramaCubeMap() showModal(document.getElementById('title-screen')) main() diff --git a/lib/globalState.js b/lib/globalState.js index 2ae42b3cb..afcf67d74 100644 --- a/lib/globalState.js +++ b/lib/globalState.js @@ -2,6 +2,8 @@ import { pointerLock } from './utils' +// todo: refactor structure with support of hideNext=false + /** * @typedef {(HTMLElement & Record)} Modal */ @@ -9,6 +11,15 @@ import { pointerLock } from './utils' /** @type {Modal[]} */ export const activeModalStack = [] +export const replaceActiveModalStack = (name, newModalStack = activeModalStacks[name]) => { + hideModal(undefined, undefined, { restorePrevious: false, force: true, }) + activeModalStack.splice(0, activeModalStack.length, ...newModalStack) + // todo restore previous +} + +/** @type {Record} */ +export const activeModalStacks = {} + window.activeModalStack = activeModalStack export const customDisplayManageKeyword = 'custom' @@ -27,14 +38,25 @@ export const showModal = (/** @type {Modal} */ modal) => { activeModalStack.push(modal) } -export const hideModal = (modal = activeModalStack.slice(-1)[0], data) => { +/** + * + * @param {*} data + * @param {{ force?: boolean, restorePrevious?: boolean, }} options + * @returns + */ +export const hideModal = (modal = activeModalStack.slice(-1)[0], data = undefined, options = {}) => { + const { force = false, restorePrevious = true } = options if (!modal) return - const cancel = modal.hide?.(data) + let cancel = modal.hide?.(data) + if (force && cancel !== customDisplayManageKeyword) { + cancel = undefined + } + if (!cancel || cancel === customDisplayManageKeyword) { if (cancel !== customDisplayManageKeyword) modal.style.display = 'none' activeModalStack.pop() const newModal = activeModalStack.slice(-1)[0] - if (newModal) { + if (newModal && restorePrevious) { // would be great to ignore cancel I guess? showModalInner(newModal) } @@ -42,7 +64,7 @@ export const hideModal = (modal = activeModalStack.slice(-1)[0], data) => { } } -export const hideCurrentModal = (data, preActions) => { +export const hideCurrentModal = (_data = undefined, preActions = undefined) => { if (hideModal(undefined, undefined)) { preActions?.() if (document.getElementById('hud').style.display === 'none') { diff --git a/lib/menus/components/button.js b/lib/menus/components/button.js index d25a884f0..10e637453 100644 --- a/lib/menus/components/button.js +++ b/lib/menus/components/button.js @@ -4,11 +4,14 @@ const audioContext = new window.AudioContext() const sounds = {} // load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded +let loadingSounds = [] async function loadSound (path) { + loadingSounds.push(path) const res = await window.fetch(path) const data = await res.arrayBuffer() sounds[path] = await audioContext.decodeAudioData(data) + loadingSounds.splice(loadingSounds.indexOf(path), 1) } async function playSound (path) { @@ -18,6 +21,8 @@ async function playSound (path) { volume = options.sound / 100 } + // todo? + if (loadingSounds.includes(path)) return let soundBuffer = sounds[path] if (!soundBuffer) throw new Error(`Sound ${path} not loaded`) diff --git a/lib/menus/loading_or_error_screen.js b/lib/menus/loading_or_error_screen.js new file mode 100644 index 000000000..48c14b68e --- /dev/null +++ b/lib/menus/loading_or_error_screen.js @@ -0,0 +1,95 @@ +//@ts-check +const { LitElement, html, css } = require('lit') +const { commonCss } = require('./components/common') +const { addPanoramaCubeMap } = require('../panorama') +const { hideModal, activeModalStacks, activeModalStack, replaceActiveModalStack } = require('../globalState') + +class LoadingErrorScreen extends LitElement { + static get styles () { + return css` + ${commonCss} + .title { + top: 30px; + } + + .error-buttons { + position: absolute; + top: calc(20% + 50px); + left: 50%; + transform: translate(-50%); + } + ` + } + + static get properties () { + return { + status: { type: String }, + loadingText: { type: String }, + maybeRecoverable: { type: Boolean }, + hasError: { type: Boolean } + } + } + + constructor () { + super() + this.hasError = false + this.maybeRecoverable = false + this.status = 'Waiting for JS load' + } + + firstUpdated () { + this.statusRunner() + } + + async statusRunner () { + const array = ['.', '..', '...', ''] + const timer = ms => new Promise((resolve) => setTimeout(resolve, ms)) + + const load = async () => { + for (let i = 0; true; i = ((i + 1) % array.length)) { + if (!this.hasError) { + this.loadingText = this.status + array[i] + } + await timer(500) + } + } + + load() + } + + hide () { + // cancel hiding + return true + } + + render () { + console.log('render') + return html` +
+ +

${this.hasError ? this.status : this.loadingText}

+ + ${this.hasError + ? html`
{ + if (window.location.search) { + const qs = new URLSearchParams(window.location.search) + // remove reconnect from qs + qs.delete('reconnect') + window.history.replaceState(null, null, qs.toString()) + } + this.hasError = false + if (activeModalStacks['main-menu']) { + replaceActiveModalStack('main-menu') + } else { + hideModal(undefined, undefined, { force: true }) + } + document.getElementById('play-screen').style.display = 'block' + addPanoramaCubeMap() + }}> window.location.reload()} pmui-label="Full Reload" pmui-width="200px">
` + : '' + } + ` + } +} + +window.customElements.define('pmui-loading-error-screen', LoadingErrorScreen) diff --git a/lib/menus/loading_screen.js b/lib/menus/loading_screen.js deleted file mode 100644 index d86dbdb3c..000000000 --- a/lib/menus/loading_screen.js +++ /dev/null @@ -1,72 +0,0 @@ -const { LitElement, html, css } = require('lit') -const { commonCss } = require('./components/common') - -class LoadingScreen extends LitElement { - static get styles () { - return css` - ${commonCss} - .title { - top: 30px; - } - - #cancel-btn { - position: absolute; - top: calc(20% + 50px); - left: 50%; - transform: translate(-50%); - } - ` - } - - static get properties () { - return { - status: { type: String }, - loadingText: { type: String }, - hasError: { type: Number } - } - } - - constructor () { - super() - this.hasError = false - this.status = 'Waiting for JS load' - } - - firstUpdated () { - this.statusRunner() - } - - async statusRunner () { - const array = ['.', '..', '...', ''] - const timer = ms => new Promise((resolve) => setTimeout(resolve, ms)) - - const load = async () => { - for (let i = 0; true; i = ((i + 1) % array.length)) { - this.loadingText = this.status + array[i] - await timer(500) - } - } - - load() - } - - hide () { - // cancel hiding - return true - } - - render () { - return html` -
- -

${this.hasError ? this.status : this.loadingText}

- - ${this.hasError - ? html` window.location.reload()}>` - : '' - } - ` - } -} - -window.customElements.define('pmui-loadingscreen', LoadingScreen) diff --git a/lib/panorama.js b/lib/panorama.js new file mode 100644 index 000000000..f1bf8e191 --- /dev/null +++ b/lib/panorama.js @@ -0,0 +1,65 @@ +//@ts-check + +let panoramaCubeMap +let viewer + +export const initPanoramaOptions = (_viewer) => { + viewer = _viewer +} + +// Menu panorama background +export function addPanoramaCubeMap () { + // remove all existing object in the viewer.scene + // viewer.scene.children = [] + + let time = 0 + viewer.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000) + viewer.camera.updateProjectionMatrix() + viewer.camera.position.set(0, 0, 0) + viewer.camera.rotation.set(0, 0, 0) + const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) + + const loader = new THREE.TextureLoader() + const panorMaterials = [ + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_1.png'), transparent: true, side: THREE.DoubleSide }), // WS + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_3.png'), transparent: true, side: THREE.DoubleSide }), // ES + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_4.png'), transparent: true, side: THREE.DoubleSide }), // Up + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_5.png'), transparent: true, side: THREE.DoubleSide }), // Down + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_0.png'), transparent: true, side: THREE.DoubleSide }), // NS + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_2.png'), transparent: true, side: THREE.DoubleSide }) // SS + ] + + const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) + + panoramaBox.onBeforeRender = () => { + time += 0.01 + panoramaBox.rotation.y = Math.PI + time * 0.01 + panoramaBox.rotation.z = Math.sin(-time * 0.001) * 0.001 + } + + const group = new THREE.Object3D() + group.add(panoramaBox) + + const Entity = require('prismarine-viewer/viewer/lib/entity/Entity') + for (let i = 0; i < 42; i++) { + const m = new Entity('1.16.4', 'squid').mesh + m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) + m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') + const v = Math.random() * 0.01 + m.children[0].onBeforeRender = () => { + m.rotation.y += v + m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2 + } + group.add(m) + } + + viewer.scene.add(group) + panoramaCubeMap = group +} + +export function removePanorama () { + if (!panoramaCubeMap) throw new Error('panorama is not there') + viewer.camera = new THREE.PerspectiveCamera(document.getElementById('options-screen').fov, window.innerWidth / window.innerHeight, 0.1, 1000) + viewer.camera.updateProjectionMatrix() + viewer.scene.remove(panoramaCubeMap) +} diff --git a/lib/utilsTs.ts b/lib/utilsTs.ts new file mode 100644 index 000000000..75cd623a3 --- /dev/null +++ b/lib/utilsTs.ts @@ -0,0 +1,33 @@ +type Options = boolean | Readonly | undefined + +function registerListener( + element: Readonly | null | undefined, + eventType: KD, + // eslint-disable-next-line functional/prefer-immutable-types + listener: (this: Document, evt: DocumentEventMap[KD]) => void, + options?: Options, +): void +function registerListener( + element: Readonly | null | undefined, + eventType: KH, + // eslint-disable-next-line functional/prefer-immutable-types + listener: (this: HTMLElement, evt: HTMLElementEventMap[KH]) => void, + options?: Options, +): void +function registerListener( + element: Readonly | null | undefined, + eventType: KW, + // eslint-disable-next-line functional/prefer-immutable-types + listener: (this: Window, evt: WindowEventMap[KW]) => void, + options?: Options, +): void +//@ts-ignore +function registerListener( + element: Readonly | null | undefined, + eventType: string, + // eslint-disable-next-line functional/prefer-immutable-types + listener: (evt: Event) => void, + options?: Options, +): void + +export type RegisterListener = typeof registerListener From b61294b51dec028cfabf04a047d7dddf3d15169c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 12 Aug 2023 20:56:34 +0300 Subject: [PATCH 035/318] build should be fixed --- build.js | 16 ++++++++-------- tsconfig.json | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build.js b/build.js index 399476435..7d93053d9 100644 --- a/build.js +++ b/build.js @@ -1,14 +1,14 @@ const fsExtra = require('fs-extra') exports.copyFiles = () => { - fsExtra.copySync('styles.css', 'public/styles.css') - fsExtra.copySync('node_modules/prismarine-viewer/public/blocksStates/', 'public/blocksStates/') - fsExtra.copySync('node_modules/prismarine-viewer/public/textures/', 'public/textures/') - fsExtra.copySync('node_modules/prismarine-viewer/public/worker.js', 'public/worker.js') - fsExtra.copySync('node_modules/prismarine-viewer/public/supportedVersions.json', 'public/supportedVersions.json') - fsExtra.copySync('assets/', 'public/') - fsExtra.copySync('extra-textures/', 'public/extra-textures/') - fsExtra.copySync('config.json', 'public/config.json') + fsExtra.copySync('./node_modules/prismarine-viewer2/public/blocksStates/', 'public/blocksStates/') + fsExtra.copySync('./node_modules/prismarine-viewer2/public/textures/', 'public/textures/') + fsExtra.copySync('./node_modules/prismarine-viewer2/public/worker.js', 'public/worker.js') + fsExtra.copySync('./node_modules/prismarine-viewer2/public/supportedVersions.json', 'public/supportedVersions.json') + fsExtra.copySync('./node_modules/prismarine-viewer2/public/supportedVersions.json', './prismarine-viewer/public/supportedVersions.json') + fsExtra.copySync('./assets/', './public/') + fsExtra.copySync('./extra-textures/', 'public/extra-textures/') + fsExtra.copySync('./config.json', 'public/config.json') } exports.copyFilesDev = () => { diff --git a/tsconfig.json b/tsconfig.json index 0d6a33e61..e8193b544 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "allowJs": true, "allowSyntheticDefaultImports": true, "noEmit": true, + "strictFunctionTypes": true // "strictNullChecks": true }, "include": [ From 216a61ddaea94d6a48e9a3568e0db0cd90c90a18 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 12 Aug 2023 20:57:09 +0300 Subject: [PATCH 036/318] dx: watch styles copy, dont slow debugger cause of creating a lot of files --- webpack.common.js | 19 ++++++------------- webpack.prod.js | 3 +++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/webpack.common.js b/webpack.common.js index 7c0392c32..33341c858 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -8,7 +8,7 @@ const config = { entry: path.resolve(__dirname, './index.js'), output: { path: path.resolve(__dirname, './public'), - filename: './[name]-[chunkhash].js', + filename: './[name].js', publicPath: './' }, resolve: { @@ -61,18 +61,11 @@ const config = { /prismarine-viewer[/|\\]viewer[/|\\]lib[/|\\]utils/, './utils.web.js' ), - // new CopyPlugin({ - // patterns: [ - // { from: path.join(__dirname, '/styles.css'), to: './styles.css' }, - // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/blocksStates/'), to: './blocksStates/' }, - // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/textures/'), to: './textures/' }, - // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/worker.js'), to: './' }, - // { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/supportedVersions.json'), to: './' }, - // { from: path.join(__dirname, 'assets/'), to: './' }, - // { from: path.join(__dirname, 'extra-textures/'), to: './extra-textures/' }, - // { from: path.join(__dirname, 'config.json'), to: './config.json' } - // ] - // }) + new CopyPlugin({ + patterns: [ + { from: path.join(__dirname, '/styles.css'), to: './styles.css' }, + ] + }) ] } diff --git a/webpack.prod.js b/webpack.prod.js index d0a2b1527..2caef9a5a 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -7,6 +7,9 @@ const WorkboxPlugin = require('workbox-webpack-plugin') const webpack = require('webpack') module.exports = merge(common, { + output: { + filename: './[name].js', + }, mode: 'production', devtool: 'source-map', plugins: [ From 75e4349b84ccd2f327d235bb93e9c1fdcb7337be Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 12 Aug 2023 21:25:22 +0300 Subject: [PATCH 037/318] ci: commit possible build fix --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index b0ac9035d..ab02738db 100644 --- a/package.json +++ b/package.json @@ -78,5 +78,10 @@ "webpack-dev-server": "^4.15.1", "webpack-merge": "^5.9.0", "workbox-webpack-plugin": "^6.6.0" + }, + "pnpm": { + "overrides": { + "minecraft-data": "latest" + } } } From bbb8539af2218e1d61663bd1d4bfde58a442eab6 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 12 Aug 2023 22:24:10 +0300 Subject: [PATCH 038/318] experimental singleplayer support --- index.js | 136 +++++++++++++++++++++++---- lib/menus/hud.js | 9 +- lib/menus/loading_or_error_screen.js | 1 - package.json | 4 +- webpack.prod.js | 4 +- 5 files changed, 128 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index 638ba05ea..4a225d526 100644 --- a/index.js +++ b/index.js @@ -43,6 +43,7 @@ const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalSta const { pointerLock, goFullscreen, toNumber } = require('./lib/utils') const { notification } = require('./lib/menus/notification') const { removePanorama, addPanoramaCubeMap, initPanoramaOptions } = require('./lib/panorama') +const { createClient } = require('minecraft-protocol') if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -139,6 +140,31 @@ function setLoadingScreenStatus (status, isError = false) { loadingScreen.status = status } +let mouseMovePostHandle = (e) => { } +let lastMouseCall +function onMouseMove (e) { + if (!pointerLock.hasPointerLock) return + e.stopPropagation?.() + const now = performance.now() + // todo: limit camera movement for now to avoid unexpected jumps + if (now - lastMouseCall < 4) return + lastMouseCall = now + let { mouseSensX, mouseSensY } = optionsScrn + if (mouseSensY === true) mouseSensY = mouseSensX + // debugPitch.innerText = +debugPitch.innerText + e.movementX + mouseMovePostHandle({ + x: e.movementX * mouseSensX * 0.0001, + y: e.movementY * mouseSensY * 0.0001 + }) +} +window.addEventListener('mousemove', onMouseMove, { capture: true }) + + +function hideCurrentScreens () { + activeModalStacks['main-menu'] = activeModalStack + replaceActiveModalStack('', []) +} + async function main () { const menu = document.getElementById('play-screen') @@ -148,6 +174,91 @@ async function main () { removePanorama() connect(options) }) + document.querySelector('#title-screen').addEventListener('singleplayer', (e) => { + menu.style = 'display: none;' + hideCurrentScreens() + removePanorama() + + const version = '1.16.4' + + const viewDistance = 10 + const center = new Vec3(0, 90, 0) + + //@ts-ignore + const World = PrismarineWorld(version) + + // gen + const diamondSquare = require('diamond-square')({ version, seed: Math.floor(Math.random() * Math.pow(2, 31)) }) + + const world = new World(diamondSquare) + const worldView = new WorldView(world, viewDistance, center) + + // Create viewer + const viewer = new Viewer(renderer) + viewer.setVersion(version) + // Attach controls to viewer + const controls = new MapControls(viewer.camera, renderer.domElement) + + // Link WorldView and Viewer + viewer.listen(worldView) + // Initialize viewer, load chunks + worldView.init(center) + + viewer.camera.position.set(center.x, center.y, center.z) + controls.update() + + postRenderFrameFn = () => { + if (controls) controls.update() + worldView.updatePosition(controls.target) + viewer.update() + } + // Browser animation loop + const animate = () => { + window.requestAnimationFrame(animate) + renderer.render(viewer.scene, viewer.camera) + } + animate() + + hud.style.display = 'block' + class FakeBot extends EventTarget { + on (...args) { + //@ts-ignore + super.addEventListener(...args) + } + once (...args) { + //@ts-ignore + super.addEventListener(...args, { once: true }) + } + } + const fakeBot = new FakeBot() + hud.init(renderer, fakeBot) + + let pitch = 0 + let yaw = 0 + mouseMovePostHandle = ({ x, y }) => { + pitch -= y + yaw -= x + pitch = Math.max(minPitch, Math.min(maxPitch, pitch)) + viewer.setFirstPersonCamera(null, pitch, yaw) + } + }) +} + +let listeners = [] +let timeouts = [] +let intervals = [] +// only for dom listeners (no removeAllListeners) +// todo refactor them out of connect fn instead +/** @type {import('./lib/utilsTs').RegisterListener} */ +const registerListener = (target, event, callback) => { + target.addEventListener(event, callback) + listeners.push({ target, event, callback }) +} +const removeAllListeners = () => { + listeners.forEach(({ target, event, callback }) => { + target.removeEventListener(event, callback) + }) + listeners = [] } async function connect (options) { @@ -348,20 +459,10 @@ async function connect (options) { setLoadingScreenStatus('Setting callbacks') - let lastMouseCall - function moveCallback (e) { - if (!pointerLock.hasPointerLock) return - e.stopPropagation?.() - const now = performance.now() - // todo: limit camera movement for now to avoid unexpected jumps - if (now - lastMouseCall < 4) return - lastMouseCall = now - let { mouseSensX, mouseSensY } = optionsScrn - if (mouseSensY === true) mouseSensY = mouseSensX - bot.entity.pitch -= e.movementY * mouseSensX * 0.0001 + mouseMovePostHandle = ({ x, y }) => { + bot.entity.pitch -= y bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch)) - bot.entity.yaw -= e.movementX * mouseSensY * 0.0001 - // debugPitch.innerText = +debugPitch.innerText + e.movementX + bot.entity.yaw -= x } function changeCallback () { @@ -371,7 +472,6 @@ async function connect (options) { } } - registerListener(window, 'mousemove', moveCallback, { capture: true }) registerListener(document, 'pointerlockchange', changeCallback, false) let lastTouch @@ -380,9 +480,8 @@ async function connect (options) { e.preventDefault() e.stopPropagation() if (lastTouch !== undefined) { - moveCallback({ movementX: e.touches[0].pageX - lastTouch.pageX, movementY: e.touches[0].pageY - lastTouch.pageY }) + onMouseMove({ movementX: e.touches[0].pageX - lastTouch.pageX, movementY: e.touches[0].pageY - lastTouch.pageY }) } - lastTouch = e.touches[0] }, { passive: false }) registerListener(document, 'touchend', (e) => { @@ -463,14 +562,13 @@ async function connect (options) { setLoadingScreenStatus('Done!') console.log('Done!') - hud.init(renderer, bot, host) + // hud.init(renderer, bot, host) hud.style.display = 'block' setTimeout(function () { if (loadingScreen.hasError) return // remove loading screen, wait a second to make sure a frame has properly rendered - activeModalStacks['main-menu'] = activeModalStack - replaceActiveModalStack('', []) + hideCurrentScreens() }, 2500) }) } diff --git a/lib/menus/hud.js b/lib/menus/hud.js index 659d22df7..446a07c41 100644 --- a/lib/menus/hud.js +++ b/lib/menus/hud.js @@ -222,9 +222,11 @@ class Hud extends LitElement { hotbar.bot = bot debugMenu.bot = bot - hotbar.init() - chat.init(bot._client) - bot.on('spawn', () => playerList.init(bot, host)) + if (bot._client) { + hotbar.init() + chat.init(bot._client) + bot.on('spawn', () => playerList.init(bot, host)) + } bot.on('entityHurt', (entity) => { if (entity !== bot.entity) return @@ -271,6 +273,7 @@ class Hud extends LitElement { healthbar.gameModeChanged(bot.player.gamemode, bot.game.hardcore) healthbar.updateHealth(bot.health) foodbar.updateHunger(bot.food) + // TODO // breathbar.updateOxygen(bot.oxygenLevel ?? 20) }) diff --git a/lib/menus/loading_or_error_screen.js b/lib/menus/loading_or_error_screen.js index 48c14b68e..2dcc4308d 100644 --- a/lib/menus/loading_or_error_screen.js +++ b/lib/menus/loading_or_error_screen.js @@ -63,7 +63,6 @@ class LoadingErrorScreen extends LitElement { } render () { - console.log('render') return html`
diff --git a/package.json b/package.json index ab02738db..02a396ee7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A minecraft client running in a browser", "main": "index.js", "scripts": { - "build": "webpack --config webpack.prod.js --progress && node build.js copyFiles", + "build": "node build.js copyFiles && webpack --config webpack.prod.js --progress", "build-dev": "node build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", "start": "NODE_OPTIONS=--max-old-space-size=8192 pnpm build-dev", "prod-start": "node server.js", @@ -35,11 +35,13 @@ "homepage": "https://github.com/PrismarineJS/prismarine-web-client#readme", "dependencies": { "compression": "^1.7.4", + "diamond-square": "^1.2.0", "express": "^4.18.2", "fs-extra": "^11.1.1", "ismobilejs": "^1.1.1", "lit": "^2.8.0", "net-browserify": "github:PrismarineJS/net-browserify", + "prismarine-world": "^3.6.2", "querystring": "^0.2.1", "stats.js": "^0.17.0", "url": "^0.11.1", diff --git a/webpack.prod.js b/webpack.prod.js index 2caef9a5a..f78ed679c 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -25,9 +25,9 @@ module.exports = merge(common, { }), new webpack.ProvidePlugin({ // get from github actions or vercel env - 'process.env.GITHUB_URL': process.env.VERCEL_GIT_REPO_OWNER + 'process.env.GITHUB_URL': `"${process.env.VERCEL_GIT_REPO_OWNER ? `https://github.com/${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` - : process.env.GITHUB_REPOSITORY + : process.env.GITHUB_REPOSITORY}"` }) ], }) From bd44805cc47def9722325cbc14a3cc576d3ef727 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 15 Aug 2023 01:38:52 +0300 Subject: [PATCH 039/318] fix hud init & build should be fixed --- index.js | 2 +- lib/menus/components/edit_box.js | 3 ++- lib/menus/play_screen.js | 2 ++ webpack.prod.js | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 4a225d526..d783dc437 100644 --- a/index.js +++ b/index.js @@ -562,7 +562,7 @@ async function connect (options) { setLoadingScreenStatus('Done!') console.log('Done!') - // hud.init(renderer, bot, host) + hud.init(renderer, bot, host) hud.style.display = 'block' setTimeout(function () { diff --git a/lib/menus/components/edit_box.js b/lib/menus/components/edit_box.js index 9d6acaafa..aafedeb64 100644 --- a/lib/menus/components/edit_box.js +++ b/lib/menus/components/edit_box.js @@ -62,6 +62,7 @@ class EditBox extends LitElement { this.id = '' this.value = '' this.label = '' + this.required = false } static get properties () { @@ -119,7 +120,7 @@ class EditBox extends LitElement { type="${this.type ?? 'text'}" name="" spellcheck="false" - required="" + ?required=${this.required} autocomplete="off" autocapitalize="off" value="${this.value}" diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js index 161ffd08b..40c5c49b9 100644 --- a/lib/menus/play_screen.js +++ b/lib/menus/play_screen.js @@ -118,6 +118,7 @@ class PlayScreen extends LitElement { pmui-id="serverip" pmui-value="${this.server}" pmui-type="url" + pmui-required="true" .autocompleteValues=${JSON.parse(localStorage.getItem('serverHistory') || '[]')} @input=${e => { this.server = e.target.value }} > @@ -136,6 +137,7 @@ class PlayScreen extends LitElement { pmui-label="Proxy IP" pmui-id="proxy" pmui-value="${this.proxy}" + pmui-required=${/* TODO derive from config? */false} pmui-type="url" @input=${e => { this.proxy = e.target.value }} > diff --git a/webpack.prod.js b/webpack.prod.js index f78ed679c..895d2144b 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -23,11 +23,11 @@ module.exports = merge(common, { skipWaiting: true, include: ['index.html', 'manifest.json'] // not caching a lot as anyway this works only online }), - new webpack.ProvidePlugin({ + new webpack.DefinePlugin({ // get from github actions or vercel env - 'process.env.GITHUB_URL': `"${process.env.VERCEL_GIT_REPO_OWNER + 'process.env.GITHUB_URL': JSON.stringify(process.env.VERCEL_GIT_REPO_OWNER ? `https://github.com/${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` - : process.env.GITHUB_REPOSITORY}"` + : process.env.GITHUB_REPOSITORY) }) ], }) From 2e6da513d736e5ff7be73f6e27c2b31c88f10100 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 15 Aug 2023 14:28:18 +0300 Subject: [PATCH 040/318] increase build size for now --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02a396ee7..d8be0e479 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A minecraft client running in a browser", "main": "index.js", "scripts": { - "build": "node build.js copyFiles && webpack --config webpack.prod.js --progress", + "build": "NODE_OPTIONS=--max-old-space-size=8192 node build.js copyFiles && webpack --config webpack.prod.js --progress", "build-dev": "node build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", "start": "NODE_OPTIONS=--max-old-space-size=8192 pnpm build-dev", "prod-start": "node server.js", From a3709c14a92984fb2b07dab822cc816ebdac8e80 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 15 Aug 2023 19:51:57 +0300 Subject: [PATCH 041/318] refactor chat providing scalability and more precise text rendering fix reconnect qs removal --- index.html | 1 + lib/chat.js | 211 ++++++++++++++++++--------- lib/globalState.js | 14 ++ lib/menus/hud.js | 2 +- lib/menus/loading_or_error_screen.js | 2 +- 5 files changed, 158 insertions(+), 72 deletions(-) diff --git a/index.html b/index.html index cac5dc561..9cfa61ff8 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@ +
diff --git a/lib/chat.js b/lib/chat.js index 38d94b2ef..3b964ac73 100644 --- a/lib/chat.js +++ b/lib/chat.js @@ -1,6 +1,9 @@ +//@ts-check const { LitElement, html, css } = require('lit') const { isMobile } = require('./menus/components/common') const { activeModalStack, hideCurrentModal } = require('./globalState') +import { repeat } from 'lit/directives/repeat.js' +import { classMap } from 'lit/directives/class-map.js' const styles = { black: 'color:#000000', @@ -36,6 +39,11 @@ function colorShadow (hex, dim = 0.25) { return `#${f(r)}${f(g)}${f(b)}` } +/** + * @typedef {{text;color?;italic?;underlined?;strikethrough?;bold?}} MessagePart + * @typedef {{parts: MessagePart[], id, fading?, faded}} Message + */ + class ChatBox extends LitElement { static get styles () { return css` @@ -44,7 +52,7 @@ class ChatBox extends LitElement { z-index: 10; } - .chat-display-wrapper { + .chat-messages-wrapper { bottom: 40px; padding: 4px; padding-left: 0; @@ -96,7 +104,7 @@ class ChatBox extends LitElement { } .chat-message { - display: block; + display: flex; padding-left: 4px; background-color: rgba(0, 0, 0, 0.5); } @@ -114,40 +122,51 @@ class ChatBox extends LitElement { transition: none !important; } - .chat-message-chat-opened { + .chat.opened .chat-message { opacity: 1 !important; transition: none !important; } + + .chat-message-part { + white-space: pre-wrap; + } ` } - render () { - return html` -
-
-
  • Welcome to prismarine-web-client! Chat appears here.
  • -
    -
    -
    -
    - -
    -
    - ` + static get properties () { + return { + messages: { + type: Array + } + } } constructor () { super() this.chatHistoryPos = 0 this.chatHistory = [] + this.messagesLimit = 200 + /** @type {Message[]} */ + this.messages = [{ + parts: [ + { + text: 'Welcome to prismarine-web-client! Chat appears here.', + } + ], + id: 0, + fading: true, + faded: true, + }] } - enableChat (isCommand) { - const chat = this.shadowRoot.querySelector('#chat') - const chatInput = this.shadowRoot.querySelector('#chatinput') + enableChat (initialText = '') { + const chat = this.shadowRoot.getElementById('chat-messages') + /** @type {HTMLInputElement} */ + // @ts-ignore + const chatInput = this.shadowRoot.getElementById('chatinput') - this.shadowRoot.querySelector('#chat-wrapper2').classList.toggle('input-mobile', isMobile()) - this.shadowRoot.querySelector('#chat-wrapper').classList.toggle('display-mobile', isMobile()) + this.shadowRoot.getElementById('chat-wrapper2').classList.toggle('input-mobile', isMobile()) + this.shadowRoot.getElementById('chat-wrapper').classList.toggle('display-mobile', isMobile()) activeModalStack.push(this) @@ -158,13 +177,13 @@ class ChatBox extends LitElement { // Show extended chat history chat.style.maxHeight = 'var(--chatHeight)' chat.scrollTop = chat.scrollHeight // Stay bottom of the list - if (isCommand) { // handle commands - chatInput.value = '/' - } + // handle / and other snippets + chatInput.value = initialText // Focus element chatInput.focus() this.chatHistoryPos = this.chatHistory.length - document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened')) + // to show + this.requestUpdate() } get inChat () { @@ -175,8 +194,10 @@ class ChatBox extends LitElement { * @param {import('minecraft-protocol').Client} client */ init (client) { - const chat = this.shadowRoot.querySelector('#chat') - const chatInput = this.shadowRoot.querySelector('#chatinput') + const chat = this.shadowRoot.getElementById('chat-messages') + /** @type {HTMLInputElement} */ + // @ts-ignore + const chatInput = this.shadowRoot.getElementById('chatinput') // Show chat chat.style.display = 'block' @@ -184,7 +205,6 @@ class ChatBox extends LitElement { // Chat events document.addEventListener('keydown', e => { if (activeModalStack.slice(-1)[0] !== this) return - e = e || window.event if (e.code === 'ArrowUp') { if (this.chatHistoryPos === 0) return chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : '' @@ -199,16 +219,15 @@ class ChatBox extends LitElement { const keyBindScrn = document.getElementById('keybinds-screen') document.addEventListener('keypress', e => { - e = e || window.event if (!this.inChat && activeModalStack.length === 0) { keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { switch (km.defaultKey) { case 'KeyT': - setTimeout(() => this.enableChat(false), 0) + setTimeout(() => this.enableChat(), 0) break case 'Slash': - setTimeout(() => this.enableChat(true), 0) + setTimeout(() => this.enableChat('/'), 0) break } } @@ -238,21 +257,18 @@ class ChatBox extends LitElement { // Hide extended chat history chat.style.maxHeight = 'var(--chatHeight)' chat.scrollTop = chat.scrollHeight // Stay bottom of the list - document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened')) + this.requestUpdate() return 'custom' // custom hide } this.hide() client.on('chat', (packet) => { - // Reading of chat message + // Handle new message const fullmessage = JSON.parse(packet.message.toString()) + /** @type {MessagePart[]} */ const msglist = [] - const colorF = (color) => { - return color.trim().startsWith('#') ? `color:${color}` : styles[color] ?? undefined - } - - const readMsg = (msglist, msg) => { + const readMsg = (msg) => { const styles = { color: msg.color, bold: !!msg.bold, @@ -264,30 +280,29 @@ class ChatBox extends LitElement { if (msg.text) { msglist.push({ + ...msg, text: msg.text, ...styles }) - } - - if (msg.translate) { + } else if (msg.translate) { const tText = window.mcData.language[msg.translate] ?? msg.translate if (msg.with) { - const splited = tText.split(/%s|%\d+\$s/g) + const splitted = tText.split(/%s|%\d+\$s/g) let i = 0 - splited.forEach((spl, j, arr) => { - msglist.push({ text: spl, ...styles }) + splitted.forEach((part, j) => { + msglist.push({ text: part, ...styles }) - if (j + 1 < arr.length) { + if (j + 1 < splitted.length) { if (msg.with[i]) { if (typeof msg.with[i] === 'string') { - readMsg(msglist, { + readMsg({ ...styles, text: msg.with[i] }) } else { - readMsg(msglist, { + readMsg({ ...styles, ...msg.with[i] }) @@ -298,6 +313,7 @@ class ChatBox extends LitElement { }) } else { msglist.push({ + ...msg, text: tText, ...styles }) @@ -306,40 +322,95 @@ class ChatBox extends LitElement { if (msg.extra) { msg.extra.forEach(ex => { - readMsg(msglist, { ...styles, ...ex }) + readMsg({ ...styles, ...ex }) }) } } - readMsg(msglist, fullmessage) - - const li = document.createElement('li') - msglist.forEach(msg => { - const span = document.createElement('span') - span.appendChild(document.createTextNode(msg.text)) - span.setAttribute( - 'style', - `${msg.color ? colorF(msg.color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(msg.color.toLowerCase()).replace('color:', ''))}` : styles.white}; ${msg.bold ? styles.bold + ';' : '' - }${msg.italic ? styles.italic + ';' : ''}${msg.strikethrough ? styles.strikethrough + ';' : '' - }${msg.underlined ? styles.underlined + ';' : ''}` - ) - li.appendChild(span) - }) - chat.appendChild(li) + readMsg(fullmessage) + + const lastId = this.messages.at(-1)?.id ?? 0 + this.messages = [...this.messages.slice(-this.messagesLimit), { + parts: msglist, + id: lastId + 1, + fading: false, + faded: false + }] + const message = this.messages.at(-1) + chat.scrollTop = chat.scrollHeight // Stay bottom of the list // fading - li.classList.add('chat-message') - if (this.inChat) { - li.classList.add('chat-message-chat-opened') - } setTimeout(() => { - li.classList.add('chat-message-fadeout') - li.classList.add('chat-message-fade') + message.fading = true + this.requestUpdate() setTimeout(() => { - li.classList.add('chat-message-faded') + message.faded = true + this.requestUpdate() }, 3000) }, 5000) }) + // todo support hover content below, {action: 'show_text', contents: {text}}, and some other types + // todo remove + window.dummyMessage = () => { + client.emit('chat', { + message: "{\"color\":\"yellow\",\"translate\":\"multiplayer.player.joined\",\"with\":[{\"insertion\":\"pviewer672\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"/tell pviewer672 \"},\"hoverEvent\":{\"action\":\"show_entity\",\"contents\":{\"type\":\"minecraft:player\",\"id\":\"ecd0eeb1-625e-3fea-b16e-cb449dcfa434\",\"name\":{\"text\":\"pviewer672\"}}},\"text\":\"pviewer672\"}]}", + position: 1, + sender: "00000000-0000-0000-0000-000000000000", + }) + } + // window.dummyMessage() + } + + renderMessagePart (/** @type {MessagePart} */{ bold, color, italic, strikethrough, text, underlined }) { + const colorF = (color) => { + return color.trim().startsWith('#') ? `color:${color}` : styles[color] ?? undefined + } + + /** @type {string[]} */ + const applyStyles = [ + color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : styles.white, + italic && styles.italic, + bold && styles.bold, + italic && styles.italic, + underlined && styles.underlined, + strikethrough && styles.strikethrough + ].filter(Boolean) + + return html` + ${text}` + } + + renderMessage (/** @type {Message} */message) { + const classes = { + 'chat-message-fadeout': message.fading, + 'chat-message-fade': message.fading, + 'chat-message-faded': message.faded, + 'chat-message': true + } + + return html` +
  • + ${message.parts.map(msg => this.renderMessagePart(msg))} +
  • + ` + } + + render () { + return html` +
    +
    + ${repeat(this.messages, (m) => m.id, (m) => this.renderMessage(m))} +
    +
    +
    +
    + +
    +
    + ` } } diff --git a/lib/globalState.js b/lib/globalState.js index afcf67d74..8cfa4b732 100644 --- a/lib/globalState.js +++ b/lib/globalState.js @@ -1,11 +1,13 @@ //@ts-check +import { proxy } from 'valtio' import { pointerLock } from './utils' // todo: refactor structure with support of hideNext=false /** * @typedef {(HTMLElement & Record)} Modal + * @typedef {{callback, label}} ContextMenuItem */ /** @type {Modal[]} */ @@ -74,3 +76,15 @@ export const hideCurrentModal = (_data = undefined, preActions = undefined) => { } } } + +// --- + +export const currentContextMenu = proxy({ items: /** @type {ContextMenuItem[] | null} */[], x: 0, y: 0, }) + +export const showContextmenu = (/** @type {ContextMenuItem[]} */items, { clientX, clientY }) => { + Object.assign(currentContextMenu, { + items, + x: clientX, + y: clientY, + }) +} diff --git a/lib/menus/hud.js b/lib/menus/hud.js index 446a07c41..3c896c5ad 100644 --- a/lib/menus/hud.js +++ b/lib/menus/hud.js @@ -305,7 +305,7 @@ class Hud extends LitElement {
    - +
    -
    + diff --git a/src/reactUi.jsx b/src/reactUi.jsx new file mode 100644 index 000000000..8ac6a470c --- /dev/null +++ b/src/reactUi.jsx @@ -0,0 +1,62 @@ +//@ts-check +import { renderToDom } from '@zardoy/react-util' + +import React, { useEffect } from 'react' +import { LeftTouchArea, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface' +import { css } from '@emotion/css' + +// todo +useInterfaceState.setState({ + isFlying: false, + updateCoord: ([coord, state]) => { + const coordToAction = [ + ['z', -1, 'forward'], + ['z', 1, 'back'], + ['x', -1, 'left'], + ['x', 1, 'right'], + ['y', 1, 'jump'], + ] + // todo refactor + const actionAndState = state !== 0 ? coordToAction.find(([axis, value]) => axis === coord && value === state) : coordToAction.filter(([axis]) => axis === coord) + console.log(actionAndState) + if (!bot) return + if (state === 0) { + for (const action of actionAndState) { + //@ts-ignore + bot.setControlState(action[2], false) + } + } else { + //@ts-ignore + bot.setControlState(actionAndState[2], true) + } + } +}) + +const App = () => { + const usingTouch = useUsingTouch() + + if (!usingTouch) return null + return ( +
    console.log('test')} + className={css` + position: fixed; + inset: 0; + height: 100%; + display: flex; + width: 100%; + justify-content: space-between; + align-items: flex-end; + `} + > + +
    + +
    + ) +} + +renderToDom(, { + strictMode: false, + selector: '#react-root', +}) diff --git a/tsconfig.json b/tsconfig.json index fca3dc30c..0977d9667 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "moduleResolution": "Node", "module": "CommonJS", "allowJs": true, + "jsx": "react-jsx", "allowSyntheticDefaultImports": true, "noEmit": true, "strictFunctionTypes": true diff --git a/webpack.common.js b/webpack.common.js index 012e9567d..f114c47af 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -59,7 +59,7 @@ const config = { module: { rules: [ { - test: /\.tsx?$/, + test: /\.(tsx?)|(jsx)$/, loader: 'esbuild-loader', // options: { // // JavaScript version to compile to From fe149990dbdf838638dc7165e7608a4fe6ebd630 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 21 Aug 2023 21:00:37 +0300 Subject: [PATCH 082/318] fix: notification didn't allow to focus chat --- src/menus/notification.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/menus/notification.js b/src/menus/notification.js index dd149534e..27700cb3b 100644 --- a/src/menus/notification.js +++ b/src/menus/notification.js @@ -20,21 +20,39 @@ export const showNotification = (/** @type {typeof notification} */newNotificati window.notification = notification class Notification extends LitElement { + static get properties () { + return { + renderHtml: { type: Boolean }, + } + } + render () { + if (!this.renderHtml) return const show = notification.show && notification.message return html` -
    +
    ${notification.message}
    ` } + ontransitionend = (event) => { + if (event.propertyName !== 'opacity') return + + if (!notification.show) { + this.renderHtml = false + } + } + constructor () { super() + this.renderHtml = false let timeout subscribe(notification, () => { if (timeout) clearTimeout(timeout) this.requestUpdate() + if (!notification.show) return + this.renderHtml = true if (!notification.autoHide) return timeout = setTimeout(() => { notification.show = false From 331f3bae627879a4503f0d35a4fe80a1f993e692 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 21 Aug 2023 21:01:09 +0300 Subject: [PATCH 083/318] cleanup package.json (if this will be published some day to npm, they will be auto-generated anyway) --- package.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/package.json b/package.json index d03a25f32..9dd6fa117 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "prismarine-web-client", "version": "1.5.0", "description": "A minecraft client running in a browser", - "main": "index.js", "scripts": { "start": "NODE_OPTIONS=--max-old-space-size=8192 run-p watch prod-start", "build": "NODE_OPTIONS=--max-old-space-size=8192 rimraf public && node scripts/build.js copyFiles && webpack --config webpack.prod.js --progress", @@ -14,10 +13,6 @@ "prepublishOnly": "npm run build", "test": "npm run lint && mocha" }, - "repository": { - "type": "git", - "url": "git+https://github.com/PrismarineJS/prismarine-web-client.git" - }, "keywords": [ "prismarine", "web", @@ -25,13 +20,6 @@ ], "author": "PrismarineJS", "license": "MIT", - "bin": { - "prismarine-web-client": "./server.js" - }, - "bugs": { - "url": "https://github.com/PrismarineJS/prismarine-web-client/issues" - }, - "homepage": "https://github.com/PrismarineJS/prismarine-web-client#readme", "dependencies": { "@emotion/css": "^11.11.2", "@types/react": "^18.2.20", From 4537626b1d84a51f5967bcc1221e9eb8af056b92 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 21 Aug 2023 22:04:53 +0300 Subject: [PATCH 084/318] add autocomplete for versions & supported indicator (wip) --- src/index.js | 6 +++--- src/menus/components/edit_box.js | 22 +++++++++++++++++++++- src/menus/loading_or_error_screen.js | 2 +- src/menus/play_screen.js | 10 ++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 02bf91bfe..b02180f13 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,9 @@ /* global THREE */ require('./chat') +// workaround for mineflayer +process.versions.node = '14.0.0' + require('./menus/components/button') require('./menus/components/edit_box') require('./menus/components/slider') @@ -64,9 +67,6 @@ _fs.promises.open = async (...args) => { const net = require('net') const Stats = require('stats.js') -// workaround for mineflayer -process.versions.node = '14.0.0' - const mineflayer = require('mineflayer') const { WorldView, Viewer, MapControls } = require('prismarine-viewer/viewer') const PrismarineWorld = require('prismarine-world') diff --git a/src/menus/components/edit_box.js b/src/menus/components/edit_box.js index aafedeb64..0f42527a6 100644 --- a/src/menus/components/edit_box.js +++ b/src/menus/components/edit_box.js @@ -11,6 +11,22 @@ class EditBox extends LitElement { background: black; border: 1px solid grey; } + .edit-container.invalid { + border: 1px solid #c70000; + } + + .edit-container.warning { + border: 1px solid rgb(159, 151, 0); + } + + .edit-container.invalid:hover, + .edit-container.invalid:focus-within { + border-color: red; + } + .edit-container.warning:hover, + .edit-container.warning:focus-within { + border-color: yellow; + } .edit-container:hover, .edit-container:focus-within { @@ -97,6 +113,10 @@ class EditBox extends LitElement { required: { type: Boolean, attribute: 'pmui-required' + }, + state: { + type: String, + attribute: true } } } @@ -104,7 +124,7 @@ class EditBox extends LitElement { render () { return html`
    diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index afbf19903..dfbf36609 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -40,7 +40,7 @@ class LoadingErrorScreen extends LitElement { constructor () { super() this.hasError = false - this.maybeRecoverable = false + this.maybeRecoverable = true this.status = 'Waiting for JS load' this._loadingDots = '' } diff --git a/src/menus/play_screen.js b/src/menus/play_screen.js index 40c5c49b9..81e7d53f8 100644 --- a/src/menus/play_screen.js +++ b/src/menus/play_screen.js @@ -1,6 +1,12 @@ const { LitElement, html, css } = require('lit') const { commonCss } = require('./components/common') const { hideCurrentModal } = require('../globalState') +const mcAssets = require("minecraft-assets") +const data = require('minecraft-data') +const mineflayer = require('mineflayer') + +const fullySupporedVersions = mcAssets.versions +const partialSupportVersions = mineflayer.supportedVersions class PlayScreen extends LitElement { static get styles () { @@ -76,6 +82,8 @@ class PlayScreen extends LitElement { constructor () { super() + this.version = '' + // todo set them sooner add indicator window.fetch('config.json').then(res => res.json()).then(c => c, (error) => { console.error('Failed to load config.json', error) return {} @@ -167,6 +175,8 @@ class PlayScreen extends LitElement { pmui-id="botversion" pmui-value="${this.version}" pmui-inputmode="decimal" + state="${this.version && (fullySupporedVersions.includes(this.version) ? '' : /* TODO improve check: check exact including all */ partialSupportVersions.some(v => this.version.startsWith(v)) ? 'warning' : 'invalid')}" + .autocompleteValues=${mcAssets.versions} @input=${e => { this.version = e.target.value }} >
    From 8d3d5caee9dce82f082ed45192bdb9f50e3003b4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 21 Aug 2023 22:06:38 +0300 Subject: [PATCH 085/318] [pnpm] enable shell-emulator for new contributors on windows --- .npmrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmrc b/.npmrc index 9a8fccec1..3a58a3dbb 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,4 @@ package-lock=false public-hoist-pattern=* ignore-workspace-root-check=true +shell-emulator=true From 2a0533c9e5a10bbb25f8cb4ba2235b23fbf768e8 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 22 Aug 2023 04:01:22 +0300 Subject: [PATCH 086/318] simple fov change on sprint (so sprint can be indicated) --- src/index.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index b02180f13..ea7b85634 100644 --- a/src/index.js +++ b/src/index.js @@ -450,10 +450,18 @@ async function connect (options) { const worldView = new WorldView(bot.world, viewDistance, center) - optionsScrn.addEventListener('fov_changed', (e) => { - viewer.camera.fov = e.detail.fov + let fovSetting = optionsScrn.fov + const updateFov = () => { + fovSetting = optionsScrn.fov + if (bot.controlState.sprint && !bot.controlState.sneak) { + // todo check value and add transition + fovSetting *= 5 + } + viewer.camera.fov = fovSetting viewer.camera.updateProjectionMatrix() - }) + } + updateFov() + optionsScrn.addEventListener('fov_changed', updateFov) viewer.setVersion(version) @@ -590,6 +598,7 @@ async function connect (options) { break case 'ControlLeft': bot.setControlState('sprint', true) + updateFov() break case 'ShiftLeft': bot.setControlState('sneak', true) @@ -620,6 +629,7 @@ async function connect (options) { switch (km.defaultKey) { case 'ControlLeft': bot.setControlState('sprint', false) + updateFov() break case 'ShiftLeft': bot.setControlState('sneak', false) From aeeb0124d3975071c84ba1560dc631df9c018932 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 22 Aug 2023 05:01:24 +0300 Subject: [PATCH 087/318] refactor modal stacks so they are: 1. watchable for changes 2. can be used along with react --- src/chat.js | 6 +++--- src/globalState.js | 34 ++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/chat.js b/src/chat.js index f60c883a2..f4834c0d9 100644 --- a/src/chat.js +++ b/src/chat.js @@ -1,7 +1,7 @@ //@ts-check const { LitElement, html, css } = require('lit') const { isMobile } = require('./menus/components/common') -const { activeModalStack, hideCurrentModal } = require('./globalState') +const { activeModalStack, hideCurrentModal, showModal } = require('./globalState') import { repeat } from 'lit/directives/repeat.js' import { classMap } from 'lit/directives/class-map.js' @@ -168,7 +168,7 @@ class ChatBox extends LitElement { this.shadowRoot.getElementById('chat-wrapper2').classList.toggle('input-mobile', isMobile()) this.shadowRoot.getElementById('chat-wrapper').classList.toggle('display-mobile', isMobile()) - activeModalStack.push(this) + showModal(this) // Exit the pointer lock document.exitPointerLock() @@ -187,7 +187,7 @@ class ChatBox extends LitElement { } get inChat () { - return activeModalStack.includes(this) + return activeModalStack.find(m => m.elem === this) !== undefined } /** diff --git a/src/globalState.js b/src/globalState.js index ca6f360ca..e6b0be15e 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -1,17 +1,17 @@ //@ts-check -import { proxy } from 'valtio' +import { proxy, ref } from 'valtio' import { pointerLock } from './utils' // todo: refactor structure with support of hideNext=false /** - * @typedef {(HTMLElement & Record)} Modal + * @typedef {({elem?: HTMLElement & Record} & {reactType?: string})} Modal * @typedef {{callback, label}} ContextMenuItem */ /** @type {Modal[]} */ -export const activeModalStack = [] +export const activeModalStack = proxy([]) export const replaceActiveModalStack = (name, newModalStack = activeModalStacks[name]) => { hideModal(undefined, undefined, { restorePrevious: false, force: true, }) @@ -26,18 +26,28 @@ window.activeModalStack = activeModalStack export const customDisplayManageKeyword = 'custom' +const defaultModalActions = { + show (/** @type {Modal} */modal) { + if (modal.elem) modal.elem.style.display = 'block' + }, + hide (/** @type {Modal} */modal) { + if (modal.elem) modal.elem.style.display = 'none' + } +} + const showModalInner = (/** @type {Modal} */ modal) => { - const cancel = modal.show?.() + const cancel = modal.elem?.show?.() if (cancel && cancel !== customDisplayManageKeyword) return false - if (cancel !== 'custom') modal.style.display = 'block' + if (cancel !== 'custom') defaultModalActions.show(modal) return true } -export const showModal = (/** @type {Modal} */ modal) => { +export const showModal = (/** @type {HTMLElement & Record | {reactType: string}} */ elem) => { + const resolved = elem instanceof HTMLElement ? { elem: ref(elem) } : elem const curModal = activeModalStack.slice(-1)[0] - if (modal === curModal || !showModalInner(modal)) return - if (curModal) curModal.style.display = 'none' - activeModalStack.push(modal) + if (elem === curModal?.elem || !showModalInner(resolved)) return + if (curModal) defaultModalActions.hide(curModal) + activeModalStack.push(resolved) } /** @@ -49,13 +59,13 @@ export const showModal = (/** @type {Modal} */ modal) => { export const hideModal = (modal = activeModalStack.slice(-1)[0], data = undefined, options = {}) => { const { force = false, restorePrevious = true } = options if (!modal) return - let cancel = modal.hide?.(data) + let cancel = modal.elem?.hide?.(data) if (force && cancel !== customDisplayManageKeyword) { cancel = undefined } if (!cancel || cancel === customDisplayManageKeyword) { - if (cancel !== customDisplayManageKeyword) modal.style.display = 'none' + if (cancel !== customDisplayManageKeyword) defaultModalActions.hide(modal) activeModalStack.pop() const newModal = activeModalStack.slice(-1)[0] if (newModal && restorePrevious) { @@ -91,7 +101,7 @@ export const showContextmenu = (/** @type {ContextMenuItem[]} */items, { clientX // --- -export const isGameActive = (foregroundCheck = false) => { +export const isGameActive = (foregroundCheck) => { if (foregroundCheck && activeModalStack.length) return false return document.getElementById('hud').style.display !== 'none' } From edf1def2e1abb352dd3cb646495d9bf80a7290bf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 22 Aug 2023 21:13:26 +0300 Subject: [PATCH 088/318] feat: display leave confirmation when playing (prevent accidental ctrl+w), but not when paused so can easily close if desired --- src/globalState.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/globalState.js b/src/globalState.js index e6b0be15e..ce881f51d 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -1,6 +1,6 @@ //@ts-check -import { proxy, ref } from 'valtio' +import { proxy, ref, subscribe } from 'valtio' import { pointerLock } from './utils' // todo: refactor structure with support of hideNext=false @@ -110,4 +110,15 @@ export const miscUiState = proxy({ currentTouch: null }) +window.addEventListener('beforeunload', (event) => { + // todo-low maybe exclude chat? + if (!isGameActive(true) && activeModalStack.at(-1)?.elem.id !== 'chat') return + // For major browsers doning only this is enough + event.preventDefault() + + // Display a confirmation prompt + event.returnValue = '' // Required for some browsers + return 'The game is running. Are you sure you want to close this page?' +}); + window.miscUiState = miscUiState From bb262086c7cfd2a3fc634e07baa0a32fcdb6974d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 22 Aug 2023 05:03:36 +0300 Subject: [PATCH 089/318] inventories are almost here wip: add mobile button review upstream wip react refresh (thats why migrating to react) to migrate to tsx to impl advanced settings --- package.json | 9 +++- src/index.js | 10 ++++- src/menus/keybinds_screen.js | 4 +- src/reactUi.jsx | 80 ++++++++++++++++++++++++++++++++++-- webpack.dev.js | 11 +++-- 5 files changed, 102 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 9dd6fa117..7a7889082 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "author": "PrismarineJS", "license": "MIT", "dependencies": { + "@dimaka/interface": "0.0.0-dev", "@emotion/css": "^11.11.2", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@zardoy/react-util": "^0.2.0", @@ -38,9 +40,9 @@ "net-browserify": "github:PrismarineJS/net-browserify", "prismarine-world": "^3.6.2", "querystring": "^0.2.1", - "@dimaka/interface": "0.0.0-dev", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-refresh": "^0.14.0", "space-squid": "github:zardoy/space-squid", "speed-measure-webpack-plugin": "^1.5.0", "stats.js": "^0.17.0", @@ -77,6 +79,7 @@ "stream-browserify": "^3.0.0", "three": "0.128.0", "timers-browserify": "^2.0.12", + "use-typed-event-listener": "^4.0.2", "vite": "^4.4.9", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", @@ -88,7 +91,9 @@ "pnpm": { "overrides": { "minecraft-data": "latest", - "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client" + "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client", + "space-squid": "file:../space-squid", + "@dimaka/interface": "file:../interface" } } } diff --git a/src/index.js b/src/index.js index ea7b85634..53c10e3de 100644 --- a/src/index.js +++ b/src/index.js @@ -593,6 +593,12 @@ async function connect (options) { keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { switch (km.defaultKey) { + case 'KeyE': + showModal({ reactType: 'inventory', }) + // todo seems to be workaround + // avoid calling inner keybinding listener, but should be handled there + e.stopImmediatePropagation() + break case 'KeyQ': if (bot.heldItem) bot.tossStack(bot.heldItem) break @@ -621,7 +627,9 @@ async function connect (options) { } } }) - }, false) + }, { + capture: true, + }) registerListener(document, 'keyup', (e) => { keyBindScrn.keymaps.forEach(km => { diff --git a/src/menus/keybinds_screen.js b/src/menus/keybinds_screen.js index f16a4bbd3..5cdb37640 100644 --- a/src/menus/keybinds_screen.js +++ b/src/menus/keybinds_screen.js @@ -104,7 +104,7 @@ class KeyBindsScreen extends LitElement { { defaultKey: 'Slash', key: 'Slash', name: 'Open Command' }, // { defaultKey: '0', key: '0', name: 'Attack/Destroy' }, // { defaultKey: '1', key: '1', name: 'Place Block' }, - { defaultKey: 'KeyQ', key: 'KeyQ', name: 'Drop Item' } + { defaultKey: 'KeyQ', key: 'KeyQ', name: 'Drop Item' }, // { defaultKey: 'Digit1', key: 'Digit1', name: 'Hotbar Slot 1' }, // { defaultKey: 'Digit2', key: 'Digit2', name: 'Hotbar Slot 2' }, // { defaultKey: 'Digit3', key: 'Digit3', name: 'Hotbar Slot 3' }, @@ -114,7 +114,7 @@ class KeyBindsScreen extends LitElement { // { defaultKey: 'Digit7', key: 'Digit7', name: 'Hotbar Slot 7' }, // { defaultKey: 'Digit8', key: 'Digit8', name: 'Hotbar Slot 8' }, // { defaultKey: 'Digit9', key: 'Digit9', name: 'Hotbar Slot 9' }, - // { defaultKey: 'KeyE', key: 'KeyE', name: 'Open Inventory' } + { defaultKey: 'KeyE', key: 'KeyE', name: 'Open Inventory' }, ] document.addEventListener('keydown', (e) => { diff --git a/src/reactUi.jsx b/src/reactUi.jsx index 8ac6a470c..3bcf2f40c 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -1,9 +1,12 @@ //@ts-check import { renderToDom } from '@zardoy/react-util' -import React, { useEffect } from 'react' -import { LeftTouchArea, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface' +import { LeftTouchArea, InventoryNew, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface' import { css } from '@emotion/css' +import { activeModalStack, hideCurrentModal, isGameActive } from './globalState' +import { useEffect, useState } from 'react' +import { useProxy } from 'valtio/utils' +import useTypedEventListener from 'use-typed-event-listener' // todo useInterfaceState.setState({ @@ -32,7 +35,8 @@ useInterfaceState.setState({ } }) -const App = () => { +const TouchControls = () => { + // todo setting const usingTouch = useUsingTouch() if (!usingTouch) return null @@ -56,6 +60,76 @@ const App = () => { ) } +const useActivateModal = (/** @type {string} */search, onlyLast = true) => { + const stack = useProxy(activeModalStack) + + return onlyLast ? stack.at(-1)?.reactType === search : stack.some((modal) => modal.reactType === search) +} + +function useIsBotAvailable() { + const stack = useProxy(activeModalStack) + + return isGameActive(false) +} + +function InventoryWrapper() { + const isInventoryOpen = useActivateModal('inventory', false) + const [slots, setSlots] = useState(bot.inventory.slots) + + useEffect(() => { + if (isInventoryOpen) { + document.exitPointerLock() + } + }, [isInventoryOpen]) + + useTypedEventListener(document, 'keydown', (e) => { + // todo use refactored keymap + if (e.code === 'KeyE' && activeModalStack.at(-1)?.reactType === 'inventory') { + hideCurrentModal() + } + }) + + useEffect(() => { + bot.inventory.on('updateSlot', () => { + setSlots(bot.inventory.slots) + }) + // todo need to think of better solution + window['mcData'] = require('minecraft-data')(bot.version) + window['mcAssets'] = require('minecraft-assets')(bot.version) + }, []) + + console.log('isInventoryOpen', isInventoryOpen) + if (!isInventoryOpen) return null + + return
    div { + scale: 0.6; + background: transparent !important; + } + `}> + { + bot.moveSlotItem(oldSlot, newSlotIndex) + // bot.inventory.selectedItem + // bot.inventory.updateSlot(oldSlot, ) + } } /> +
    +} + +const App = () => { + const isBotAvailable = useIsBotAvailable() + if (!isBotAvailable) return null + + return
    + + +
    +} + renderToDom(, { strictMode: false, selector: '#react-root', diff --git a/webpack.dev.js b/webpack.dev.js index 36138e299..4ec0913b7 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -1,6 +1,6 @@ const { merge } = require('webpack-merge') const common = require('./webpack.common.js') -const path = require('path') +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin') /** @type {import('webpack-dev-server').Configuration['rel']} */ module.exports = merge(common, @@ -17,8 +17,8 @@ module.exports = merge(common, compress: true, // inline: true, // open: true, - // hot: true, - liveReload: true, + hot: true, + // liveReload: true, devMiddleware: { writeToDisk: true, }, @@ -44,5 +44,8 @@ module.exports = merge(common, } } } - } + }, + plugins: [ + new ReactRefreshWebpackPlugin() + ], }) From 7b08d17f079a699557e21575076c300afec8d592 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 22 Aug 2023 05:05:50 +0300 Subject: [PATCH 090/318] rm log --- src/reactUi.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reactUi.jsx b/src/reactUi.jsx index 3bcf2f40c..d261c0c5e 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -98,7 +98,6 @@ function InventoryWrapper() { window['mcAssets'] = require('minecraft-assets')(bot.version) }, []) - console.log('isInventoryOpen', isInventoryOpen) if (!isInventoryOpen) return null return
    Date: Sat, 26 Aug 2023 06:09:49 +0300 Subject: [PATCH 091/318] add ci.yml --- .github/workflows/ci.yml | 68 +++++++--------------------------------- 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c46e06266..cc357e5a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,61 +1,17 @@ -name: CI - +name: Deploy to GitHub pages on: push: - branches: [ master ] - pull_request: - branches: [ master ] - + branches: [main] jobs: - build: - + build-and-deploy: runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x] - + permissions: write-all steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run build - - run: npm test - DeployPages: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' }} - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. - with: - persist-credentials: false - fetch-depth: 0 - - name: Edit config - run: | - sed -i -E 's/^ "defaultProxy": ""/ "defaultProxy": "pproxy.rom1504.fr"/g' config.json - sed -i -E 's/^ "defaultProxyPort": 0/ "defaultProxyPort": 443/g' config.json - - name: Build - run: | - npm install - npm run build - cp -R public/ ../ - rm -Rf ./* - git checkout gh-pages - rm -Rf ./* - rm -Rf .github .gitignore .gitpod .gitpod.DockerFile .npmignore .npmrc - cp -R ../public/* ./ - - name: Create commits - run: | - git config user.name 'rom1504bot' - git config user.email 'rom1504bot@users.noreply.github.com' - git add --all - git commit --amend -m "Update gh-pages" - - name: Deploy 🚀 - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: gh-pages - force: true + - name: Checkout repository + uses: actions/checkout@master + - name: Install pnpm + run: npm i -g pnpm + - run: pnpm install + - run: pnpm build + # todo use nohup and official action? + # - run: pnpm prod-start & pnpm test:cypress From 714bf3d55b3751ee7b51d3e0dac5e40cef9b97ff Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 02:52:24 +0300 Subject: [PATCH 092/318] make render distance updatable, refactor controls spend some time on adding flying add settings like the one to disable in-game auto-update --- src/botControls.js | 166 ++++++++++++++++++++++++++++++++++++ src/globalState.js | 25 +++++- src/index.js | 138 ++++++++++-------------------- src/menus/options_screen.js | 18 ++-- src/optionsStorage.js | 16 +++- 5 files changed, 260 insertions(+), 103 deletions(-) create mode 100644 src/botControls.js diff --git a/src/botControls.js b/src/botControls.js new file mode 100644 index 000000000..039795b41 --- /dev/null +++ b/src/botControls.js @@ -0,0 +1,166 @@ +//@ts-check + +const { Vec3 } = require('vec3') +const { isGameActive, showModal, gameAdditionalState } = require('./globalState') + +const keyBindScrn = document.getElementById('keybinds-screen') + +// these controls are for gamemode 3 actually + +const makeInterval = (fn, interval) => { + const intervalId = setInterval(fn, interval) + + const cleanup = () => { + clearInterval(intervalId) + cleanup.active = false + } + cleanup.active = true + return cleanup +} + +const flySpeedMult = 0.5 + +const isFlying = () => bot.physics.gravity === 0 +/** @type {ReturnType|undefined} */ +let endFlyLoop + +const currentFlyVector = new Vec3(0, 0, 0) +window.currentFlyVector = currentFlyVector + +const startFlyLoop = () => { + if (!isFlying()) return + endFlyLoop?.() + + endFlyLoop = makeInterval(() => { + if (!window.bot) endFlyLoop() + bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(flySpeedMult, flySpeedMult, flySpeedMult))) + }, 50) +} + +// todo we will get rid of patching it when refactor controls +let originalSetControlState +const patchedSetControlState = (action, state) => { + if (!isFlying()) { + return originalSetControlState(action, state) + } + + const actionPerFlyVector = { + jump: new Vec3(0, 1, 0), + sneak: new Vec3(0, -1, 0), + } + + const changeVec = actionPerFlyVector[action] + if (!changeVec) { + return originalSetControlState(action, state) + } + const toAddVec = changeVec.scaled(state ? 1 : -1) + for (const coord of ['x', 'y', 'z']) { + if (toAddVec[coord] === 0) continue + if (currentFlyVector[coord] === toAddVec[coord]) return + } + currentFlyVector.add(toAddVec) +} + +const toggleFly = () => { + if (bot.game.gameMode !== 'creative') return + if (bot.setControlState !== patchedSetControlState) { + originalSetControlState = bot.setControlState + bot.setControlState = patchedSetControlState + } + + if (isFlying()) { + bot.creative.stopFlying() + endFlyLoop?.() + } else { + bot.entity.velocity = new Vec3(0, 0, 0) + bot.creative.startFlying() + startFlyLoop() + } + gameAdditionalState.isFlying = isFlying() +} + +let lastJumpUsage = 0 +document.addEventListener('keydown', (e) => { + if (!isGameActive(true)) return + + keyBindScrn.keymaps.forEach(km => { + if (e.code === km.key) { + switch (km.defaultKey) { + case 'KeyE': + showModal({ reactType: 'inventory', }) + // todo seems to be workaround + // avoid calling inner keybinding listener, but should be handled there + e.stopImmediatePropagation() + break + case 'KeyQ': + if (bot.heldItem) bot.tossStack(bot.heldItem) + break + case 'ControlLeft': + bot.setControlState('sprint', true) + gameAdditionalState.isSprinting = true + break + case 'ShiftLeft': + bot.setControlState('sneak', true) + break + case 'Space': + bot.setControlState('jump', true) + break + case 'KeyD': + bot.setControlState('right', true) + break + case 'KeyA': + bot.setControlState('left', true) + break + case 'KeyS': + bot.setControlState('back', true) + break + case 'KeyW': + bot.setControlState('forward', true) + break + } + } + }) + + e.preventDefault() +}, { + capture: true, +}) + +document.addEventListener('keyup', (e) => { + if (!isGameActive(true)) return + + keyBindScrn.keymaps.forEach(km => { + if (e.code === km.key) { + switch (km.defaultKey) { + case 'ControlLeft': + bot.setControlState('sprint', false) + gameAdditionalState.isSprinting = false + break + case 'ShiftLeft': + bot.setControlState('sneak', false) + break + case 'Space': + const toggleFlyAction = Date.now() - lastJumpUsage < 500 + if (toggleFlyAction) { + toggleFly() + } + lastJumpUsage = Date.now() + + bot.setControlState('jump', false) + break + case 'KeyD': + bot.setControlState('right', false) + break + case 'KeyA': + bot.setControlState('left', false) + break + case 'KeyS': + bot.setControlState('back', false) + break + case 'KeyW': + bot.setControlState('forward', false) + break + } + } + }) +}, false) diff --git a/src/globalState.js b/src/globalState.js index ce881f51d..e35e39fad 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -2,6 +2,7 @@ import { proxy, ref, subscribe } from 'valtio' import { pointerLock } from './utils' +import { options } from './optionsStorage' // todo: refactor structure with support of hideNext=false @@ -107,12 +108,34 @@ export const isGameActive = (foregroundCheck) => { } export const miscUiState = proxy({ - currentTouch: null + currentTouch: null, + singleplayer: false }) +// state that is not possible to get via bot +export const gameAdditionalState = proxy({ + isFlying: false, + isSprinting: false, +}) + +window.gameAdditionalState = gameAdditionalState + +// todo thats weird workaround, probably we can do better? +let forceDisableLeaveWarning = false +const info = console.info +console.info = (...args) => { + const message = args[0] + if (message === '[webpack-dev-server] App updated. Recompiling...') { + forceDisableLeaveWarning = true + } + info.apply(console, args) +} + +// todo move from global state window.addEventListener('beforeunload', (event) => { // todo-low maybe exclude chat? if (!isGameActive(true) && activeModalStack.at(-1)?.elem.id !== 'chat') return + if (forceDisableLeaveWarning && options.preventDevReloadWhilePlaying === false) return // For major browsers doning only this is enough event.preventDefault() diff --git a/src/index.js b/src/index.js index 53c10e3de..8f938cba5 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ require('./menus/notification') require('./menus/title_screen') require('./optionsStorage') require('./reactUi.jsx') +require('./botControls') // @ts-ignore require('crypto').createPublicKey = () => { } @@ -78,7 +79,7 @@ const Cursor = require('./cursor') //@ts-ignore global.THREE = require('three') const { initVR } = require('./vr') -const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalStacks, replaceActiveModalStack, isGameActive } = require('./globalState') +const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalStacks, replaceActiveModalStack, isGameActive, miscUiState, gameAdditionalState } = require('./globalState') const { pointerLock, goFullscreen, toNumber } = require('./utils') const { notification } = require('./menus/notification') const { removePanorama, addPanoramaCubeMap, initPanoramaOptions } = require('./panorama') @@ -87,6 +88,9 @@ const { startLocalServer } = require('./createLocalServer') const serverOptions = require('./defaultLocalServerOptions') const { customCommunication } = require('./customServer') const { default: updateTime } = require('./updateTime') +const { options } = require('./optionsStorage') +const { subscribe } = require('valtio') +const { subscribeKey } = require('valtio/utils') if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -189,7 +193,6 @@ const loadingScreen = document.getElementById('loading-error-screen') const hud = document.getElementById('hud') const optionsScrn = document.getElementById('options-screen') -const keyBindScrn = document.getElementById('keybinds-screen') const pauseMenu = document.getElementById('pause-screen') function setLoadingScreenStatus (status, isError = false) { @@ -247,6 +250,7 @@ async function main () { } let listeners = [] +let disposables = [] let timeouts = [] let intervals = [] // only for dom listeners (no removeAllListeners) @@ -261,17 +265,22 @@ const removeAllListeners = () => { target.removeEventListener(event, callback) }) listeners = [] + for (const disposable of disposables) { + disposable() + } + disposables = [] } /** - * @param {{ server: any; port?: string; singleplayer: any; username: any; password: any; proxy: any; botVersion?: any; }} options + * @param {{ server: any; port?: string; singleplayer: any; username: any; password: any; proxy: any; botVersion?: any; }} connectOptions */ -async function connect (options) { +async function connect (connectOptions) { const menu = document.getElementById('play-screen') menu.style = 'display: none;' removePanorama() - const singeplayer = options.singleplayer + const singeplayer = connectOptions.singleplayer + miscUiState.singleplayer = singeplayer const oldSetInterval = window.setInterval // @ts-ignore window.setInterval = (callback, ms) => { @@ -288,11 +297,11 @@ async function connect (options) { } const debugMenu = hud.shadowRoot.querySelector('#debug-overlay') - const viewDistance = optionsScrn.renderDistance - const hostprompt = options.server - const proxyprompt = options.proxy - const username = options.username - const password = options.password + const { renderDistance, maxMultiplayerRenderDistance } = options + const hostprompt = connectOptions.server + const proxyprompt = connectOptions.proxy + const username = connectOptions.username + const password = connectOptions.password let host, port, proxy, proxyport if (!hostprompt.includes(':')) { @@ -349,7 +358,7 @@ async function connect (options) { window.addEventListener('keydown', (e) => { if (e.code !== 'KeyR') return controller.abort() - connect(options) + connect(connectOptions) loadingScreen.hasError = false }, { signal: controller.signal }) // #endregion @@ -364,19 +373,20 @@ async function connect (options) { }, { signal: errorAbortController.signal }) + let singlePlayerServer try { if (singeplayer) { window.serverDataChannel ??= {} window.worldLoaded = false //@ts-ignore TODO Object.assign(serverOptions, _.defaultsDeep(JSON.parse(localStorage.localServerOptions || '{}'), serverOptions)) - const server = startLocalServer() + singlePlayerServer = startLocalServer() // todo need just to call quit if started loadingScreen.maybeRecoverable = false // init world, todo: do it for any async plugins - if (!server.worldsReady) { + if (!singlePlayerServer.worldsReady) { await new Promise(resolve => { - server.once('worldsReady', resolve) + singlePlayerServer.once('worldsReady', resolve) }) } } @@ -384,7 +394,7 @@ async function connect (options) { bot = mineflayer.createBot({ host, port, - version: options.botVersion === '' ? false : options.botVersion, + version: connectOptions.botVersion === '' ? false : connectOptions.botVersion, ...singeplayer ? { version: serverOptions.version, connect () { }, @@ -430,7 +440,7 @@ async function connect (options) { // server is ok, add it to the history /** @type {string[]} */ const serverHistory = JSON.parse(localStorage.getItem('serverHistory') || '[]') - serverHistory.unshift(options.server) + serverHistory.unshift(connectOptions.server) localStorage.setItem('serverHistory', JSON.stringify([...new Set(serverHistory)])) setLoadingScreenStatus('Loading world') @@ -448,19 +458,34 @@ async function connect (options) { const center = bot.entity.position - const worldView = new WorldView(bot.world, viewDistance, center) + const worldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) + if (singeplayer) { + const d = subscribeKey(options, 'renderDistance', () => { + singlePlayerServer.options['view-distance'] = options.renderDistance + worldView.viewDistance = options.renderDistance + if (miscUiState.singleplayer) { + window.onPlayerChangeRenderDistance?.(options.renderDistance) + } + }) + disposables.push(d) + } let fovSetting = optionsScrn.fov const updateFov = () => { fovSetting = optionsScrn.fov + // todo check values and add transition if (bot.controlState.sprint && !bot.controlState.sneak) { - // todo check value and add transition - fovSetting *= 5 + fovSetting += 5 + } + if (gameAdditionalState.isFlying) { + fovSetting += 5 } viewer.camera.fov = fovSetting viewer.camera.updateProjectionMatrix() } updateFov() + subscribeKey(gameAdditionalState, 'isFlying', updateFov) + subscribeKey(gameAdditionalState, 'isSprinting', updateFov) optionsScrn.addEventListener('fov_changed', updateFov) viewer.setVersion(version) @@ -587,81 +612,6 @@ async function connect (options) { bot.clearControlStates() }, false) - registerListener(document, 'keydown', (e) => { - if (activeModalStack.length) return - - keyBindScrn.keymaps.forEach(km => { - if (e.code === km.key) { - switch (km.defaultKey) { - case 'KeyE': - showModal({ reactType: 'inventory', }) - // todo seems to be workaround - // avoid calling inner keybinding listener, but should be handled there - e.stopImmediatePropagation() - break - case 'KeyQ': - if (bot.heldItem) bot.tossStack(bot.heldItem) - break - case 'ControlLeft': - bot.setControlState('sprint', true) - updateFov() - break - case 'ShiftLeft': - bot.setControlState('sneak', true) - break - case 'Space': - bot.setControlState('jump', true) - break - case 'KeyD': - bot.setControlState('right', true) - break - case 'KeyA': - bot.setControlState('left', true) - break - case 'KeyS': - bot.setControlState('back', true) - break - case 'KeyW': - bot.setControlState('forward', true) - break - } - } - }) - }, { - capture: true, - }) - - registerListener(document, 'keyup', (e) => { - keyBindScrn.keymaps.forEach(km => { - if (e.code === km.key) { - switch (km.defaultKey) { - case 'ControlLeft': - bot.setControlState('sprint', false) - updateFov() - break - case 'ShiftLeft': - bot.setControlState('sneak', false) - break - case 'Space': - bot.setControlState('jump', false) - break - case 'KeyD': - bot.setControlState('right', false) - break - case 'KeyA': - bot.setControlState('left', false) - break - case 'KeyS': - bot.setControlState('back', false) - break - case 'KeyW': - bot.setControlState('forward', false) - break - } - } - }) - }, false) - setLoadingScreenStatus('Done!') console.log('Done!') diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index d7ef0b12a..9997a187c 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -1,8 +1,11 @@ const { LitElement, html, css } = require('lit') const { commonCss, isMobile } = require('./components/common') -const { showModal, hideCurrentModal, isGameActive } = require('../globalState') +const { showModal, hideCurrentModal, isGameActive, miscUiState } = require('../globalState') const { CommonOptionsScreen } = require('./options_store') const { toNumber } = require('../utils') +const { options } = require('../optionsStorage') +const { subscribe } = require('valtio') +const { subscribeKey } = require('valtio/utils') class OptionsScreen extends CommonOptionsScreen { static get styles () { @@ -43,7 +46,6 @@ class OptionsScreen extends CommonOptionsScreen { chatHeight: { type: Number }, chatScale: { type: Number }, sound: { type: Number }, - renderDistance: { type: Number }, fov: { type: Number }, guiScale: { type: Number } } @@ -59,7 +61,6 @@ class OptionsScreen extends CommonOptionsScreen { chatHeight: { defaultValue: 180, convertFn: (v) => toNumber(v) }, chatScale: { defaultValue: 100, convertFn: (v) => toNumber(v) }, sound: { defaultValue: 50, convertFn: (v) => toNumber(v) }, - renderDistance: { defaultValue: 6, convertFn: (v) => toNumber(v) }, fov: { defaultValue: 75, convertFn: (v) => toNumber(v) }, guiScale: { defaultValue: 3, convertFn: (v) => toNumber(v) }, mouseRawInput: { defaultValue: false, convertFn: (v) => v === 'true' }, @@ -70,6 +71,13 @@ class OptionsScreen extends CommonOptionsScreen { document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`) document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) + + subscribe(options, () => { + this.requestUpdate() + }) + subscribeKey(miscUiState, 'singleplayer', () => { + this.requestUpdate() + }) } render () { @@ -114,8 +122,8 @@ class OptionsScreen extends CommonOptionsScreen { }}>
    - { - this.changeOption('renderDistance', e.target.value) + { + options.renderDistance = +e.target.value }}> { this.changeOption('fov', e.target.value) diff --git a/src/optionsStorage.js b/src/optionsStorage.js index 3da40ae0a..bf3dd258a 100644 --- a/src/optionsStorage.js +++ b/src/optionsStorage.js @@ -5,10 +5,16 @@ import { proxy, subscribe } from 'valtio/vanilla' import { subscribeKey } from 'valtio/utils' import { mergeAny } from './optionsStorageTypes' +const defaultOptions = { + renderDistance: 4, + alwaysShowMobileControls: false, + maxMultiplayerRenderDistance: 6, + excludeCommunicationDebugEvents: [], + preventDevReloadWhilePlaying: false +} + export const options = proxy( - mergeAny({ - alwaysShowMobileControls: false - }, JSON.parse(localStorage.options || '{}')) + mergeAny(defaultOptions, JSON.parse(localStorage.options || '{}')) ) window.options = options @@ -33,6 +39,10 @@ export const watchValue = (proxy, callback) => { }) } +watchValue(options, o => { + globalThis.excludeCommunicationDebugEvents = o.excludeCommunicationDebugEvents +}) + export const useOptionValue = (setting, valueCallback) => { valueCallback(setting) subscribe(setting, valueCallback) From e519e69584f93a454145a923433898035b96c03c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 26 Aug 2023 06:12:47 +0300 Subject: [PATCH 093/318] make new ci.yml run on pulls --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc357e5a2..867fefba8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,6 @@ name: Deploy to GitHub pages on: - push: - branches: [main] + pull_request: jobs: build-and-deploy: runs-on: ubuntu-latest From 2e3f30a9de5d4f04957d14fc5f9ec77500c56ab3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 02:53:05 +0300 Subject: [PATCH 094/318] replace legacy lib, comment --- package.json | 1 - src/menus/components/common.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 7a7889082..c3b718c02 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "fs-extra": "^11.1.1", - "ismobilejs": "^1.1.1", "lit": "^2.8.0", "net-browserify": "github:PrismarineJS/net-browserify", "prismarine-world": "^3.6.2", diff --git a/src/menus/components/common.js b/src/menus/components/common.js index e84fe4304..707e31d81 100644 --- a/src/menus/components/common.js +++ b/src/menus/components/common.js @@ -44,8 +44,7 @@ const commonCss = css` /** @returns {boolean} */ function isMobile () { - const m = require('ismobilejs').default() - return m.any + return window.matchMedia('(pointer: coarse)').matches } /** From 50b5c76c36e2a96d137915fa284488b9526211d0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 02:53:35 +0300 Subject: [PATCH 095/318] flying check for spectator --- src/botControls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botControls.js b/src/botControls.js index 039795b41..1b43c2d7e 100644 --- a/src/botControls.js +++ b/src/botControls.js @@ -62,7 +62,7 @@ const patchedSetControlState = (action, state) => { } const toggleFly = () => { - if (bot.game.gameMode !== 'creative') return + if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return if (bot.setControlState !== patchedSetControlState) { originalSetControlState = bot.setControlState bot.setControlState = patchedSetControlState From 37a3a7b2645cccfe4579ad3727154f4bae976621 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 02:57:59 +0300 Subject: [PATCH 096/318] play -> connect to server --- src/menus/title_screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index 60bd0d74e..856abb7eb 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -144,7 +144,7 @@ class TitleScreen extends LitElement {
    showModal(document.getElementById('options-screen'))}> { - window.location.search = '' - window.location.reload() + bot._client.emit('end') + // window.location.search = '' + // window.location.reload() }}> ` From 3c8200a6facc6acbbc62de43331d18aef3f5ca61 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 06:52:54 +0300 Subject: [PATCH 101/318] support world saving :rocket: --- src/globalState.js | 4 ++++ src/index.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/globalState.js b/src/globalState.js index 07067fc7d..e84bc400a 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -134,6 +134,10 @@ console.info = (...args) => { window.addEventListener('unload', (e) => { if (window.singlePlayerServer) { for (const player of window.singlePlayerServer.players) { + // const worlds = [singlePlayerServer.overworld] + // for (const world of worlds) { + // world.storageProvider.close() + // } player.save() } } diff --git a/src/index.js b/src/index.js index 35984b44d..bc402b16d 100644 --- a/src/index.js +++ b/src/index.js @@ -65,6 +65,10 @@ _fs.promises.open = async (...args) => { return await new Promise(resolve => { _fs[x](fd, ...args, (err, bytesRead, buffer) => { if (err) throw err + if (x === 'write') { + // flush data, though alternatively we can rely on close in unload + _fs.fsync(fd, () => { }) + } resolve({ buffer, bytesRead }) }) }) From d58867bdc509de195b3c54988113df4b3bf52197 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 21:47:32 +0300 Subject: [PATCH 102/318] refactor: move browserfs to its own file --- src/browserfs.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 45 ++------------------------------------------- 2 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 src/browserfs.js diff --git a/src/browserfs.js b/src/browserfs.js new file mode 100644 index 000000000..c69a46b84 --- /dev/null +++ b/src/browserfs.js @@ -0,0 +1,45 @@ +//@ts-check +const { promisify } = require('util') +const browserfs = require('browserfs') +const _fs = require('fs') + +browserfs.install(window) +// todo migrate to StorageManager API for localsave as localstorage has only 5mb limit, when localstorage is fallback test limit warning on 4mb +browserfs.configure({ + // todo change to localstorage: mkdir doesnt work for some reason + fs: 'MountableFileSystem', + options: { + "/world": { fs: "LocalStorage" } + }, +}, (e) => { + if (e) throw e +}) +//@ts-ignore +_fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir'].map(key => [key, promisify(_fs[key])])), { + get (target, p, receiver) { + //@ts-ignore + if (!target[p]) throw new Error(`Not implemented fs.promises.${p}`) + return (...args) => { + // browser fs bug: if path doesn't start with / dirname will return . which would cause infinite loop, so we need to normalize paths + if (typeof args[0] === 'string' && !args[0].startsWith('/')) args[0] = '/' + args[0] + //@ts-ignore + return target[p](...args) + } + } +}) +//@ts-ignore +_fs.promises.open = async (...args) => { + const fd = await promisify(_fs.open)(...args) + return Object.fromEntries(['read', 'write', 'close'].map(x => [x, async (...args) => { + return await new Promise(resolve => { + _fs[x](fd, ...args, (err, bytesRead, buffer) => { + if (err) throw err + if (x === 'write') { + // flush data, though alternatively we can rely on close in unload + _fs.fsync(fd, () => { }) + } + resolve({ buffer, bytesRead }) + }) + }) + }])) +} diff --git a/src/index.js b/src/index.js index bc402b16d..9ed4f6a61 100644 --- a/src/index.js +++ b/src/index.js @@ -24,57 +24,16 @@ require('./menus/options_screen') require('./menus/advanced_options_screen') require('./menus/notification') require('./menus/title_screen') + require('./optionsStorage') require('./reactUi.jsx') require('./botControls') require('./dragndrop') +require('./browserfs') // @ts-ignore require('crypto').createPublicKey = () => { } -const { promisify } = require('util') -const browserfs = require('browserfs') -browserfs.install(window) -browserfs.configure({ - // todo change to localstorage: mkdir doesnt work for some reason - fs: 'MountableFileSystem', - options: { - "/world": { fs: "LocalStorage" } - }, -}, (e) => { - if (e) throw e -}) -const _fs = require('fs') -//@ts-ignore -_fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir'].map(key => [key, promisify(_fs[key])])), { - get (target, p, receiver) { - //@ts-ignore - if (!target[p]) throw new Error(`Not implemented fs.promises.${p}`) - return (...args) => { - // browser fs bug: if path doesn't start with / dirname will return . which would cause infinite loop, so we need to normalize paths - if (typeof args[0] === 'string' && !args[0].startsWith('/')) args[0] = '/' + args[0] - //@ts-ignore - return target[p](...args) - } - } -}) -//@ts-ignore -_fs.promises.open = async (...args) => { - const fd = await promisify(_fs.open)(...args) - return Object.fromEntries(['read', 'write', 'close'].map(x => [x, async (...args) => { - return await new Promise(resolve => { - _fs[x](fd, ...args, (err, bytesRead, buffer) => { - if (err) throw err - if (x === 'write') { - // flush data, though alternatively we can rely on close in unload - _fs.fsync(fd, () => { }) - } - resolve({ buffer, bytesRead }) - }) - }) - }])) -} - const net = require('net') const Stats = require('stats.js') From b4052892e5660515c2d64a82087f84a1a9349ece Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 23 Aug 2023 23:34:09 +0300 Subject: [PATCH 103/318] temporary fix for block model in inventory gui --- src/styles.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/styles.css b/src/styles.css index 38efac0ae..81fca2dce 100644 --- a/src/styles.css +++ b/src/styles.css @@ -92,6 +92,14 @@ canvas { font-family: minecraft, mojangles, monospace; } +/* todo I guess the best solution would be to render it on canvas */ +.BlockModel { + scale: 0.5; + width: 183%; + height: 190%; + transform: translate(-50%, -50%); +} + @media only screen and (max-width: 971px) { #ui-root { transform: scale(2); From b1b03f07ffd7a056c5556800fb3954763dfc2577 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 24 Aug 2023 01:39:42 +0300 Subject: [PATCH 104/318] add support for icons in button TODO not pixel-perfect --- package.json | 1 + src/index.js | 1 + src/menus/advanced_options_screen.js | 3 +-- src/menus/components/button.js | 16 ++++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c3b718c02..7da3dc1db 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "fs-extra": "^11.1.1", + "iconify-icon": "^1.0.8", "lit": "^2.8.0", "net-browserify": "github:PrismarineJS/net-browserify", "prismarine-world": "^3.6.2", diff --git a/src/index.js b/src/index.js index 9ed4f6a61..61d7ef993 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ //@ts-check /* global THREE */ +require('iconify-icon') require('./chat') // workaround for mineflayer diff --git a/src/menus/advanced_options_screen.js b/src/menus/advanced_options_screen.js index 75c1da732..e94fc0c22 100644 --- a/src/menus/advanced_options_screen.js +++ b/src/menus/advanced_options_screen.js @@ -72,8 +72,7 @@ class AdvancedOptionsScreen extends CommonOptionsScreen { this.changeOption('frameLimit', newVal > this.frameLimitMax ? false : newVal) this.requestUpdate() }}> - - { + { const rate = await getScreenRefreshRate() this.frameLimitMax = rate this.requestUpdate() diff --git a/src/menus/components/button.js b/src/menus/components/button.js index dbec5096e..185e75147 100644 --- a/src/menus/components/button.js +++ b/src/menus/components/button.js @@ -50,6 +50,9 @@ class Button extends LitElement { border: none; z-index: 1; outline: none; + display: inline-flex; + justify-content: center; + align-items: center; } .button:hover, @@ -91,6 +94,13 @@ class Button extends LitElement { background-position-y: calc(var(--txrV) * -1); z-index: -1; } + + .icon { + position: absolute; + top: 3px; + left: 3px; + font-size: 14px; + } ` } @@ -110,6 +120,10 @@ class Button extends LitElement { onPress: { type: Function, attribute: 'pmui-click' + }, + icon: { + type: Function, + attribute: 'pmui-icon' } } } @@ -130,6 +144,8 @@ class Button extends LitElement { @click=${this.onBtnClick} style="width: ${this.width};" > + + ${this.icon ? html`` : ''} ${this.label} ` } From 7eb2d5442b70e6c0e91d558b5e945b32ce308d3c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 26 Aug 2023 06:09:34 +0300 Subject: [PATCH 105/318] feat(big!): implement folder save loading with write access --- package.json | 6 +- src/browserfs.js | 85 +++++++++++++++++--- src/dragndrop.ts | 29 +++++++ src/globalState.js | 4 - src/globals.d.ts | 13 +++ src/index.js | 18 +++-- src/loadFolder.ts | 102 ++++++++++++++++++++++++ src/mcTypes.ts | 164 ++++++++++++++++++++++++++++++++++++++ src/menus/pause_screen.js | 16 +++- src/menus/title_screen.js | 13 ++- src/optionsStorage.js | 2 + webpack.common.js | 11 +-- 12 files changed, 430 insertions(+), 33 deletions(-) create mode 100644 src/dragndrop.ts create mode 100644 src/loadFolder.ts create mode 100644 src/mcTypes.ts diff --git a/package.json b/package.json index 7da3dc1db..23302a0fc 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,10 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@types/wicg-file-system-access": "^2020.9.6", "@zardoy/react-util": "^0.2.0", - "browserfs": "^1.4.3", + "@zardoy/utils": "^0.0.11", + "browserfs": "github:zardoy/browserfs#build", "compression": "^1.7.4", "cypress-plugin-snapshots": "^1.4.4", "diamond-square": "^1.2.0", @@ -91,7 +93,7 @@ "pnpm": { "overrides": { "minecraft-data": "latest", - "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client", + "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra", "space-squid": "file:../space-squid", "@dimaka/interface": "file:../interface" } diff --git a/src/browserfs.js b/src/browserfs.js index c69a46b84..dfc531f54 100644 --- a/src/browserfs.js +++ b/src/browserfs.js @@ -1,4 +1,6 @@ //@ts-check +import { fsState, loadFolder } from './loadFolder' +import { oneOf } from '@zardoy/utils' const { promisify } = require('util') const browserfs = require('browserfs') const _fs = require('fs') @@ -15,13 +17,16 @@ browserfs.configure({ if (e) throw e }) //@ts-ignore -_fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir'].map(key => [key, promisify(_fs[key])])), { +_fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir', 'rename', /* 'copyFile', */'readdir'].map(key => [key, promisify(_fs[key])])), { get (target, p, receiver) { //@ts-ignore if (!target[p]) throw new Error(`Not implemented fs.promises.${p}`) return (...args) => { // browser fs bug: if path doesn't start with / dirname will return . which would cause infinite loop, so we need to normalize paths if (typeof args[0] === 'string' && !args[0].startsWith('/')) args[0] = '/' + args[0] + // Write methods + // todo issue one-time warning (in chat I guess) + if (oneOf(p, 'writeFile', 'mkdir', 'rename') && fsState.isReadonly) return //@ts-ignore return target[p](...args) } @@ -30,16 +35,74 @@ _fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'm //@ts-ignore _fs.promises.open = async (...args) => { const fd = await promisify(_fs.open)(...args) - return Object.fromEntries(['read', 'write', 'close'].map(x => [x, async (...args) => { - return await new Promise(resolve => { - _fs[x](fd, ...args, (err, bytesRead, buffer) => { - if (err) throw err - if (x === 'write') { - // flush data, though alternatively we can rely on close in unload - _fs.fsync(fd, () => { }) - } - resolve({ buffer, bytesRead }) + return { + ...Object.fromEntries(['read', 'write', 'close'].map(x => [x, async (...args) => { + return await new Promise(resolve => { + _fs[x](fd, ...args, (err, bytesRead, buffer) => { + if (err) throw err + // todo if readonly probably there is no need to open at all (return some mocked version - check reload)? + if (x === 'write' && !fsState.isReadonly && fsState.syncFs) { + // flush data, though alternatively we can rely on close in unload + _fs.fsync(fd, () => { }) + } + resolve({ buffer, bytesRead }) + }) + }) + }])), + // for debugging + fd, + filename: args[0], + close: () => { + return new Promise(resolve => { + _fs.close(fd, (err) => { + if (err) { + throw err + } else { + resolve() + } + }) }) + } + } +} + +const SUPPORT_WRITE = true + +export const openWorldDirectory = async (dragndropData) => { + /** @type {FileSystemDirectoryHandle} */ + let _directoryHandle + try { + _directoryHandle = await window.showDirectoryPicker() + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return + throw err + } + const directoryHandle = _directoryHandle + + const requestResult = SUPPORT_WRITE ? await directoryHandle.requestPermission?.({ mode: 'readwrite' }) : undefined + const writeAccess = requestResult === 'granted' + + const doContinue = writeAccess || !SUPPORT_WRITE || confirm('Continue in readonly mode?') + if (!doContinue) return + await new Promise(resolve => { + browserfs.configure({ + // todo + fs: 'MountableFileSystem', + options: { + "/world": { + fs: "FileSystemAccess", + options: { + handle: directoryHandle + } + } + }, + }, (e) => { + if (e) throw e + resolve() }) - }])) + }) + + fsState.isReadonly = !writeAccess + fsState.syncFs = false + loadFolder() } diff --git a/src/dragndrop.ts b/src/dragndrop.ts new file mode 100644 index 000000000..b29ca1c8f --- /dev/null +++ b/src/dragndrop.ts @@ -0,0 +1,29 @@ +import * as nbt from 'prismarine-nbt' +import { promisify } from 'util' +import { showNotification } from './menus/notification' + +const parseNbt = promisify(nbt.parse); + +// todo display drop zone +["drag", "dragstart", "dragend", "dragover", "dragenter", "dragleave", "drop"].forEach(event => { + window.addEventListener(event, (e: any) => { + if (e.dataTransfer && !e.dataTransfer.types.includes("Files")) { + // e.dataTransfer.effectAllowed = "none" + return + } + e.preventDefault() + }) +}) +window.addEventListener("drop", async e => { + if (!e.dataTransfer?.files.length) return + // todo support drop save folder + const { files } = e.dataTransfer + const file = files.item(0)! + const buffer = await file.arrayBuffer() + const parsed = await parseNbt(Buffer.from(buffer)) + showNotification({ + message: `${file.name} data available in browser console`, + }) + console.log('raw', parsed) + console.log('simplified', nbt.simplify(parsed).Data) +}) diff --git a/src/globalState.js b/src/globalState.js index e84bc400a..07067fc7d 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -134,10 +134,6 @@ console.info = (...args) => { window.addEventListener('unload', (e) => { if (window.singlePlayerServer) { for (const player of window.singlePlayerServer.players) { - // const worlds = [singlePlayerServer.overworld] - // for (const world of worlds) { - // world.storageProvider.close() - // } player.save() } } diff --git a/src/globals.d.ts b/src/globals.d.ts index 43875c1eb..f4719819d 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,3 +1,5 @@ +/// + declare const THREE: typeof import('three'); // todo declare const bot: import('mineflayer').Bot @@ -15,3 +17,14 @@ declare interface DocumentFragment { declare interface Window extends Record { } + +type StringKeys = Extract + + +interface ObjectConstructor { + keys(obj: T): StringKeys[] + entries(obj: T): [StringKeys, T[keyof T]][] + // todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right + fromEntries(obj: T): Record + assign, K extends Record>(target: T, source: K): asserts target is T & K +} diff --git a/src/index.js b/src/index.js index 61d7ef993..2696a2502 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,10 @@ //@ts-check /* global THREE */ +// todo cant ignore fs thus doesnt work in browser +// const soureMapSupport = require('source-map-support') +// soureMapSupport.install({ +// environment: 'browser', +// }) require('iconify-icon') require('./chat') @@ -201,12 +206,13 @@ async function main () { const options = e.detail connect(options) }) - const connectSingleplayer = () => { + const connectSingleplayer = (serverOverrides = {}) => { // todo clean - connect({ server: '', port: '', proxy: '', singleplayer: true, username: 'wanderer', password: '' }) + connect({ server: '', port: '', proxy: '', singleplayer: true, username: 'wanderer', password: '', serverOverrides }) } document.querySelector('#title-screen').addEventListener('singleplayer', (e) => { - connectSingleplayer() + //@ts-ignore + connectSingleplayer(e.detail) }) const qs = new URLSearchParams(window.location.search) if (qs.get('singleplayer') === '1') { @@ -240,7 +246,7 @@ const removeAllListeners = () => { } /** - * @param {{ server: any; port?: string; singleplayer: any; username: any; password: any; proxy: any; botVersion?: any; }} connectOptions + * @param {{ server: any; port?: string; singleplayer: any; username: any; password: any; proxy: any; botVersion?: any; serverOverrides? }} connectOptions */ async function connect (connectOptions) { const menu = document.getElementById('play-screen') @@ -349,10 +355,10 @@ async function connect (connectOptions) { window.serverDataChannel ??= {} window.worldLoaded = false //@ts-ignore TODO - Object.assign(serverOptions, _.defaultsDeep(JSON.parse(localStorage.localServerOptions || '{}'), serverOptions)) + Object.assign(serverOptions, _.defaultsDeep(JSON.parse(localStorage.localServerOptions || '{}'), connectOptions.serverOverrides, serverOptions)) singlePlayerServer = window.singlePlayerServer = startLocalServer() // todo need just to call quit if started - loadingScreen.maybeRecoverable = false + // loadingScreen.maybeRecoverable = false // init world, todo: do it for any async plugins if (!singlePlayerServer.worldsReady) { await new Promise(resolve => { diff --git a/src/loadFolder.ts b/src/loadFolder.ts new file mode 100644 index 000000000..e1e739c8f --- /dev/null +++ b/src/loadFolder.ts @@ -0,0 +1,102 @@ +import fs from 'fs' +import { supportedVersions } from 'space-squid/src/lib/version' +import * as nbt from 'prismarine-nbt' +import { promisify } from 'util' +import { options } from './optionsStorage' + +const parseNbt = promisify(nbt.parse) + +// additional fs metadata +export const fsState = { + isReadonly: false, + syncFs: false, +} + +const PROPOSE_BACKUP = true + +export const loadFolder = async () => { + // todo-low cache reading + const warnings: string[] = [] + let levelDatContent + try { + levelDatContent = await fs.promises.readFile('/world/level.dat') + } catch (err) { + if (err.code === 'ENOENT') { + if (!fsState.isReadonly) { + warnings.push('level.dat not found, world in current folder will be created') + } else { + throw new Error('level.dat not found, ensure you are loading world folder') + } + } else { + throw err + } + } + + + let version: string | undefined + if (levelDatContent) { + const parsedRaw = await parseNbt(Buffer.from(levelDatContent)) + const levelDat: import('./mcTypes').LevelDat = nbt.simplify(parsedRaw).Data + version = levelDat.Version?.Name + if (!version) { + const newVersion = prompt(`In 1.8 and before world save doesn\'t contain version info, please enter version you want to use to load the world.\nSupported versions ${supportedVersions.join(', ')}`, '1.8.8') + if (!newVersion) return + version = newVersion + } + if (!supportedVersions.includes(version)) { + warnings.push(`Version ${version} is not supported, supported versions ${supportedVersions.join(', ')}, 1.16.1 will be used`) + version = '1.16.1' + } + let isFlat = false + if (levelDat.WorldGenSettings) { + for (const [key, value] of Object.entries(levelDat.WorldGenSettings.dimensions)) { + if (key.slice(10) === 'overworld') { + if (value.generator.type === 'flat') isFlat = true + break + } + } + } + + if (levelDat.generatorName) { + isFlat = levelDat.generatorName === 'flat' + } + if (!isFlat) { + warnings.push(`Generator ${levelDat.generatorName} is not supported yet`) + } + } + + if (warnings.length) { + const doContinue = confirm(`Continue with following warnings?\n${warnings.join('\n')}`) + if (!doContinue) return + } + + if (PROPOSE_BACKUP) { + // TODO-HIGH! enable after copyFile in browserfs is implemented + + // const doBackup = options.alwaysBackupWorldBeforeLoading ?? confirm('Do you want to backup your world files before loading it?') + // // const doBackup = true + // if (doBackup) { + // // todo do it in flying squid instead + // await fs.promises.copyFile('/world/level.dat', `/world/level.dat_old`) + // try { + // await fs.promises.mkdir('/backups/region.old', { recursive: true }) + // } catch (err) { } + // const files = await fs.promises.readdir('/world/region') + // for (const file of files) { + // await fs.promises.copyFile(`/world/region/${file}`, `/world/region.old/${file}`) + // } + // } + } + + if (!fsState.isReadonly) { + // todo allow also to ctrl+s + alert("Note: the world is saved only when you click disconnect!") + } + + document.querySelector('#title-screen').dispatchEvent(new CustomEvent('singleplayer', { + // todo check gamemode level.dat data etc + detail: { + version + }, + })) +} diff --git a/src/mcTypes.ts b/src/mcTypes.ts new file mode 100644 index 000000000..e7f6c7afd --- /dev/null +++ b/src/mcTypes.ts @@ -0,0 +1,164 @@ +// todo move from here + +//@ts-format-ignore-region +// 1.8.8 +export interface LevelDat { + WorldGenSettings?: WorldGenSettings; + RandomSeed: number[]; + generatorName?: string; + BorderCenterX: number; + BorderCenterZ: number; + Difficulty: number; + DifficultyLocked: number; + BorderSizeLerpTime: number[]; + Version?: { + Name: string + // id, snapshot + } + /** 0,1 */ + raining: number; + Time: number[]; + GameType: number; + MapFeatures: number; + BorderDamagePerBlock: number; + BorderWarningBlocks: number; + BorderSizeLerpTarget: number; + DayTime: number[]; + initialized: number; + allowCommands: number; + SizeOnDisk: number[]; + GameRules: GameRules; + Player: Player; + SpawnY: number; + rainTime: number; + thunderTime: number; + SpawnZ: number; + hardcore: number; + SpawnX: number; + clearWeatherTime: number; + thundering: number; + generatorVersion?: number; + version: number; + BorderSafeZone: number; + generatorOptions?: string; + LastPlayed: number[]; + BorderWarningTime: number; + LevelName: string; + BorderSize: number; +} + +export interface GameRules { + doTileDrops: string; + doFireTick: string; + reducedDebugInfo: string; + naturalRegeneration: string; + doMobLoot: string; + keepInventory: string; + doEntityDrops: string; + mobGriefing: string; + randomTickSpeed: string; + commandBlockOutput: string; + doMobSpawning: string; + logAdminCommands: string; + sendCommandFeedback: string; + doDaylightCycle: string; + showDeathMessages: string; +} + +export interface Player { + HurtByTimestamp: number; + SleepTimer: number; + Attributes: Attribute[]; + Invulnerable: number; + PortalCooldown: number; + AbsorptionAmount: number; + abilities: Abilities; + FallDistance: number; + DeathTime: number; + XpSeed: number; + HealF: number; + XpTotal: number; + playerGameType: number; + SelectedItem: SelectedItem; + Motion: number[]; + UUIDLeast: number[]; + Health: number; + foodSaturationLevel: number; + Air: number; + OnGround: number; + Dimension: number; + Rotation: number[]; + XpLevel: number; + Score: number; + UUIDMost: number[]; + Sleeping: number; + Pos: number[]; + Fire: number; + XpP: number; + EnderItems: any[]; + foodLevel: number; + foodExhaustionLevel: number; + HurtTime: number; + SelectedItemSlot: number; + Inventory: SelectedItem[]; + foodTickTimer: number; +} + +export interface Attribute { + Base: number; + Name: string; +} + +export interface SelectedItem { + Slot?: number; + id: string; + Count: number; + Damage: number; +} + +export interface Abilities { + invulnerable: number; + mayfly: number; + instabuild: number; + walkSpeed: number; + mayBuild: number; + flying: number; + flySpeed: number; +} + +// 1.16+ + +export interface WorldGenSettings { + /** 0,1 */ + bonus_chest: number; + seed: number[]; + /** 0,1 */ + generate_features: number; + dimensions: Dimensions; +} + +export interface Dimensions { + // :overworld, :the_nether, :the_end + [key: string]: WorldGen; +} + +export interface WorldGen { + generator: WorldGenGenerator; + // same as key + type: string; +} + +export interface WorldGenGenerator { + settings: string; + seed: number[]; + biome_source: PurpleBiomeSource; + type: string; +} + +export interface PurpleBiomeSource { + seed: number[]; + /** only for overworld 0,1 */ + large_biomes?: number; + // :noise, :flat, ? + type: string; +} diff --git a/src/menus/pause_screen.js b/src/menus/pause_screen.js index e52e3977f..81e97ce46 100644 --- a/src/menus/pause_screen.js +++ b/src/menus/pause_screen.js @@ -61,10 +61,18 @@ class PauseScreen extends LitElement { openURL('https://discord.gg/4Ucm684Fq3')}>
    showModal(document.getElementById('options-screen'))}> - { - bot._client.emit('end') - // window.location.search = '' - // window.location.reload() + { + if (window.singlePlayerServer) { + for (const player of window.singlePlayerServer.players) { + const worlds = [singlePlayerServer.overworld] + for (const world of worlds) { + await world.storageProvider.close() + } + await player.save() + singlePlayerServer.quit() + } + } + bot._client.emit('end') }}> ` diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index 856abb7eb..204f0f86c 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -1,4 +1,6 @@ +const { openWorldDirectory } = require('../browserfs') const { showModal } = require('../globalState') +const { fsState } = require('../loadFolder') const { openURL } = require('./components/common') const { LitElement, html, css } = require('lit') @@ -145,10 +147,19 @@ class TitleScreen extends LitElement {
    ` diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index 9519179ea..891cfdbd1 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -78,6 +78,7 @@ class LoadingErrorScreen extends LitElement { ${this.hasError ? html`
    { this.hasError = false + miscUiState.gameLoaded = false if (activeModalStacks['main-menu']) { replaceActiveModalStack('main-menu') } else { diff --git a/src/menus/pause_screen.js b/src/menus/pause_screen.js index a2290d06a..aecdf6ccf 100644 --- a/src/menus/pause_screen.js +++ b/src/menus/pause_screen.js @@ -1,7 +1,7 @@ //@ts-check const { LitElement, html, css } = require('lit') const { openURL } = require('./components/common') -const { hideCurrentModal, showModal } = require('../globalState') +const { hideCurrentModal, showModal, miscUiState } = require('../globalState') const { fsState } = require('../loadFolder') const { subscribe } = require('valtio') const { saveWorld } = require('../builtinCommands') @@ -76,6 +76,7 @@ class PauseScreen extends LitElement { singlePlayerServer.quit() } bot._client.emit('end') + miscUiState.gameLoaded = false }}> ` diff --git a/src/reactUi.jsx b/src/reactUi.jsx index 4413d7b22..5a254adb3 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -7,12 +7,13 @@ import { activeModalStack, hideCurrentModal, isGameActive } from './globalState' import { useEffect, useState } from 'react' import { useProxy } from 'valtio/utils' import useTypedEventListener from 'use-typed-event-listener' +import { isProbablyIphone } from './menus/components/common' // todo useInterfaceState.setState({ isFlying: false, uiCustomization: { - touchButtonSize: 40, + touchButtonSize: isProbablyIphone() ? 55 : 40, }, updateCoord: ([coord, state]) => { const coordToAction = [ @@ -106,21 +107,23 @@ function InventoryWrapper() { if (!isInventoryOpen) return null - return
    div { - scale: 0.6; - background: transparent !important; - } - `}> - { - bot.moveSlotItem(oldSlot, newSlotIndex) - } } /> -
    + return null + + // return
    div { + // scale: 0.6; + // background: transparent !important; + // } + // `}> + // { + // bot.moveSlotItem(oldSlot, newSlotIndex) + // } } /> + //
    } const App = () => { diff --git a/src/styles.css b/src/styles.css index 726d4aefe..3fb6fd13a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -92,12 +92,10 @@ canvas { font-family: minecraft, mojangles, monospace; } -/* todo I guess the best solution would be to render it on canvas */ -.BlockModel { - scale: 0.5; - width: 183%; - height: 190%; - transform: translate(-50%, -50%); +/* todo move this fix to lib */ +.TouchMovementArea { + grid-template-columns: "c ." + "d ."; } @media only screen and (max-width: 971px) { diff --git a/tsconfig.json b/tsconfig.json index 0977d9667..883dcf2fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "jsx": "react-jsx", "allowSyntheticDefaultImports": true, "noEmit": true, - "strictFunctionTypes": true + "strictFunctionTypes": true, + "resolveJsonModule": true // "strictNullChecks": true }, "include": [ From 5f8d8b6c662457c3a4861b24d4877c249474ae10 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 1 Sep 2023 07:21:23 +0300 Subject: [PATCH 150/318] feat(setting): auto exit fullscreen on esc --- src/index.js | 3 +++ src/menus/options_screen.js | 5 +++++ src/optionsStorage.js | 1 + 3 files changed, 9 insertions(+) diff --git a/src/index.js b/src/index.js index 9a9c08743..c415b4ba1 100644 --- a/src/index.js +++ b/src/index.js @@ -648,6 +648,9 @@ window.addEventListener('keydown', (e) => { } else { if (pointerLock.hasPointerLock) { document.exitPointerLock() + if (options.autoExitFullscreen) { + document.exitFullscreen() + } } else { document.dispatchEvent(new Event('pointerlockchange')) } diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index a57d7039f..ba0f19e8a 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -144,6 +144,11 @@ class OptionsScreen extends CommonOptionsScreen { { options.autoFullScreen = !options.autoFullScreen } + }> + + { + options.autoExitFullscreen = !options.autoExitFullscreen + } }>
    diff --git a/src/optionsStorage.js b/src/optionsStorage.js index 1fd765b68..360a84cee 100644 --- a/src/optionsStorage.js +++ b/src/optionsStorage.js @@ -16,6 +16,7 @@ const defaultOptions = { preventDevReloadWhilePlaying: false, autoFullScreen: false, mouseRawInput: false, + autoExitFullscreen: false, numWorkers: 4, localServerOptions: {}, localUsername: 'wanderer', From 077c20a1cdf5b29378f6b352e47ca969c59c78b3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 1 Sep 2023 07:51:16 +0300 Subject: [PATCH 151/318] test vercel preview deploys on ci --- .github/workflows/preview.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/preview.yml diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 000000000..159d9c2af --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,23 @@ +name: CI Deploy Preview +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +on: + workflow_dispatch: + pull_request: +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install Global Dependencies + run: npm install --global vercel pnpm + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + - name: Build Project Artifacts + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} From be01da9c964cec05041c42293aa4d63eb43ff033 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 31 Aug 2023 19:41:43 +0300 Subject: [PATCH 152/318] fix critical gameLoaded bug --- src/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index d4dcba6fc..9a9c08743 100644 --- a/src/index.js +++ b/src/index.js @@ -176,9 +176,11 @@ const pauseMenu = document.getElementById('pause-screen') function setLoadingScreenStatus (status, isError = false) { // todo update in component instead - miscUiState.gameLoaded = false showModal(loadingScreen) - if (loadingScreen.hasError) return + if (loadingScreen.hasError) { + miscUiState.gameLoaded = false + return + } loadingScreen.hasError = isError loadingScreen.status = status } From 6d1a89107bfb0afaeb6f10cd0ae8b417bae4ffbd Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 1 Sep 2023 07:23:09 +0300 Subject: [PATCH 153/318] fix all known annoying mis-keyboard reaction! fix annoying slow creative flying speed (also can be controlled via window for now) --- src/botControls.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/botControls.js b/src/botControls.js index b4649ac3a..829a0db77 100644 --- a/src/botControls.js +++ b/src/botControls.js @@ -1,7 +1,8 @@ //@ts-check const { Vec3 } = require('vec3') -const { isGameActive, showModal, gameAdditionalState } = require('./globalState') +const { isGameActive, showModal, gameAdditionalState, activeModalStack } = require('./globalState') +const { subscribe } = require('valtio') const keyBindScrn = document.getElementById('keybinds-screen') @@ -61,6 +62,7 @@ const patchedSetControlState = (action, state) => { currentFlyVector.add(toAddVec) } +const standardAirborneAcceleration = 0.02 const toggleFly = () => { if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return if (bot.setControlState !== patchedSetControlState) { @@ -69,9 +71,12 @@ const toggleFly = () => { } if (isFlying()) { + bot.physics['airborneAcceleration'] = standardAirborneAcceleration bot.creative.stopFlying() endFlyLoop?.() } else { + // window.flyingSpeed will be removed + bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 bot.entity.velocity = new Vec3(0, 0, 0) bot.creative.startFlying() startFlyLoop() @@ -79,6 +84,21 @@ const toggleFly = () => { gameAdditionalState.isFlying = isFlying() } +/** @type {Set} */ +const pressedKeys = new Set() +// window.pressedKeys = pressedKeys + +// detect pause open, as ANY keyup event is not fired when you exit pointer lock (esc) +subscribe(activeModalStack, () => { + if (activeModalStack.length) { + // iterate over pressedKeys + for (const key of pressedKeys) { + const e = new KeyboardEvent('keyup', { code: key }) + document.dispatchEvent(e) + } + } +}) + let lastJumpUsage = 0 document.addEventListener('keydown', (e) => { if (!isGameActive(true)) return @@ -124,12 +144,16 @@ document.addEventListener('keydown', (e) => { } } }) + pressedKeys.add(e.code) }, { capture: true, }) document.addEventListener('keyup', (e) => { - // if (!isGameActive(true)) return + // workaround for pause pressed keys, multiple keyboard + if (!isGameActive(false) || !pressedKeys.has(e.code)) { + return + } keyBindScrn.keymaps.forEach(km => { if (e.code === km.key) { @@ -165,4 +189,5 @@ document.addEventListener('keyup', (e) => { } } }) + pressedKeys.delete(e.code) }, false) From 8a948d14ea157dca1a438aa1ebf0991c466fd752 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 1 Sep 2023 08:35:08 +0300 Subject: [PATCH 154/318] oops fix prevuilt target --- .github/workflows/preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 159d9c2af..5235072b4 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -18,6 +18,6 @@ jobs: - name: Pull Vercel Environment Information run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project Artifacts - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy Project Artifacts to Vercel run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} From 9b71088c72925d5b305e765ea92e96ff43156ce6 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 1 Sep 2023 08:55:03 +0300 Subject: [PATCH 155/318] vercel: get correct env --- .github/workflows/preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 5235072b4..ee83ba249 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -16,7 +16,7 @@ jobs: - name: Install Global Dependencies run: npm install --global vercel pnpm - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project Artifacts run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy Project Artifacts to Vercel From 670c56094047847072d5d3d6acee390d7d55ddff Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 02:33:03 +0300 Subject: [PATCH 156/318] fix dragndrop files in firefox and / action --- src/chat.js | 1 + src/dragndrop.ts | 47 +++++++++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/chat.js b/src/chat.js index 7ee032d86..2497d09d5 100644 --- a/src/chat.js +++ b/src/chat.js @@ -240,6 +240,7 @@ class ChatBox extends LitElement { break case 'Slash': setTimeout(() => this.enableChat('/'), 0) + e.preventDefault() break } } diff --git a/src/dragndrop.ts b/src/dragndrop.ts index 3b62e307c..bcb8d5711 100644 --- a/src/dragndrop.ts +++ b/src/dragndrop.ts @@ -19,30 +19,37 @@ window.nbt = nbt; }) window.addEventListener("drop", async e => { if (!e.dataTransfer?.files.length) return - // todo support drop save folder const { items } = e.dataTransfer const item = items[0] - const filehandle = await item.getAsFileSystemHandle() as FileSystemFileHandle | FileSystemDirectoryHandle - if (filehandle.kind === 'file') { - const file = await filehandle.getFile() + if (item.getAsFileSystemHandle) { + const filehandle = await item.getAsFileSystemHandle() as FileSystemFileHandle | FileSystemDirectoryHandle + if (filehandle.kind === 'file') { + const file = await filehandle.getFile() - if (file.name.endsWith('.zip')) { - openWorldZip(file) - return + await handleDroppedFile(file) + } else { + if (isGameActive(false)) { + alert('Exit current world first, before loading a new one.') + return + } + await openWorldDirectory(filehandle as FileSystemDirectoryHandle) } - - const buffer = await file.arrayBuffer() - const parsed = await parseNbt(Buffer.from(buffer)) - showNotification({ - message: `${file.name} data available in browser console`, - }) - console.log('raw', parsed) - console.log('simplified', nbt.simplify(parsed)) } else { - if (isGameActive(false)) { - alert('Exit current world first, before loading a new one.') - return - } - await openWorldDirectory(filehandle as FileSystemDirectoryHandle) + await handleDroppedFile(item.getAsFile()) } }) + +async function handleDroppedFile(file: File) { + if (file.name.endsWith('.zip')) { + openWorldZip(file) + return + } + + const buffer = await file.arrayBuffer() + const parsed = await parseNbt(Buffer.from(buffer)) + showNotification({ + message: `${file.name} data available in browser console`, + }) + console.log('raw', parsed) + console.log('simplified', nbt.simplify(parsed)) +} From 9976b9a3f67ce30730aab58073c179876601d595 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 03:34:50 +0300 Subject: [PATCH 157/318] download minecraft-data on demand --- package.json | 9 +++-- scripts/esbuildPlugins.mjs | 37 +++++++++--------- scripts/prepareData.mjs | 79 ++++++++++++++++++++++++++++++++++++++ src/index.js | 7 ++++ src/utils.js | 27 +++++++++++++ 5 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 scripts/prepareData.mjs diff --git a/package.json b/package.json index d97aadcce..2d97f03c6 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "1.5.0", "description": "A minecraft client running in a browser", "scripts": { - "start": "node scripts/build.js copyFilesDev && node esbuild.mjs --minify --watch", + "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --minify --watch", "start-watch-script": "nodemon -w esbuild.mjs esbuild.mjs", - "build": "node scripts/build.js copyFiles && node esbuild.mjs --minify --prod", + "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs && node esbuild.mjs --minify --prod", "watch": "node scripts/build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", "test:cypress": "cypress run", "test:e2e": "start-test http-get://localhost:8080 test:cypress", @@ -32,6 +32,7 @@ "compression": "^1.7.4", "cypress-plugin-snapshots": "^1.4.4", "diamond-square": "^1.2.0", + "eruda": "^3.0.1", "esbuild": "^0.19.2", "esbuild-loader": "^4.0.0", "esbuild-plugin-polyfill-node": "^0.3.0", @@ -73,6 +74,7 @@ "https-browserify": "^1.0.0", "lodash-webpack-plugin": "^0.11.6", "memfs": "^3.5.3", + "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#custom", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", @@ -91,8 +93,7 @@ "webpack-dev-middleware": "^6.1.1", "webpack-dev-server": "^4.15.1", "webpack-merge": "^5.9.0", - "workbox-webpack-plugin": "^6.6.0", - "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next" + "workbox-webpack-plugin": "^6.6.0" }, "pnpm": { "overrides": { diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index 42ca70b9e..ae4193666 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -20,24 +20,24 @@ const plugins = [ path: (await build.resolve('minecraft-protocol/src/index.js', { kind, resolveDir })).path, } }) - // build.onResolve({ - // filter: /^\.\/data.js$/, - // }, ({ resolveDir, path }) => { - // if (!resolveDir.endsWith('minecraft-data')) return - // return { - // namespace: 'load-global-minecraft-data', - // path - // } - // }) - // build.onLoad({ - // filter: /.+/, - // namespace: 'load-global-minecraft-data', - // }, () => { - // return { - // contents: 'module.exports = window.minecraftData', - // loader: 'js', - // } - // }) + build.onResolve({ + filter: /^\.\/data.js$/, + }, ({ resolveDir, path }) => { + if (!resolveDir.endsWith('minecraft-data')) return + return { + namespace: 'load-global-minecraft-data', + path + } + }) + build.onLoad({ + filter: /.+/, + namespace: 'load-global-minecraft-data', + }, () => { + return { + contents: 'window.mcData ??= {};module.exports = { pc: window.mcData }', + loader: 'js', + } + }) // build.onResolve({ // filter: /^minecraft-assets$/, // }, ({ resolveDir, path }) => { @@ -89,6 +89,7 @@ const plugins = [ filter: /.*/, namespace: customMcDataNs, }, async ({ path, ...rest }) => { + throw new Error('unreachable') const resolvedPath = await build.resolve('minecraft-data/minecraft-data/data/dataPaths.json', { kind: 'require-call', resolveDir: process.cwd() }) const dataPaths = JSON.parse(await fs.promises.readFile(resolvedPath.path, 'utf8')) diff --git a/scripts/prepareData.mjs b/scripts/prepareData.mjs new file mode 100644 index 000000000..2953736ca --- /dev/null +++ b/scripts/prepareData.mjs @@ -0,0 +1,79 @@ +//@ts-check +import { build } from 'esbuild' +import Module from "node:module" +import { dirname } from 'node:path' + +const require = Module.createRequire(import.meta.url) + +const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json') + +function toMajor (version) { + const [a, b] = (version + '').split('.') + return `${a}.${b}` +} + +const ignoredVersionsRegex = /(^0\.30c$)|w|-pre|-rc/ + +const grouped = {} + +for (const [version, data] of Object.entries(dataPaths.pc)) { + if (ignoredVersionsRegex.test(version)) continue + const major = toMajor(version) + grouped[major] ??= {} + grouped[major][version] = data +} + +console.log('preparing data') +for (const [major, versions] of Object.entries(grouped)) { + let contents = 'Object.assign(window.mcData, {\n' + for (const [version, dataSet] of Object.entries(versions)) { + contents += ` '${version}': {\n` + for (const [dataType, dataPath] of Object.entries(dataSet)) { + const loc = `minecraft-data/data/${dataPath}/` + contents += ` get ${dataType} () { return require("./${loc}${dataType}.json") },\n` + } + contents += ' },\n' + } + contents += '})' + + build({ + bundle: true, + outfile: `dist/mc-data/${major}.js`, + // entryPoints: ['mcData.js'], + stdin: { + contents, + + resolveDir: dirname(require.resolve('minecraft-data')), + sourcefile: `mcData${major}.js`, + loader: 'js', + }, + plugins: [ + // { + // name: 'mcData', + // setup (build) { + // build.onResolve({ + // filter: /^mcData\.js$/, + // }, ({ path, pluginData }) => { + // return { + // path, + // namespace: 'mc-data', + // } + // }) + // build.onLoad({ + // filter: /.*/, + // namespace: 'mc-data', + // }, ({ namespace, path }) => { + // const version = path.split('/')[2] + // const data = versions[version] + // if (!data) throw new Error(`No data for ${version}`) + // return { + // contents: `export const data = ${JSON.stringify(data, null, 2)}`, + // loader: 'js', + // } + // }) + // } + // } + ], + }) +} +console.log('data prepared') diff --git a/src/index.js b/src/index.js index c415b4ba1..91d51bff4 100644 --- a/src/index.js +++ b/src/index.js @@ -366,6 +366,13 @@ async function connect (connectOptions) { }) let singlePlayerServer try { + Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides, options.localServerOptions, serverOptions)) + let version = connectOptions.botVersion ?? serverOptions.version + if (version) { + setLoadingScreenStatus(`Downloading data for ${version}`) + await loadScript(`./mc-data/${toMajorVersion(version)}.js`) + } + if (singeplayer) { window.serverDataChannel ??= {} window.worldLoaded = false diff --git a/src/utils.js b/src/utils.js index aec751cee..411e92763 100644 --- a/src/utils.js +++ b/src/utils.js @@ -132,3 +132,30 @@ function javaUUID (s) { export function nameToMcOfflineUUID (name) { return (new UUID(javaUUID('OfflinePlayer:' + name))).toString() } +export const loadScript = async function (/** @type {string} */ scriptSrc) { + if (document.querySelector(`script[src="${scriptSrc}"]`)) { + return + } + + return new Promise((resolve, reject) => { + const scriptElement = document.createElement('script') + scriptElement.src = scriptSrc + scriptElement.async = true + + scriptElement.onload = () => { + resolve(scriptElement) + } + + scriptElement.onerror = (error) => { + reject(error) + } + + document.head.appendChild(scriptElement) + }) +} + +// doesn't support snapshots +export const toMajorVersion = (version) => { + const [a, b] = (version + '').split('.') + return `${a}.${b}` +} From 190c511e544a003b2e7f42599a4ef1e2929c31ae Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 03:37:28 +0300 Subject: [PATCH 158/318] allow to load worlf from ?map= qs url --- src/browserfs.js | 6 +++--- src/downloadAndOpenWorld.ts | 36 ++++++++++++++++++++++++++++++++++++ src/index.js | 18 ++++-------------- src/loadFolder.ts | 6 ++++-- 4 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 src/downloadAndOpenWorld.ts diff --git a/src/browserfs.js b/src/browserfs.js index f5f34c084..ca51aca3d 100644 --- a/src/browserfs.js +++ b/src/browserfs.js @@ -152,7 +152,7 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} loadFolder() } -export const openWorldZip = async (/** @type {File} */file) => { +export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = file['name']) => { await new Promise(async resolve => { browserfs.configure({ // todo @@ -161,8 +161,8 @@ export const openWorldZip = async (/** @type {File} */file) => { "/world": { fs: "ZipFS", options: { - zipData: Buffer.from(await file.arrayBuffer()), - name: file.name + zipData: Buffer.from(file instanceof File ? (await file.arrayBuffer()) : file), + name } } }, diff --git a/src/downloadAndOpenWorld.ts b/src/downloadAndOpenWorld.ts new file mode 100644 index 000000000..e14e4deb1 --- /dev/null +++ b/src/downloadAndOpenWorld.ts @@ -0,0 +1,36 @@ +import { openWorldZip } from './browserfs' +import { setLoadingScreenStatus } from './utils' +import { filesize } from 'filesize' + +window.addEventListener('load', async (e) => { + const qs = new URLSearchParams(window.location.search) + const mapUrl = qs.get('map') + if (!mapUrl) return + + const menu = document.getElementById('play-screen') + menu.style = 'display: none;' + const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-25) + setLoadingScreenStatus(`Downloading world ${name}...`) + + const response = await fetch(mapUrl) + const contentLength = +response.headers.get('Content-Length') + setLoadingScreenStatus(`Downloading world ${name}: have to download ${filesize(contentLength)}...`) + // const reader = response.body!.getReader() + // let doneValue + // while (true) { + // // done is true for the last chunk + // // value is Uint8Array of the chunk bytes + // const { done, value } = await reader.read() + + // if (done) { + // doneValue = value + // break + // } + + // setLoadingScreenStatus(`Downloading world ${name}: ${filesize(value.length)} / ${filesize(contentLength)}MB...`) + // } + await openWorldZip(await response.arrayBuffer()) +}) + +export default async () => { +} diff --git a/src/index.js b/src/index.js index 91d51bff4..659aea6b0 100644 --- a/src/index.js +++ b/src/index.js @@ -40,6 +40,8 @@ require('./reactUi.jsx') require('./botControls') require('./dragndrop') require('./browserfs') +require('./eruda') +require('./downloadAndOpenWorld') const net = require('net') const Stats = require('stats.js') @@ -56,7 +58,7 @@ const Cursor = require('./cursor').default global.THREE = require('three') const { initVR } = require('./vr') const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalStacks, replaceActiveModalStack, isGameActive, miscUiState, gameAdditionalState } = require('./globalState') -const { pointerLock, goFullscreen, toNumber, isCypress } = require('./utils') +const { pointerLock, goFullscreen, toNumber, isCypress, loadScript, toMajorVersion, setLoadingScreenStatus } = require('./utils') const { notification } = require('./menus/notification') const { removePanorama, addPanoramaCubeMap, initPanoramaOptions } = require('./panorama') const { startLocalServer, unsupportedLocalServerFeatures } = require('./createLocalServer') @@ -174,17 +176,6 @@ const hud = document.getElementById('hud') const optionsScrn = document.getElementById('options-screen') const pauseMenu = document.getElementById('pause-screen') -function setLoadingScreenStatus (status, isError = false) { - // todo update in component instead - showModal(loadingScreen) - if (loadingScreen.hasError) { - miscUiState.gameLoaded = false - return - } - loadingScreen.hasError = isError - loadingScreen.status = status -} - let mouseMovePostHandle = (e) => { } let lastMouseCall function onMouseMove (e) { @@ -366,7 +357,7 @@ async function connect (connectOptions) { }) let singlePlayerServer try { - Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides, options.localServerOptions, serverOptions)) + Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) let version = connectOptions.botVersion ?? serverOptions.version if (version) { setLoadingScreenStatus(`Downloading data for ${version}`) @@ -376,7 +367,6 @@ async function connect (connectOptions) { if (singeplayer) { window.serverDataChannel ??= {} window.worldLoaded = false - Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides, options.localServerOptions, serverOptions)) singlePlayerServer = window.singlePlayerServer = startLocalServer() // todo need just to call quit if started // loadingScreen.maybeRecoverable = false diff --git a/src/loadFolder.ts b/src/loadFolder.ts index 14d7f6861..6b7a3e5d9 100644 --- a/src/loadFolder.ts +++ b/src/loadFolder.ts @@ -18,6 +18,7 @@ export const fsState = proxy({ const PROPOSE_BACKUP = true +// todo rename to loadWorld export const loadFolder = async (root = '/world') => { // todo do it in singleplayer as well for (const key in forceCachedDataPaths) { @@ -47,7 +48,8 @@ export const loadFolder = async (root = '/world') => { const parsedRaw = await parseNbt(Buffer.from(levelDatContent)) const levelDat: import('./mcTypes').LevelDat = nbt.simplify(parsedRaw).Data - version = levelDat.Version?.Name + const qs = new URLSearchParams(window.location.search) + version = levelDat.Version?.Name ?? qs.get('version') if (!version) { const newVersion = prompt(`In 1.8 and before world save doesn\'t contain version info, please enter version you want to use to load the world.\nSupported versions ${supportedVersions.join(', ')}`, '1.8.8') if (!newVersion) return @@ -69,7 +71,7 @@ export const loadFolder = async (root = '/world') => { if (levelDat.generatorName) { isFlat = levelDat.generatorName === 'flat' } - if (!isFlat) { + if (!isFlat && levelDat.generatorName !== 'default' && levelDat.generatorName !== 'customized') { warnings.push(`Generator ${levelDat.generatorName} may not be supported yet`) } From 9787f798fb0b0ca4ac80b3eec523df766c28b7d8 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 03:37:53 +0300 Subject: [PATCH 159/318] refactor to utils for fixes --- src/menus/pause_screen.js | 8 ++------ src/utils.js | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/menus/pause_screen.js b/src/menus/pause_screen.js index aecdf6ccf..a863198b4 100644 --- a/src/menus/pause_screen.js +++ b/src/menus/pause_screen.js @@ -6,6 +6,7 @@ const { fsState } = require('../loadFolder') const { subscribe } = require('valtio') const { saveWorld } = require('../builtinCommands') const { notification } = require('./notification') +const { disconnect } = require('../utils') class PauseScreen extends LitElement { static get styles () { @@ -71,12 +72,7 @@ class PauseScreen extends LitElement {
    showModal(document.getElementById('options-screen'))}> { - if (window.singlePlayerServer) { - await saveWorld() - singlePlayerServer.quit() - } - bot._client.emit('end') - miscUiState.gameLoaded = false + disconnect() }}> ` diff --git a/src/utils.js b/src/utils.js index 411e92763..f48313929 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,9 +1,10 @@ //@ts-check -import { activeModalStack, miscUiState } from './globalState' +import { activeModalStack, miscUiState, showModal } from './globalState' import { notification } from './menus/notification' import * as crypto from 'crypto' import UUID from 'uuid-1345' import { options } from './optionsStorage' +import { saveWorld } from './builtinCommands' export const goFullscreen = async (doToggle = false) => { if (!document.fullscreenElement) { @@ -132,6 +133,30 @@ function javaUUID (s) { export function nameToMcOfflineUUID (name) { return (new UUID(javaUUID('OfflinePlayer:' + name))).toString() } + +export const setLoadingScreenStatus = function (/** @type {string} */status, isError = false) { + const loadingScreen = document.getElementById('loading-error-screen') + + // todo update in component instead + showModal(loadingScreen) + if (loadingScreen.hasError) { + miscUiState.gameLoaded = false + return + } + loadingScreen.hasError = isError + loadingScreen.status = status +} + + +export const disconnect = async () => { + if (window.singlePlayerServer) { + await saveWorld() + singlePlayerServer.quit() + } + bot._client.emit('end') + miscUiState.gameLoaded = false +} + export const loadScript = async function (/** @type {string} */ scriptSrc) { if (document.querySelector(`script[src="${scriptSrc}"]`)) { return From f92c1f7fc42b21499e6636223a288804262e7c19 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 09:19:48 +0300 Subject: [PATCH 160/318] add eruda --- src/eruda.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/eruda.js diff --git a/src/eruda.js b/src/eruda.js new file mode 100644 index 000000000..fd21fde5d --- /dev/null +++ b/src/eruda.js @@ -0,0 +1,4 @@ +if (process.env.NODE_ENV === 'development') { + require('eruda').default.init() + console.log('JS Loaded in', Date.now() - window.startLoad) +} From b9f621de76752008edfe4a987f3b6339c2443aa3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Sep 2023 09:32:42 +0300 Subject: [PATCH 161/318] cleanup preparedata --- scripts/prepareData.mjs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/scripts/prepareData.mjs b/scripts/prepareData.mjs index 2953736ca..a8e19ab7c 100644 --- a/scripts/prepareData.mjs +++ b/scripts/prepareData.mjs @@ -36,10 +36,9 @@ for (const [major, versions] of Object.entries(grouped)) { } contents += '})' - build({ + await build({ bundle: true, outfile: `dist/mc-data/${major}.js`, - // entryPoints: ['mcData.js'], stdin: { contents, @@ -47,33 +46,6 @@ for (const [major, versions] of Object.entries(grouped)) { sourcefile: `mcData${major}.js`, loader: 'js', }, - plugins: [ - // { - // name: 'mcData', - // setup (build) { - // build.onResolve({ - // filter: /^mcData\.js$/, - // }, ({ path, pluginData }) => { - // return { - // path, - // namespace: 'mc-data', - // } - // }) - // build.onLoad({ - // filter: /.*/, - // namespace: 'mc-data', - // }, ({ namespace, path }) => { - // const version = path.split('/')[2] - // const data = versions[version] - // if (!data) throw new Error(`No data for ${version}`) - // return { - // contents: `export const data = ${JSON.stringify(data, null, 2)}`, - // loader: 'js', - // } - // }) - // } - // } - ], }) } console.log('data prepared') From fc9fc8b308ab01c836d70d920c5990875675b0e5 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 10:04:26 +0300 Subject: [PATCH 162/318] try to fix with css loading with webpack + new space-squid esbuild require --- package.json | 3 +++ webpack.common.js | 31 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2d97f03c6..a6007b5c2 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "constants-browserify": "^1.0.0", "copy-webpack-plugin": "^11.0.0", "crypto-browserify": "^3.12.0", + "css-loader": "^6.8.1", "cypress": "^9.5.4", "cypress-esbuild-preprocessor": "^1.0.2", "events": "^3.3.0", @@ -84,8 +85,10 @@ "process": "github:PrismarineJS/node-process", "rimraf": "^5.0.1", "stream-browserify": "^3.0.0", + "style-loader": "^3.3.3", "three": "0.128.0", "timers-browserify": "^2.0.12", + "url-loader": "^4.1.1", "use-typed-event-listener": "^4.0.2", "vite": "^4.4.9", "webpack": "^5.88.2", diff --git a/webpack.common.js b/webpack.common.js index 58a6d9949..0d4308685 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -29,7 +29,9 @@ const config = { ), // Hack to allow creating the client in a browser express: false, net: 'net-browserify', - 'valtio/utils$': require.resolve('valtio/vanilla/utils'), + 'valtio$': require.resolve('./valtio.js'), + 'valtio/vanilla$': require.resolve('./valtio.js'), + 'valtio/utils$': require.resolve('./valtio.js'), jose: false }, fallback: { @@ -54,7 +56,9 @@ const config = { extensions: [ '.js', '.ts', - '.json' + '.json', + '.jsx', + '.tsx' ], }, module: { @@ -67,7 +71,30 @@ const config = { // target: 'es2015' // } }, + { + test: /\.(png|jpg|gif|svg)$/i, + use: [ + { + loader: 'url-loader', + options: { + limit: true, + }, + }, + ], + }, + { + test: /\.css$/i, + use: ["style-loader", { + loader: 'css-loader', + options: { url: false } + }], + }, ], + parser: { + javascript: { + commonjsMagicComments: true, + }, + }, }, plugins: [ new HtmlWebpackPlugin({ From 47f86ca86dea05214da496ed20e18c30f56d02af Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 13:19:20 +0300 Subject: [PATCH 163/318] fix inventory & hotbar crash on unknown itme --- src/inventory.ts | 2 +- src/menus/components/hotbar.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inventory.ts b/src/inventory.ts index 407ed0311..7ec34773c 100644 --- a/src/inventory.ts +++ b/src/inventory.ts @@ -64,7 +64,7 @@ const getItemSlice = (name) => { const invspriteImg = loadedImages.get('invsprite') if (!invspriteImg || !invspriteImg.width) return - const { x, y } = invspriteJson[name] + const { x, y } = invspriteJson[name] ?? /* unknown item */ { x: 0, y: 0 } const sprite = [x, y, 32, 32] return sprite } diff --git a/src/menus/components/hotbar.js b/src/menus/components/hotbar.js index 637fd4d96..948fae11c 100644 --- a/src/menus/components/hotbar.js +++ b/src/menus/components/hotbar.js @@ -148,7 +148,7 @@ class Hotbar extends LitElement { if (slot >= this.bot.inventory.hotbarStart + 9) return if (slot < this.bot.inventory.hotbarStart) return - const sprite = newItem ? invsprite[newItem.name] : invsprite.air + const sprite = newItem ? invsprite[newItem.name] ?? { x: 0, y: 0 } : invsprite.air const slotEl = this.shadowRoot.getElementById('hotbar-' + (slot - this.bot.inventory.hotbarStart)) const slotIcon = slotEl.children[0] const slotStack = slotEl.children[1] @@ -161,7 +161,7 @@ class Hotbar extends LitElement { async reloadHotbar () { for (let i = 0; i < 9; i++) { const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + i] - const sprite = item ? invsprite[item.name] : invsprite.air + const sprite = item ? invsprite[item.name] ?? { x: 0, y: 0 } : invsprite.air const slotEl = this.shadowRoot.getElementById('hotbar-' + i) const slotIcon = slotEl.children[0] const slotStack = slotEl.children[1] From f897a6e6806613c59282dd38ae3cd981911f2778 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 13:19:47 +0300 Subject: [PATCH 164/318] add firefox attach config that I don't use --- .vscode/launch.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7b9f4733f..abdc541bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -43,5 +43,18 @@ "/**/*minecraftData*" ], }, + { + // to launch "C:\Program Files\Mozilla Firefox\firefox.exe" -start-debugger-server + "type": "firefox", + "name": "Attach Firefox", + "request": "attach", + // comment if using webpack + "url": "http://localhost:8080/", + "webRoot": "${workspaceFolder}/", + "skipFiles": [ + // "/**/*vendors*" + "/**/*minecraftData*" + ], + }, ] } From 0c1607b5505d7c96b9b04b1575b74e10a71e6c88 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 13:20:26 +0300 Subject: [PATCH 165/318] add singleplayer explainer a few load steps --- src/index.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/index.js b/src/index.js index 659aea6b0..c59ea2da7 100644 --- a/src/index.js +++ b/src/index.js @@ -365,6 +365,18 @@ async function connect (connectOptions) { } if (singeplayer) { + // SINGLEPLAYER EXPLAINER: + // Note 1: here we have custom sync communication between server Client (flying-squid) and game client (mineflayer) + // Note 2: custom Server class is used which simplifies communication & Client creation on it's side + // local server started + // mineflayer.createBot (see source def) + // bot._client = bot._client ?? mc.createClient(options) <-- mc-protocol package + // tcpDns() skipped since we define connect option + // in setProtocol: we emit 'connect' here below so in that file we send set_protocol and login_start (onLogin handler) + // Client (class) of flying-squid (in server/login.js of mc-protocol): onLogin handler: skip most logic & go to loginClient() which assigns uuid and sends 'success' back to client (onLogin handler) and emits 'login' on the server (login.js in flying-squid handler) + // flying-squid: 'login' -> player.login -> now sends 'login' event to the client (handled in many plugins in mineflayer) -> then 'update_health' is sent which emits 'spawn' + + setLoadingScreenStatus('Starting local server') window.serverDataChannel ??= {} window.worldLoaded = false singlePlayerServer = window.singlePlayerServer = startLocalServer() @@ -378,6 +390,7 @@ async function connect (connectOptions) { } } + setLoadingScreenStatus('Creating mineflayer bot') bot = mineflayer.createBot({ host, port, From e51e7c2327e647f7e51fbb818d310c357d5943bc Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 17:27:20 +0300 Subject: [PATCH 166/318] do not bundle large assets package, cleanup esbuildPlugins --- scripts/esbuildPlugins.mjs | 78 +++----------------------------------- src/menus/play_screen.js | 12 +++--- 2 files changed, 12 insertions(+), 78 deletions(-) diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index ae4193666..eccfd89f3 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -38,28 +38,11 @@ const plugins = [ loader: 'js', } }) - // build.onResolve({ - // filter: /^minecraft-assets$/, - // }, ({ resolveDir, path }) => { - // // if (!resolveDir.endsWith('minecraft-data')) return - // return { - // namespace: 'load-global-minecraft-assets', - // path - // } - // }) - // build.onLoad({ - // filter: /.+/, - // namespace: 'load-global-minecraft-assets', - // }, async () => { - // const resolvedPath = await build.resolve('minecraft-assets/index.js', { kind: 'require-call', resolveDir: process.cwd() }) - // let contents = (await fs.promises.readFile(resolvedPath.path, 'utf8')) - // contents = contents.slice(0, contents.indexOf('const data = ')) + 'const data = window.minecraftAssets;' + contents.slice(contents.indexOf('module.exports.versions')) - // return { - // contents, - // loader: 'js', - // resolveDir: dirname(resolvedPath.path), - // } - // }) + build.onResolve({ + filter: /^minecraft-assets$/, + }, () => { + throw new Error('hit banned package') + }) } }, { @@ -71,10 +54,7 @@ const plugins = [ filter: /.*/, }, async ({ path, ...rest }) => { if (join(rest.resolveDir, path).replaceAll('\\', '/').endsWith('minecraft-data/data.js')) { - return { - namespace: customMcDataNs, - path - } + throw new Error('Should not hit') } if (['.woff', '.woff2', '.ttf'].some(ext => path.endsWith(ext))) { return { @@ -85,52 +65,6 @@ const plugins = [ } }) - build.onLoad({ - filter: /.*/, - namespace: customMcDataNs, - }, async ({ path, ...rest }) => { - throw new Error('unreachable') - const resolvedPath = await build.resolve('minecraft-data/minecraft-data/data/dataPaths.json', { kind: 'require-call', resolveDir: process.cwd() }) - const dataPaths = JSON.parse(await fs.promises.readFile(resolvedPath.path, 'utf8')) - - // bedrock unsupported - delete dataPaths.bedrock - - const allowOnlyList = process.env.ONLY_MC_DATA?.split(',') ?? [] - - // skip data for 0.30c, snapshots and pre-releases - const ignoredVersionsRegex = /(^0\.30c$)|w|-pre|-rc/ - - const includedVersions = [] - let contents = 'module.exports =\n{\n' - for (const platform of Object.keys(dataPaths)) { - contents += ` '${platform}': {\n` - for (const version of Object.keys(dataPaths[platform])) { - if (allowOnlyList.length && !allowOnlyList.includes(version)) continue - if (ignoredVersionsRegex.test(version)) continue - - includedVersions.push(version) - contents += ` '${version}': {\n` - for (const dataType of Object.keys(dataPaths[platform][version])) { - const loc = `minecraft-data/data/${dataPaths[platform][version][dataType]}/` - contents += ` get ${dataType} () { return require("./${loc}${dataType}.json") },\n` - } - contents += ' },\n' - } - contents += ' },\n' - } - contents += '}\n' - - if (prod) { - console.log('Included mc-data versions:', includedVersions) - } - return { - contents, - loader: 'js', - resolveDir: join(dirname(resolvedPath.path), '../..'), - } - }) - build.onEnd(async ({ metafile, outputFiles }) => { // write outputFiles for (const file of outputFiles) { diff --git a/src/menus/play_screen.js b/src/menus/play_screen.js index 81e7d53f8..0cdcf3cda 100644 --- a/src/menus/play_screen.js +++ b/src/menus/play_screen.js @@ -1,12 +1,12 @@ +//@ts-check const { LitElement, html, css } = require('lit') const { commonCss } = require('./components/common') const { hideCurrentModal } = require('../globalState') -const mcAssets = require("minecraft-assets") -const data = require('minecraft-data') const mineflayer = require('mineflayer') +const viewerSupportedVersions = require('prismarine-viewer/viewer/supportedVersions.json') -const fullySupporedVersions = mcAssets.versions -const partialSupportVersions = mineflayer.supportedVersions +const fullySupporedVersions = viewerSupportedVersions +const partiallySupportVersions = mineflayer.supportedVersions class PlayScreen extends LitElement { static get styles () { @@ -175,8 +175,8 @@ class PlayScreen extends LitElement { pmui-id="botversion" pmui-value="${this.version}" pmui-inputmode="decimal" - state="${this.version && (fullySupporedVersions.includes(this.version) ? '' : /* TODO improve check: check exact including all */ partialSupportVersions.some(v => this.version.startsWith(v)) ? 'warning' : 'invalid')}" - .autocompleteValues=${mcAssets.versions} + state="${this.version && (fullySupporedVersions.includes(/** @type {any} */(this.version)) ? '' : /* TODO improve check: check exact including all */ partiallySupportVersions.some(v => this.version.startsWith(v)) ? 'warning' : 'invalid')}" + .autocompleteValues=${fullySupporedVersions} @input=${e => { this.version = e.target.value }} >
    From 6faa3c63beb0c9757dcd63d76b5bae067743463b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 18:41:44 +0300 Subject: [PATCH 167/318] speed preparedata --- scripts/prepareData.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/prepareData.mjs b/scripts/prepareData.mjs index a8e19ab7c..f128f3110 100644 --- a/scripts/prepareData.mjs +++ b/scripts/prepareData.mjs @@ -24,6 +24,8 @@ for (const [version, data] of Object.entries(dataPaths.pc)) { } console.log('preparing data') +console.time('data prepared') +let builds = [] for (const [major, versions] of Object.entries(grouped)) { let contents = 'Object.assign(window.mcData, {\n' for (const [version, dataSet] of Object.entries(versions)) { @@ -36,7 +38,7 @@ for (const [major, versions] of Object.entries(grouped)) { } contents += '})' - await build({ + const promise = build({ bundle: true, outfile: `dist/mc-data/${major}.js`, stdin: { @@ -47,5 +49,7 @@ for (const [major, versions] of Object.entries(grouped)) { loader: 'js', }, }) + builds.push(promise) } -console.log('data prepared') +await Promise.all(builds) +console.timeEnd('data prepared') From 734ecdf3f6a9aa34da3aa1c7457245665a3f92c3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 3 Sep 2023 18:42:29 +0300 Subject: [PATCH 168/318] fix always showing mobile inventory button --- src/menus/components/hotbar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menus/components/hotbar.js b/src/menus/components/hotbar.js index 948fae11c..216454c37 100644 --- a/src/menus/components/hotbar.js +++ b/src/menus/components/hotbar.js @@ -224,9 +224,9 @@ class Hotbar extends LitElement {
    -
    { + ${miscUiState.currentTouch && html`
    { showModal({ reactType: 'inventory', }) - }}> + }}>`}
    From 54d6598ac91f510d03b7f56900179d4b7af6dde0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:39:16 +0300 Subject: [PATCH 169/318] refactor controls to use contro-max! it allows to use between mobile keyboard & gamepad consistently! fixed double jump! --- .gitignore | 3 +- .vscode/launch.json | 1 + package.json | 1 + src/botControls.js | 193 ------------------------ src/chat.js | 16 -- src/controls.ts | 266 +++++++++++++++++++++++++++++++++ src/index.js | 6 +- src/menus/components/hotbar.js | 4 +- src/menus/options_screen.js | 2 +- src/reactUi.jsx | 83 ++-------- tsconfig.json | 3 +- 11 files changed, 291 insertions(+), 287 deletions(-) delete mode 100644 src/botControls.js create mode 100644 src/controls.ts diff --git a/.gitignore b/.gitignore index 23b666f87..950306c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules/ package-lock.json .vscode -public +**/public *.log .env.local Thumbs.db @@ -13,3 +13,4 @@ dist world out *.iml +.vercel diff --git a/.vscode/launch.json b/.vscode/launch.json index abdc541bc..87e66901e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,6 @@ { "configurations": [ + // UPDATED: all configs below are misconfigured and will crash vscode, open dist/index.html and use live preview debug instead // recommended as much faster { // to launch "C:\Program Files\Google\Chrome Beta\Application\chrome.exe" --remote-debugging-port=9222 diff --git a/package.json b/package.json index a6007b5c2..ba7869023 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "buffer": "^6.0.3", "clean-webpack-plugin": "^4.0.0", "constants-browserify": "^1.0.0", + "contro-max": "^0.1.0", "copy-webpack-plugin": "^11.0.0", "crypto-browserify": "^3.12.0", "css-loader": "^6.8.1", diff --git a/src/botControls.js b/src/botControls.js deleted file mode 100644 index 829a0db77..000000000 --- a/src/botControls.js +++ /dev/null @@ -1,193 +0,0 @@ -//@ts-check - -const { Vec3 } = require('vec3') -const { isGameActive, showModal, gameAdditionalState, activeModalStack } = require('./globalState') -const { subscribe } = require('valtio') - -const keyBindScrn = document.getElementById('keybinds-screen') - -// these controls are for gamemode 3 actually - -const makeInterval = (fn, interval) => { - const intervalId = setInterval(fn, interval) - - const cleanup = () => { - clearInterval(intervalId) - cleanup.active = false - } - cleanup.active = true - return cleanup -} - -const flySpeedMult = 0.5 - -const isFlying = () => bot.physics.gravity === 0 -/** @type {ReturnType|undefined} */ -let endFlyLoop - -const currentFlyVector = new Vec3(0, 0, 0) -window.currentFlyVector = currentFlyVector - -const startFlyLoop = () => { - if (!isFlying()) return - endFlyLoop?.() - - endFlyLoop = makeInterval(() => { - if (!window.bot) endFlyLoop() - bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(flySpeedMult, flySpeedMult, flySpeedMult))) - }, 50) -} - -// todo we will get rid of patching it when refactor controls -let originalSetControlState -const patchedSetControlState = (action, state) => { - if (!isFlying()) { - return originalSetControlState(action, state) - } - - const actionPerFlyVector = { - jump: new Vec3(0, 1, 0), - sneak: new Vec3(0, -1, 0), - } - - const changeVec = actionPerFlyVector[action] - if (!changeVec) { - return originalSetControlState(action, state) - } - const toAddVec = changeVec.scaled(state ? 1 : -1) - for (const coord of ['x', 'y', 'z']) { - if (toAddVec[coord] === 0) continue - if (currentFlyVector[coord] === toAddVec[coord]) return - } - currentFlyVector.add(toAddVec) -} - -const standardAirborneAcceleration = 0.02 -const toggleFly = () => { - if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return - if (bot.setControlState !== patchedSetControlState) { - originalSetControlState = bot.setControlState - bot.setControlState = patchedSetControlState - } - - if (isFlying()) { - bot.physics['airborneAcceleration'] = standardAirborneAcceleration - bot.creative.stopFlying() - endFlyLoop?.() - } else { - // window.flyingSpeed will be removed - bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 - bot.entity.velocity = new Vec3(0, 0, 0) - bot.creative.startFlying() - startFlyLoop() - } - gameAdditionalState.isFlying = isFlying() -} - -/** @type {Set} */ -const pressedKeys = new Set() -// window.pressedKeys = pressedKeys - -// detect pause open, as ANY keyup event is not fired when you exit pointer lock (esc) -subscribe(activeModalStack, () => { - if (activeModalStack.length) { - // iterate over pressedKeys - for (const key of pressedKeys) { - const e = new KeyboardEvent('keyup', { code: key }) - document.dispatchEvent(e) - } - } -}) - -let lastJumpUsage = 0 -document.addEventListener('keydown', (e) => { - if (!isGameActive(true)) return - - keyBindScrn.keymaps.forEach(km => { - if (e.code === km.key) { - switch (km.defaultKey) { - case 'KeyE': - // todo reenable - showModal({ reactType: 'inventory', }) - // todo seems to be workaround - // avoid calling inner keybinding listener, but should be handled there - e.stopImmediatePropagation() - break - case 'KeyQ': - if (bot.heldItem) bot.tossStack(bot.heldItem) - break - case 'ControlLeft': - bot.setControlState('sprint', true) - gameAdditionalState.isSprinting = true - break - case 'ShiftLeft': - bot.setControlState('sneak', true) - break - case 'Space': - bot.setControlState('jump', true) - break - case 'KeyD': - bot.setControlState('right', true) - e.preventDefault() - break - case 'KeyA': - bot.setControlState('left', true) - e.preventDefault() - break - case 'KeyS': - bot.setControlState('back', true) - e.preventDefault() - break - case 'KeyW': - bot.setControlState('forward', true) - break - } - } - }) - pressedKeys.add(e.code) -}, { - capture: true, -}) - -document.addEventListener('keyup', (e) => { - // workaround for pause pressed keys, multiple keyboard - if (!isGameActive(false) || !pressedKeys.has(e.code)) { - return - } - - keyBindScrn.keymaps.forEach(km => { - if (e.code === km.key) { - switch (km.defaultKey) { - case 'ControlLeft': - bot.setControlState('sprint', false) - gameAdditionalState.isSprinting = false - break - case 'ShiftLeft': - bot.setControlState('sneak', false) - break - case 'Space': - const toggleFlyAction = Date.now() - lastJumpUsage < 500 - if (toggleFlyAction) { - toggleFly() - } - lastJumpUsage = Date.now() - - bot.setControlState('jump', false) - break - case 'KeyD': - bot.setControlState('right', false) - break - case 'KeyA': - bot.setControlState('left', false) - break - case 'KeyS': - bot.setControlState('back', false) - break - case 'KeyW': - bot.setControlState('forward', false) - break - } - } - }) - pressedKeys.delete(e.code) -}, false) diff --git a/src/chat.js b/src/chat.js index 2497d09d5..bd36b8374 100644 --- a/src/chat.js +++ b/src/chat.js @@ -228,24 +228,8 @@ class ChatBox extends LitElement { } }) - const keyBindScrn = document.getElementById('keybinds-screen') - document.addEventListener('keypress', e => { if (!this.inChat && activeModalStack.length === 0) { - keyBindScrn.keymaps.forEach(km => { - if (e.code === km.key) { - switch (km.defaultKey) { - case 'KeyT': - setTimeout(() => this.enableChat(), 0) - break - case 'Slash': - setTimeout(() => this.enableChat('/'), 0) - e.preventDefault() - break - } - } - }) - return false } diff --git a/src/controls.ts b/src/controls.ts new file mode 100644 index 000000000..c60233337 --- /dev/null +++ b/src/controls.ts @@ -0,0 +1,266 @@ +//@ts-check + +import { Vec3 } from 'vec3' +import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal } from './globalState' +import { proxy, subscribe } from 'valtio' + +import { ControMax } from 'contro-max/build/controMax' +import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types' +import { stringStartsWith } from 'contro-max/build/stringUtils' + +// doesnt seem to work for now +const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) +subscribe(customKeymaps, () => { + localStorage.keymap = JSON.parse(customKeymaps) +}) + +export const contro = new ControMax({ + commands: { + general: { + jump: ['Space', 'A'], + inventory: ['KeyE', 'X'], + drop: ['KeyQ', 'B'], + sneak: ['ShiftLeft', 'Right Stick'], + sprint: ['ControlLeft', 'Left Stick'], + nextHotbarSlot: [null, 'Left Bumper'], + prevHotbarSlot: [null, 'Right Bumper'], + attackDestroy: [null, 'Right Trigger'], + interactPlace: [null, 'Left Trigger'], + chat: [['KeyT', 'Enter'], null], + command: ['Slash', null], + }, + // waila: { + // showLookingBlockRecipe: ['Numpad3'], + // showLookingBlockUsages: ['Numpad4'] + // } + } satisfies Record>, + movementKeymap: 'WASD', + movementVector: '2d', + groupedCommands: { + general: { + switchSlot: ['Digits', []] + } + }, +}, { + target: document, + captureEvents() { + return bot && isGameActive(false) + }, + storeProvider: { + load: () => customKeymaps, + save() { }, + } +}) +export type Command = CommandEventArgument['command'] + +const setSprinting = (state: boolean) => { + bot.setControlState('sprint', state) + gameAdditionalState.isSprinting = state +} + +contro.on('movementUpdate', ({ vector, gamepadIndex }) => { + // gamepadIndex will be used for splitscreen in future + const coordToAction = [ + ['z', -1, 'forward'], + ['z', 1, 'back'], + ['x', -1, 'left'], + ['x', 1, 'right'], + ] as const + + const newState: Partial = {} + for (const [coord, v] of Object.entries(vector)) { + if (v === undefined || Math.abs(v) < 0.3) continue + // todo use raw values eg for slow movement + const mappedValue = v < 0 ? -1 : 1 + const foundAction = coordToAction.find(([c, mapV]) => c === coord && mapV === mappedValue)?.[2] + newState[foundAction] = true + } + + for (const key of ['forward', 'back', 'left', 'right'] as const) { + if (newState[key] === bot.controlState[key]) continue + const action = !!newState[key] + if (action && !isGameActive(true)) continue + bot.setControlState(key, action) + + if (key === 'forward') { + // todo workaround: need to refactor + if (action) { + contro.emit('trigger', { command: 'general.forward' } as any) + } else { + setSprinting(false) + } + } + } +}) + +let lastCommandTrigger = null as { command: string, time: number } | null + +const secondActionActivationTimeout = 600 +const secondActionCommands = { + 'general.jump'() { + toggleFly() + }, + 'general.forward'() { + setSprinting(true) + } +} + +// detect pause open, as ANY keyup event is not fired when you exit pointer lock (esc) +subscribe(activeModalStack, () => { + if (activeModalStack.length) { + // iterate over pressedKeys + for (const key of contro.pressedKeys) { + contro.pressedKeyOrButtonChanged({ code: key }, false) + } + } +}) + +const onTriggerOrReleased = (command: Command, pressed: boolean) => { + // always allow release! + if (pressed && !isGameActive(true)) return + if (stringStartsWith(command, 'general')) { + // handle general commands + switch (command) { + case 'general.jump': + bot.setControlState('jump', pressed) + break + case 'general.sneak': + bot.setControlState('sneak', pressed) + break + case 'general.sprint': + setSprinting(pressed) + break + } + } +} + +// im still not sure, maybe need to refactor to handle in inventory instead +const alwaysHandledCommand = (command: Command) => { + if (command === 'general.inventory') { + if (activeModalStack.at(-1)?.reactType === 'inventory') { + hideCurrentModal() + } + } +} + +contro.on('trigger', ({ command }) => { + const willContinue = !isGameActive(true) + alwaysHandledCommand(command) + if (willContinue) return + + const secondActionCommand = secondActionCommands[command] + if (secondActionCommand) { + const commandToTrigger = secondActionCommands[lastCommandTrigger?.command] + if (commandToTrigger && Date.now() - lastCommandTrigger.time < secondActionActivationTimeout) { + commandToTrigger() + lastCommandTrigger = null + } else { + lastCommandTrigger = { + command, + time: Date.now(), + } + } + } + + onTriggerOrReleased(command, true) + + if (stringStartsWith(command, 'general')) { + switch (command) { + case 'general.inventory': + document.exitPointerLock?.() + showModal({ reactType: 'inventory' }) + break + case 'general.drop': + if (bot.heldItem) bot.tossStack(bot.heldItem) + break + case 'general.chat': + document.getElementById('hud').shadowRoot.getElementById('chat').enableChat() + break + case 'general.command': + document.getElementById('hud').shadowRoot.getElementById('chat').enableChat('/') + break + // todo place / destroy + } + } +}) + +contro.on('release', ({ command }) => { + onTriggerOrReleased(command, false) +}) + +// #region creative fly +// these controls are more like for gamemode 3 + +const makeInterval = (fn, interval) => { + const intervalId = setInterval(fn, interval) + + const cleanup = () => { + clearInterval(intervalId) + cleanup.active = false + } + cleanup.active = true + return cleanup +} + +const isFlying = () => bot.physics.gravity === 0 +let endFlyLoop: ReturnType | undefined + +const currentFlyVector = new Vec3(0, 0, 0) +window.currentFlyVector = currentFlyVector + +const startFlyLoop = () => { + if (!isFlying()) return + endFlyLoop?.() + + endFlyLoop = makeInterval(() => { + if (!window.bot) endFlyLoop() + bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0))) + }, 50) +} + +// todo we will get rid of patching it when refactor controls +let originalSetControlState +const patchedSetControlState = (action, state) => { + if (!isFlying()) { + return originalSetControlState(action, state) + } + + const actionPerFlyVector = { + jump: new Vec3(0, 1, 0), + sneak: new Vec3(0, -1, 0), + } + + const changeVec = actionPerFlyVector[action] + if (!changeVec) { + return originalSetControlState(action, state) + } + const toAddVec = changeVec.scaled(state ? 1 : -1) + for (const coord of ['x', 'y', 'z']) { + if (toAddVec[coord] === 0) continue + if (currentFlyVector[coord] === toAddVec[coord]) return + } + currentFlyVector.add(toAddVec) +} + +const standardAirborneAcceleration = 0.02 +const toggleFly = () => { + if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return + if (bot.setControlState !== patchedSetControlState) { + originalSetControlState = bot.setControlState + bot.setControlState = patchedSetControlState + } + + if (isFlying()) { + bot.physics['airborneAcceleration'] = standardAirborneAcceleration + bot.creative.stopFlying() + endFlyLoop?.() + } else { + // window.flyingSpeed will be removed + bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 + bot.entity.velocity = new Vec3(0, 0, 0) + bot.creative.startFlying() + startFlyLoop() + } + gameAdditionalState.isFlying = isFlying() +} +// #endregion diff --git a/src/index.js b/src/index.js index c59ea2da7..a1d66b9f9 100644 --- a/src/index.js +++ b/src/index.js @@ -37,7 +37,7 @@ require('./menus/title_screen') require('./optionsStorage') require('./reactUi.jsx') -require('./botControls') +require('./controls') require('./dragndrop') require('./browserfs') require('./eruda') @@ -47,9 +47,7 @@ const net = require('net') const Stats = require('stats.js') const mineflayer = require('mineflayer') -const { WorldView, Viewer, MapControls } = require('prismarine-viewer/viewer') -const PrismarineWorld = require('prismarine-world') -const nbt = require('prismarine-nbt') +const { WorldView, Viewer } = require('prismarine-viewer/viewer') const pathfinder = require('mineflayer-pathfinder') const { Vec3 } = require('vec3') diff --git a/src/menus/components/hotbar.js b/src/menus/components/hotbar.js index 216454c37..7f65681e5 100644 --- a/src/menus/components/hotbar.js +++ b/src/menus/components/hotbar.js @@ -224,9 +224,9 @@ class Hotbar extends LitElement {
    - ${miscUiState.currentTouch && html`
    { + ${miscUiState.currentTouch ? html`
    { showModal({ reactType: 'inventory', }) - }}>`} + }}>` : undefined}
    diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index ba0f19e8a..fc2cfce13 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -113,7 +113,7 @@ class OptionsScreen extends CommonOptionsScreen { }}>
    - showModal(document.getElementById('keybinds-screen'))}> + showModal(document.getElementById('keybinds-screen'))}> { this.changeOption('guiScale', e.target.value) document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) diff --git a/src/reactUi.jsx b/src/reactUi.jsx index 5a254adb3..72751aaf2 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -1,13 +1,13 @@ //@ts-check import { renderToDom } from '@zardoy/react-util' -import { LeftTouchArea, InventoryNew, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface' +import { LeftTouchArea, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface' import { css } from '@emotion/css' -import { activeModalStack, hideCurrentModal, isGameActive } from './globalState' -import { useEffect, useState } from 'react' -import { useProxy } from 'valtio/utils' -import useTypedEventListener from 'use-typed-event-listener' +import { activeModalStack, isGameActive } from './globalState' import { isProbablyIphone } from './menus/components/common' +// import DeathScreen from './react/DeathScreen' +import { useSnapshot } from 'valtio' +import { contro } from './controls' // todo useInterfaceState.setState({ @@ -17,23 +17,22 @@ useInterfaceState.setState({ }, updateCoord: ([coord, state]) => { const coordToAction = [ - ['z', -1, 'forward'], - ['z', 1, 'back'], - ['x', -1, 'left'], - ['x', 1, 'right'], - ['y', 1, 'jump'], + ['z', -1, 'KeyW'], + ['z', 1, 'KeyS'], + ['x', -1, 'KeyA'], + ['x', 1, 'KeyD'], + ['y', 1, 'Space'], // todo jump ] // todo refactor const actionAndState = state !== 0 ? coordToAction.find(([axis, value]) => axis === coord && value === state) : coordToAction.filter(([axis]) => axis === coord) if (!bot) return if (state === 0) { for (const action of actionAndState) { - //@ts-ignore - bot.setControlState(action[2], false) + contro.pressedKeyOrButtonChanged({code: action[2],}, false) } } else { //@ts-ignore - bot.setControlState(actionAndState[2], true) + contro.pressedKeyOrButtonChanged({code: actionAndState[2],}, true) } } }) @@ -67,71 +66,17 @@ const TouchControls = () => { ) } -const useActivateModal = (/** @type {string} */search, onlyLast = true) => { - const stack = useProxy(activeModalStack) - - return onlyLast ? stack.at(-1)?.reactType === search : stack.some((modal) => modal.reactType === search) -} - function useIsBotAvailable() { - const stack = useProxy(activeModalStack) + const stack = useSnapshot(activeModalStack) return isGameActive(false) } -function InventoryWrapper() { - const isInventoryOpen = useActivateModal('inventory', false) - const [slots, setSlots] = useState(bot.inventory.slots) - - useEffect(() => { - if (isInventoryOpen) { - document.exitPointerLock?.() - } - }, [isInventoryOpen]) - - useTypedEventListener(document, 'keydown', (e) => { - // todo use refactored keymap - if (e.code === 'KeyE' && activeModalStack.at(-1)?.reactType === 'inventory') { - hideCurrentModal() - } - }) - - useEffect(() => { - bot.inventory.on('updateSlot', () => { - setSlots([...bot.inventory.slots]) - }) - // todo need to think of better solution - window['mcData'] = require('minecraft-data')(bot.version) - window['mcAssets'] = require('minecraft-assets')(bot.version) - }, []) - - if (!isInventoryOpen) return null - - return null - - // return
    div { - // scale: 0.6; - // background: transparent !important; - // } - // `}> - // { - // bot.moveSlotItem(oldSlot, newSlotIndex) - // } } /> - //
    -} - const App = () => { const isBotAvailable = useIsBotAvailable() - if (!isBotAvailable) return null + // if (!isBotAvailable) return return
    -
    } diff --git a/tsconfig.json b/tsconfig.json index 883dcf2fc..d6ed70d8f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "allowSyntheticDefaultImports": true, "noEmit": true, "strictFunctionTypes": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "noFallthroughCasesInSwitch": true // "strictNullChecks": true }, "include": [ From 70056347ad03daad2ba533dc20a3301a11defea4 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:51:28 +0300 Subject: [PATCH 170/318] fix sw error, cache mc-data --- esbuild.mjs | 18 ++---------------- scripts/build.js | 3 +++ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/esbuild.mjs b/esbuild.mjs index 43ee2b1dc..9c55251c2 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -10,20 +10,6 @@ import { getSwAdditionalEntries } from './scripts/build.js' /** @type {import('esbuild').BuildOptions} */ let baseConfig = {} -// // legacy html config -// baseConfig = { -// entryPoints: ['index.html'], -// assetNames: 'assets/[name]', -// chunkNames: '[ext]/[name]', -// outdir: 'dist', -// outfile: undefined, -// plugins: [ -// htmlPlugin({ -// scriptsTarget: 'esnext', -// }) -// ], -// } - // // testing config // baseConfig = { // entryPoints: ['files/index.js'], @@ -35,8 +21,7 @@ try { await import('./localSettings.mjs') } catch { } -fs.copyFileSync('index.html', 'dist/index.html') -fs.writeFileSync('dist/index.html', fs.readFileSync('dist/index.html', 'utf8').replace('', ''), 'utf8') +fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replace('', ''), 'utf8') const watch = process.argv.includes('--watch') || process.argv.includes('-w') const prod = process.argv.includes('--prod') @@ -142,6 +127,7 @@ if (watch) { skipWaiting: true, clientsClaim: true, additionalManifestEntries: getSwAdditionalEntries(), + globPatterns: [], swDest: 'dist/service-worker.js', }) } diff --git a/scripts/build.js b/scripts/build.js index 9f30fd36e..e71413b07 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -47,6 +47,7 @@ exports.getSwAdditionalEntries = () => { 'index.js', 'index.css', 'favicon.ico', + `mc-data/${defaultLocalServerOptions.versionMajor}.js`, `blocksStates/${singlePlayerVersion}.json`, 'extra-textures/**', // todo-low copy from assets @@ -60,6 +61,8 @@ exports.getSwAdditionalEntries = () => { `textures/1.16.4/entity/squid.png`, ] const filesNeedsCacheKey = [ + 'index.js', + 'index.css', 'worker.js', ] const output = [] From 7622a153e26062cce8b2274fe06b7c526fb37dcf Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:52:26 +0300 Subject: [PATCH 171/318] controls: use ctrl only as activation to match original game behavior --- src/controls.ts | 5 ++++- src/reactUi.jsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index c60233337..e4056dd7e 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -128,7 +128,10 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => { bot.setControlState('sneak', pressed) break case 'general.sprint': - setSprinting(pressed) + // todo add setting to change behavior + if (pressed) { + setSprinting(pressed) + } break } } diff --git a/src/reactUi.jsx b/src/reactUi.jsx index 72751aaf2..e8f6656da 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -74,7 +74,7 @@ function useIsBotAvailable() { const App = () => { const isBotAvailable = useIsBotAvailable() - // if (!isBotAvailable) return + if (!isBotAvailable) return null return
    From e66862dd5c192498225aa83e3fa83d2b3a94a90a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:55:22 +0300 Subject: [PATCH 172/318] dispose world on exit (that should finally allow clean exit) --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index a1d66b9f9..71abd0b30 100644 --- a/src/index.js +++ b/src/index.js @@ -305,6 +305,7 @@ async function connect (connectOptions) { /** @type {mineflayer.Bot} */ let bot const destroyAll = () => { + viewer.resetAll() window.singlePlayerServer = undefined // simple variant, still buggy From 342a2f0f70e53ee4f4419ede286c6d8a15b6772a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:57:23 +0300 Subject: [PATCH 173/318] singlePlayerServer -> localServer (intention: will allow other players to connect soon) --- src/builtinCommands.ts | 12 ++++++------ src/globalState.js | 4 ++-- src/globals.d.ts | 2 +- src/globals.js | 2 +- src/index.js | 14 ++++++++------ src/utils.js | 4 ++-- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index 61de33661..15f2df8a5 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -31,7 +31,7 @@ async function addFolderToZip(folderPath, zip, relativePath) { const exportWorld = async () => { // todo issue into chat warning if fs is writable! const zip = new JSZip() - let worldFolder: string = singlePlayerServer.options.worldFolder + let worldFolder: string = localServer.options.worldFolder if (!worldFolder.startsWith('/')) worldFolder = `/${worldFolder}` await addFolderToZip(worldFolder, zip, '') @@ -66,9 +66,9 @@ const commands = [ if (fsState.isReadonly || !fsState.syncFs) return // todo for testing purposes sessionStorage.oldData = localStorage - singlePlayerServer.quit() + localServer.quit() // todo browserfs bug - fs.rmdirSync(singlePlayerServer.options.worldFolder, { recursive: true }) + fs.rmdirSync(localServer.options.worldFolder, { recursive: true }) } }, { @@ -80,7 +80,7 @@ const commands = [ ] export const tryHandleBuiltinCommand = (message) => { - if (!singlePlayerServer) return + if (!localServer) return for (const command of commands) { if (command.command.includes(message)) { @@ -91,10 +91,10 @@ export const tryHandleBuiltinCommand = (message) => { } export const saveWorld = async () => { - for (const player of window.singlePlayerServer.players) { + for (const player of localServer.players) { await player.save() } - const worlds = [singlePlayerServer.overworld] + const worlds = [localServer.overworld] for (const world of worlds) { await world.storageProvider.close() } diff --git a/src/globalState.js b/src/globalState.js index 7a500e4bf..7d83340b5 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -124,8 +124,8 @@ export const gameAdditionalState = proxy({ window.gameAdditionalState = gameAdditionalState const savePlayers = () => { - if (!window.singlePlayerServer) return - for (const player of window.singlePlayerServer.players) { + if (!window.localServer) return + for (const player of window.localServer.players) { player.save() } } diff --git a/src/globals.d.ts b/src/globals.d.ts index cc13b180e..ecad16368 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -3,7 +3,7 @@ declare const THREE: typeof import('three') // todo declare const bot: import('mineflayer').Bot -declare const singlePlayerServer: any +declare const localServer: any declare interface Document { getElementById(id): any diff --git a/src/globals.js b/src/globals.js index 241853564..4362604c3 100644 --- a/src/globals.js +++ b/src/globals.js @@ -2,4 +2,4 @@ window.bot = undefined window.THREE = undefined -window.singlePlayerServer = undefined +window.localServer = undefined diff --git a/src/index.js b/src/index.js index 71abd0b30..5ba199193 100644 --- a/src/index.js +++ b/src/index.js @@ -306,7 +306,7 @@ async function connect (connectOptions) { let bot const destroyAll = () => { viewer.resetAll() - window.singlePlayerServer = undefined + window.localServer = undefined // simple variant, still buggy postRenderFrameFn = () => { } @@ -354,7 +354,7 @@ async function connect (connectOptions) { }, { signal: errorAbortController.signal }) - let singlePlayerServer + let localServer try { Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) let version = connectOptions.botVersion ?? serverOptions.version @@ -378,13 +378,13 @@ async function connect (connectOptions) { setLoadingScreenStatus('Starting local server') window.serverDataChannel ??= {} window.worldLoaded = false - singlePlayerServer = window.singlePlayerServer = startLocalServer() + localServer = window.localServer = startLocalServer() // todo need just to call quit if started // loadingScreen.maybeRecoverable = false // init world, todo: do it for any async plugins - if (!singlePlayerServer.worldsReady) { + if (!localServer.worldsReady) { await new Promise(resolve => { - singlePlayerServer.once('worldsReady', resolve) + localServer.once('worldsReady', resolve) }) } } @@ -466,10 +466,12 @@ async function connect (connectOptions) { const center = bot.entity.position + /** @type {import('../prismarine-viewer/viewer/lib/worldView').WorldView} */ const worldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) if (singeplayer) { + let prevRenderDistance = renderDistance const d = subscribeKey(options, 'renderDistance', () => { - singlePlayerServer.options['view-distance'] = options.renderDistance + localServer.options['view-distance'] = options.renderDistance worldView.viewDistance = options.renderDistance window.onPlayerChangeRenderDistance?.(options.renderDistance) }) diff --git a/src/utils.js b/src/utils.js index f48313929..ae6ae43c0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -149,9 +149,9 @@ export const setLoadingScreenStatus = function (/** @type {string} */status, isE export const disconnect = async () => { - if (window.singlePlayerServer) { + if (window.localServer) { await saveWorld() - singlePlayerServer.quit() + localServer.quit() } bot._client.emit('end') miscUiState.gameLoaded = false From ce566b02730edb124a0c524f84a5bacd1939d86f Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 07:57:59 +0300 Subject: [PATCH 174/318] upstream fork changes --- package.json | 2 +- src/index.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ba7869023..e7bfbc53c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-refresh": "^0.14.0", - "space-squid": "github:zardoy/space-squid", + "space-squid": "github:zardoy/space-squid#everything", "speed-measure-webpack-plugin": "^1.5.0", "stats.js": "^0.17.0", "url": "^0.11.1", diff --git a/src/index.js b/src/index.js index 5ba199193..37d52094c 100644 --- a/src/index.js +++ b/src/index.js @@ -473,7 +473,9 @@ async function connect (connectOptions) { const d = subscribeKey(options, 'renderDistance', () => { localServer.options['view-distance'] = options.renderDistance worldView.viewDistance = options.renderDistance - window.onPlayerChangeRenderDistance?.(options.renderDistance) + localServer.players[0].emit('playerChangeRenderDistance', options.renderDistance) + worldView.updatePosition(bot.entity.position) + prevRenderDistance = options.renderDistance }) disposables.push(d) } From bebd65e65b4798eb548f4237d0954b77d538d932 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 08:10:20 +0300 Subject: [PATCH 175/318] display sync error as well! --- src/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.js b/src/index.js index 37d52094c..7e20ee892 100644 --- a/src/index.js +++ b/src/index.js @@ -354,6 +354,11 @@ async function connect (connectOptions) { }, { signal: errorAbortController.signal }) + window.addEventListener('error', (e) => { + handleError(e.message) + }, { + signal: errorAbortController.signal + }) let localServer try { Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) From 70681fa519b19fef5bae1dd7768f97c0cb469adf Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 08:29:14 +0300 Subject: [PATCH 176/318] change default singleplayer version to 1.8.8 as it supports more features, preserve for old saves --- src/defaultLocalServerOptions.js | 3 ++- src/menus/title_screen.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/defaultLocalServerOptions.js b/src/defaultLocalServerOptions.js index 4df9ddc60..3ad6cebba 100644 --- a/src/defaultLocalServerOptions.js +++ b/src/defaultLocalServerOptions.js @@ -31,5 +31,6 @@ module.exports = { }, 'everybody-op': true, 'max-entities': 100, - 'version': '1.16.1', + 'version': '1.8.8', + versionMajor: '1.8' } diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index 93c6e1ff7..f11c320f1 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -3,8 +3,11 @@ const { showModal } = require('../globalState') const { fsState } = require('../loadFolder') const { openURL } = require('./components/common') const { LitElement, html, css, unsafeCSS } = require('lit') +const fs = require('fs') const mcImage = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/title/minecraft.png') +const { options } = require('../optionsStorage') +const defaultLocalServerOptions = require('../defaultLocalServerOptions') // const SUPPORT_WORLD_LOADING = !!window.showDirectoryPicker const SUPPORT_WORLD_LOADING = true @@ -161,6 +164,12 @@ class TitleScreen extends LitElement { isReadonly: false, syncFs: true, }) + const notFirstTime = fs.existsSync('./world/level.dat') + if (notFirstTime && !options.localServerOptions.version) { + options.localServerOptions.version = '1.16.1' // legacy version, now we use 1.8.8 + } else { + options.localServerOptions.version ??= defaultLocalServerOptions.version + } this.dispatchEvent(new window.CustomEvent('singleplayer', {})) }}> ${SUPPORT_WORLD_LOADING ? html` { From c4ae6500ac92f1a89d85d4bf0c744949caa4dc53 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 08:42:08 +0300 Subject: [PATCH 177/318] add visual sneaking + touch support --- package.json | 2 +- src/controls.ts | 1 + src/globalState.js | 1 + src/index.js | 14 ++++++-------- src/reactUi.jsx | 1 + 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e7bfbc53c..c516331e4 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "author": "PrismarineJS", "license": "MIT", "dependencies": { - "@dimaka/interface": "0.0.0-dev", + "@dimaka/interface": "0.0.1", "@emotion/css": "^11.11.2", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@types/react": "^18.2.20", diff --git a/src/controls.ts b/src/controls.ts index e4056dd7e..5db86b39a 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -125,6 +125,7 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => { bot.setControlState('jump', pressed) break case 'general.sneak': + gameAdditionalState.isSneaking = pressed bot.setControlState('sneak', pressed) break case 'general.sprint': diff --git a/src/globalState.js b/src/globalState.js index 7d83340b5..86d6720f8 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -119,6 +119,7 @@ window.miscUiState = miscUiState export const gameAdditionalState = proxy({ isFlying: false, isSprinting: false, + isSneaking: false, }) window.gameAdditionalState = gameAdditionalState diff --git a/src/index.js b/src/index.js index 7e20ee892..c67d23e98 100644 --- a/src/index.js +++ b/src/index.js @@ -119,6 +119,7 @@ renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) // Create viewer +/** @type {import('../prismarine-viewer/viewer/lib/viewer').Viewer} */ const viewer = new Viewer(renderer, options.numWorkers) initPanoramaOptions(viewer) @@ -501,6 +502,11 @@ async function connect (connectOptions) { updateFov() subscribeKey(gameAdditionalState, 'isFlying', updateFov) subscribeKey(gameAdditionalState, 'isSprinting', updateFov) + const defaultPlayerHeight = viewer.playerHeight + subscribeKey(gameAdditionalState, 'isSneaking', () => { + viewer.playerHeight = gameAdditionalState.isSneaking ? defaultPlayerHeight - 0.3 : defaultPlayerHeight + viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) + }) optionsScrn.addEventListener('fov_changed', updateFov) viewer.setVersion(version) @@ -688,14 +694,6 @@ window.addEventListener('keydown', (e) => { // } }) -window.addEventListener('unhandledrejection', (e) => { - // todo - if (e.reason.message.includes('Unable to decode audio data')) { - console.warn(e.reason) - return - } -}) - addPanoramaCubeMap() showModal(document.getElementById('title-screen')) main() diff --git a/src/reactUi.jsx b/src/reactUi.jsx index e8f6656da..b2730b031 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -22,6 +22,7 @@ useInterfaceState.setState({ ['x', -1, 'KeyA'], ['x', 1, 'KeyD'], ['y', 1, 'Space'], // todo jump + ['y', -1, 'ShiftLeft'], // todo jump ] // todo refactor const actionAndState = state !== 0 ? coordToAction.find(([axis, value]) => axis === coord && value === state) : coordToAction.filter(([axis]) => axis === coord) From 4cf323c541e8489873a01e483a32bd98d57b26a5 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 09:23:05 +0300 Subject: [PATCH 178/318] add force reload chunks (F3+A) --- src/controls.ts | 28 +++++++++++++++++++++++++-- src/globals.d.ts | 8 ++++++++ src/globals.js | 3 +++ src/index.js | 20 +++---------------- src/menus/components/debug_overlay.js | 2 +- src/menus/options_screen.js | 2 +- src/optionsStorage.js | 2 +- src/styles.css | 6 ------ src/utils.js | 10 ++++++++++ src/watchOptions.js | 8 ++++++++ 10 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 src/watchOptions.js diff --git a/src/controls.ts b/src/controls.ts index 5db86b39a..36374ea38 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -7,6 +7,7 @@ import { proxy, subscribe } from 'valtio' import { ControMax } from 'contro-max/build/controMax' import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types' import { stringStartsWith } from 'contro-max/build/stringUtils' +import { reloadChunks } from './utils' // doesnt seem to work for now const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) @@ -154,8 +155,8 @@ contro.on('trigger', ({ command }) => { const secondActionCommand = secondActionCommands[command] if (secondActionCommand) { - const commandToTrigger = secondActionCommands[lastCommandTrigger?.command] - if (commandToTrigger && Date.now() - lastCommandTrigger.time < secondActionActivationTimeout) { + if (command === lastCommandTrigger?.command && Date.now() - lastCommandTrigger.time < secondActionActivationTimeout) { + const commandToTrigger = secondActionCommands[lastCommandTrigger.command] commandToTrigger() lastCommandTrigger = null } else { @@ -192,6 +193,29 @@ contro.on('release', ({ command }) => { onTriggerOrReleased(command, false) }) +// hard-coded keybindings + +const hardcodedPressedKeys = new Set() +document.addEventListener('keydown', (e) => { + if (hardcodedPressedKeys.has('F3')) { + // reload chunks + if (e.code === 'KeyA') { + //@ts-ignore + const loadedChunks = Object.entries(worldView.loadedChunks).filter(([, v]) => v).map(([key]) => key.split(',').map(Number)) + for (const [x, z] of loadedChunks) { + worldView.unloadChunk({ x, z }) + } + reloadChunks() + } + } + + if (hardcodedPressedKeys.has(e.code)) return + hardcodedPressedKeys.add(e.code) +}) +document.addEventListener('keyup', (e) => { + hardcodedPressedKeys.delete(e.code) +}) + // #region creative fly // these controls are more like for gamemode 3 diff --git a/src/globals.d.ts b/src/globals.d.ts index ecad16368..dadeafa72 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -3,12 +3,20 @@ declare const THREE: typeof import('three') // todo declare const bot: import('mineflayer').Bot +declare const viewer: import('../prismarine-viewer/viewer/lib/viewer').Viewer | undefined +declare const worldView: import('../prismarine-viewer/viewer/lib/worldView').WorldView | undefined declare const localServer: any declare interface Document { getElementById(id): any } +declare namespace JSX { + interface IntrinsicElements { + [elemName: string]: any + } +} + declare interface DocumentFragment { getElementById(id): HTMLElement & Record querySelector(id): HTMLElement & Record diff --git a/src/globals.js b/src/globals.js index 4362604c3..ecffcea2d 100644 --- a/src/globals.js +++ b/src/globals.js @@ -3,3 +3,6 @@ window.bot = undefined window.THREE = undefined window.localServer = undefined +window.worldView = undefined +window.viewer = undefined +window.loadedData = undefined diff --git a/src/index.js b/src/index.js index c67d23e98..1bd991b0a 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ require('./chat') require('./inventory') //@ts-ignore require('./globals.js') +require('./watchOptions') // workaround for mineflayer process.versions.node = '18.0.0' @@ -474,17 +475,6 @@ async function connect (connectOptions) { /** @type {import('../prismarine-viewer/viewer/lib/worldView').WorldView} */ const worldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) - if (singeplayer) { - let prevRenderDistance = renderDistance - const d = subscribeKey(options, 'renderDistance', () => { - localServer.options['view-distance'] = options.renderDistance - worldView.viewDistance = options.renderDistance - localServer.players[0].emit('playerChangeRenderDistance', options.renderDistance) - worldView.updatePosition(bot.entity.position) - prevRenderDistance = options.renderDistance - }) - disposables.push(d) - } let fovSetting = optionsScrn.fov const updateFov = () => { @@ -511,14 +501,13 @@ async function connect (connectOptions) { viewer.setVersion(version) + window.viewer = viewer + window.loadedData = mcData window.worldView = worldView window.bot = bot - window.mcData = mcData - window.viewer = viewer window.Vec3 = Vec3 window.pathfinder = pathfinder window.debugMenu = debugMenu - window.settings = optionsScrn window.renderer = renderer initVR(bot, renderer, viewer) @@ -689,9 +678,6 @@ window.addEventListener('keydown', (e) => { if (e.code === 'KeyL' && e.altKey) { console.clear() } - // if (e.code === 'KeyD') { - // debugPitch.innerText = '0' - // } }) addPanoramaCubeMap() diff --git a/src/menus/components/debug_overlay.js b/src/menus/components/debug_overlay.js index a1146497f..7eb3293ec 100644 --- a/src/menus/components/debug_overlay.js +++ b/src/menus/components/debug_overlay.js @@ -131,7 +131,7 @@ class DebugOverlay extends LitElement {

    Facing (minecraft): ${quadsDescription[minecraftQuad]} (${minecraftYaw.toFixed(1)} ${(rot[1] * -180 / Math.PI).toFixed(1)})

    Light: ${skyL} (${skyL} sky)

    -

    Biome: minecraft:${window.mcData.biomesArray[biomeId]?.name ?? 'unknown biome'}

    +

    Biome: minecraft:${window.loadedData.biomesArray[biomeId]?.name ?? 'unknown biome'}

    Day: ${this.bot.time.day}

    ${Object.entries(this.customEntries).map(([name, value]) => html`

    ${name}: ${value}

    `)} diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index fc2cfce13..9ace424b0 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -113,7 +113,7 @@ class OptionsScreen extends CommonOptionsScreen { }}>
    - showModal(document.getElementById('keybinds-screen'))}> + showModal(document.getElementById('keybinds-screen'))}> { this.changeOption('guiScale', e.target.value) document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) diff --git a/src/optionsStorage.js b/src/optionsStorage.js index 360a84cee..3632c54da 100644 --- a/src/optionsStorage.js +++ b/src/optionsStorage.js @@ -26,7 +26,7 @@ export const options = proxy( mergeAny(defaultOptions, JSON.parse(localStorage.options || '{}')) ) -window.options = options +window.options = window.settings = options subscribe(options, () => { localStorage.options = JSON.stringify(options) diff --git a/src/styles.css b/src/styles.css index 3fb6fd13a..b04f4d55b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -92,12 +92,6 @@ canvas { font-family: minecraft, mojangles, monospace; } -/* todo move this fix to lib */ -.TouchMovementArea { - grid-template-columns: "c ." - "d ."; -} - @media only screen and (max-width: 971px) { #ui-root { transform: scale(2); diff --git a/src/utils.js b/src/utils.js index ae6ae43c0..284a883a8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -184,3 +184,13 @@ export const toMajorVersion = (version) => { const [a, b] = (version + '').split('.') return `${a}.${b}` } + +let prevRenderDistance = options.renderDistance +export const reloadChunks = () => { + if (!worldView || !localServer) return + localServer.options['view-distance'] = options.renderDistance + worldView.viewDistance = options.renderDistance + localServer.players[0].emit('playerChangeRenderDistance', options.renderDistance) + worldView.updatePosition(bot.entity.position, true) + prevRenderDistance = options.renderDistance +} diff --git a/src/watchOptions.js b/src/watchOptions.js new file mode 100644 index 000000000..7757208f5 --- /dev/null +++ b/src/watchOptions.js @@ -0,0 +1,8 @@ +//@ts-check +// not all options are watched here + +import { subscribeKey } from 'valtio/utils' +import { options } from './optionsStorage' +import { reloadChunks } from './utils' + +subscribeKey(options, 'renderDistance', reloadChunks) From e04fef0e17d47e097acafcd2eadd06354a05060e Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 09:23:23 +0300 Subject: [PATCH 179/318] enable eruda only on mobile --- src/eruda.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/eruda.js b/src/eruda.js index fd21fde5d..61de59649 100644 --- a/src/eruda.js +++ b/src/eruda.js @@ -1,4 +1,6 @@ -if (process.env.NODE_ENV === 'development') { +import { isMobile } from './menus/components/common' + +if (process.env.NODE_ENV === 'development' && isMobile()) { require('eruda').default.init() console.log('JS Loaded in', Date.now() - window.startLoad) } From c9b2b1c57a9a73b1f96cea69dec5e29a023949d6 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 09:27:20 +0300 Subject: [PATCH 180/318] loadFolder -> loadSave --- src/browserfs.js | 8 ++++---- src/builtinCommands.ts | 2 +- src/{loadFolder.ts => loadSave.ts} | 3 +-- src/menus/loading_or_error_screen.js | 2 +- src/menus/pause_screen.js | 2 +- src/menus/title_screen.js | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) rename src/{loadFolder.ts => loadSave.ts} (98%) diff --git a/src/browserfs.js b/src/browserfs.js index ca51aca3d..44c291420 100644 --- a/src/browserfs.js +++ b/src/browserfs.js @@ -1,5 +1,5 @@ //@ts-check -import { fsState, loadFolder } from './loadFolder' +import { fsState, loadSave } from './loadSave' import { oneOf } from '@zardoy/utils' import JSZip from 'jszip' import { join } from 'path' @@ -149,7 +149,7 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} fsState.isReadonly = !writeAccess fsState.syncFs = false - loadFolder() + loadSave() } export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = file['name']) => { @@ -176,7 +176,7 @@ export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = fsState.syncFs = true if (fs.existsSync('/world/level.dat')) { - loadFolder() + loadSave() } else { const dirs = fs.readdirSync('/world') let availableWorlds = [] @@ -192,7 +192,7 @@ export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = } if (availableWorlds.length === 1) { - loadFolder(`/world/${availableWorlds[0]}`) + loadSave(`/world/${availableWorlds[0]}`) return } diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index 15f2df8a5..a3bc13d87 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -1,7 +1,7 @@ import JSZip from 'jszip' import fs from 'fs' import { join } from 'path' -import { fsState } from './loadFolder' +import { fsState } from './loadSave' const notImplemented = () => { return 'Not implemented yet' diff --git a/src/loadFolder.ts b/src/loadSave.ts similarity index 98% rename from src/loadFolder.ts rename to src/loadSave.ts index 6b7a3e5d9..3563328de 100644 --- a/src/loadFolder.ts +++ b/src/loadSave.ts @@ -18,8 +18,7 @@ export const fsState = proxy({ const PROPOSE_BACKUP = true -// todo rename to loadWorld -export const loadFolder = async (root = '/world') => { +export const loadSave = async (root = '/world') => { // todo do it in singleplayer as well for (const key in forceCachedDataPaths) { delete forceCachedDataPaths[key] diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index 891cfdbd1..23aa2cd97 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -4,7 +4,7 @@ const { commonCss } = require('./components/common') const { addPanoramaCubeMap } = require('../panorama') const { hideModal, activeModalStacks, activeModalStack, replaceActiveModalStack, miscUiState } = require('../globalState') const { guessProblem } = require('../guessProblem') -const { fsState } = require('../loadFolder') +const { fsState } = require('../loadSave') class LoadingErrorScreen extends LitElement { static get styles () { diff --git a/src/menus/pause_screen.js b/src/menus/pause_screen.js index a863198b4..d6091b0f1 100644 --- a/src/menus/pause_screen.js +++ b/src/menus/pause_screen.js @@ -2,7 +2,7 @@ const { LitElement, html, css } = require('lit') const { openURL } = require('./components/common') const { hideCurrentModal, showModal, miscUiState } = require('../globalState') -const { fsState } = require('../loadFolder') +const { fsState } = require('../loadSave') const { subscribe } = require('valtio') const { saveWorld } = require('../builtinCommands') const { notification } = require('./notification') diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index f11c320f1..a3d4effa5 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -1,6 +1,6 @@ const { openWorldDirectory, openWorldZip } = require('../browserfs') const { showModal } = require('../globalState') -const { fsState } = require('../loadFolder') +const { fsState } = require('../loadSave') const { openURL } = require('./components/common') const { LitElement, html, css, unsafeCSS } = require('lit') const fs = require('fs') From aeca97d44d3fbca1fd18f15ae9966be18df2bd77 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 10:46:53 +0300 Subject: [PATCH 181/318] build prismarine-viewer locally which should increase install speed --- package.json | 4 ++++ scripts/build.js | 14 ++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c516331e4..971fff78c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,11 @@ "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", +<<<<<<< HEAD "prismarine-viewer": "github:PrismarineJS/prismarine-viewer", +======= + "prismarine-viewer": "./prismarine-viewer", +>>>>>>> 3daefc2 (build prismarine-viewer locally which should increase install speed) "process": "github:PrismarineJS/node-process", "rimraf": "^5.0.1", "stream-browserify": "^3.0.0", diff --git a/scripts/build.js b/scripts/build.js index e71413b07..53d669bc6 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -7,14 +7,15 @@ const path = require('path') // these files need to be copied before build for now const filesAlwaysToCopy = [ - { from: './node_modules/prismarine-viewer2/public/supportedVersions.json', to: './prismarine-viewer/public/supportedVersions.json' }, + // { from: './node_modules/prismarine-viewer2/public/supportedVersions.json', to: './prismarine-viewer/public/supportedVersions.json' }, ] // these files could be copied at build time eg with copy plugin, but copy plugin slows down the config (2x in my testing, sometimes with too many open files error) is slow so we also copy them there const webpackFilesToCopy = [ - { from: './node_modules/prismarine-viewer2/public/blocksStates/', to: 'dist/blocksStates/' }, - // { from: './node_modules/prismarine-viewer2/public/textures/', to: 'dist/textures/' }, - { from: './node_modules/prismarine-viewer2/public/worker.js', to: 'dist/worker.js' }, - { from: './node_modules/prismarine-viewer2/public/supportedVersions.json', to: 'dist/supportedVersions.json' }, + { from: './prismarine-viewer/public/blocksStates/', to: 'dist/blocksStates/' }, + // { from: './prismarine-viewer/public/textures/', to: 'dist/textures/' }, + // { from: './prismarine-viewer/public/textures/1.17.1/gui', to: 'dist/gui' }, + { from: './prismarine-viewer/public/worker.js', to: 'dist/worker.js' }, + // { from: './prismarine-viewer/public/supportedVersions.json', to: 'dist/supportedVersions.json' }, { from: './assets/', to: './dist/' }, { from: './config.json', to: 'dist/config.json' } ] @@ -24,7 +25,8 @@ exports.copyFiles = (isDev = false) => { [...filesAlwaysToCopy, ...webpackFilesToCopy].forEach(file => { fsExtra.copySync(file.from, file.to) }) - const cwd = './node_modules/prismarine-viewer2/public/textures/' + // todo copy directly only needed + const cwd = './prismarine-viewer/public/textures/' const files = glob.sync('{*/entity/**,*.png}', { cwd: cwd, nodir: true, }) for (const file of files) { const copyDest = path.join('dist/textures/', file) From 2d8d52267c104c6d64d253ee87c6e3a879c1fbff Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 10:47:15 +0300 Subject: [PATCH 182/318] add a way to disable prompts on load --- src/browserfs.js | 5 +++-- src/index.js | 1 - src/loadSave.ts | 6 ++++-- src/optionsStorage.js | 2 ++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/browserfs.js b/src/browserfs.js index 44c291420..5ea250a1c 100644 --- a/src/browserfs.js +++ b/src/browserfs.js @@ -3,6 +3,7 @@ import { fsState, loadSave } from './loadSave' import { oneOf } from '@zardoy/utils' import JSZip from 'jszip' import { join } from 'path' +import { options } from './optionsStorage' const { promisify } = require('util') const browserfs = require('browserfs') @@ -124,10 +125,10 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} } const directoryHandle = _directoryHandle - const requestResult = SUPPORT_WRITE ? await directoryHandle.requestPermission?.({ mode: 'readwrite' }) : undefined + const requestResult = SUPPORT_WRITE && !options.preferLoadReadonly ? await directoryHandle.requestPermission?.({ mode: 'readwrite' }) : undefined const writeAccess = requestResult === 'granted' - const doContinue = writeAccess || !SUPPORT_WRITE || confirm('Continue in readonly mode?') + const doContinue = writeAccess || !SUPPORT_WRITE || options.disableLoadPrompts || confirm('Continue in readonly mode?') if (!doContinue) return await new Promise(resolve => { browserfs.configure({ diff --git a/src/index.js b/src/index.js index 1bd991b0a..550b60d76 100644 --- a/src/index.js +++ b/src/index.js @@ -384,7 +384,6 @@ async function connect (connectOptions) { setLoadingScreenStatus('Starting local server') window.serverDataChannel ??= {} - window.worldLoaded = false localServer = window.localServer = startLocalServer() // todo need just to call quit if started // loadingScreen.maybeRecoverable = false diff --git a/src/loadSave.ts b/src/loadSave.ts index 3563328de..5ed8b967c 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -19,6 +19,8 @@ export const fsState = proxy({ const PROPOSE_BACKUP = true export const loadSave = async (root = '/world') => { + const disablePrompts = options.disableLoadPrompts + // todo do it in singleplayer as well for (const key in forceCachedDataPaths) { delete forceCachedDataPaths[key] @@ -50,7 +52,7 @@ export const loadSave = async (root = '/world') => { const qs = new URLSearchParams(window.location.search) version = levelDat.Version?.Name ?? qs.get('version') if (!version) { - const newVersion = prompt(`In 1.8 and before world save doesn\'t contain version info, please enter version you want to use to load the world.\nSupported versions ${supportedVersions.join(', ')}`, '1.8.8') + const newVersion = disablePrompts ? '1.8.8' : prompt(`In 1.8 and before world save doesn\'t contain version info, please enter version you want to use to load the world.\nSupported versions ${supportedVersions.join(', ')}`, '1.8.8') if (!newVersion) return version = newVersion } @@ -89,7 +91,7 @@ export const loadSave = async (root = '/world') => { } - if (warnings.length) { + if (warnings.length && !disablePrompts) { const doContinue = confirm(`Continue with following warnings?\n${warnings.join('\n')}`) if (!doContinue) return } diff --git a/src/optionsStorage.js b/src/optionsStorage.js index 3632c54da..8165140ac 100644 --- a/src/optionsStorage.js +++ b/src/optionsStorage.js @@ -20,6 +20,8 @@ const defaultOptions = { numWorkers: 4, localServerOptions: {}, localUsername: 'wanderer', + preferLoadReadonly: false, + disableLoadPrompts: false } export const options = proxy( From f856bc5f5f5c0d277caa8dc0de7e570e4c8d651a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 10:50:17 +0300 Subject: [PATCH 183/318] add a way to always init eruda --- index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/index.html b/index.html index f409c4270..4054509a2 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,19 @@ + + Prismarine Web Client From 0dca522d11251cdf3603b65e06635c16eb552072 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:24:34 +0300 Subject: [PATCH 184/318] fix autoversion (download data on demand) --- scripts/esbuildPlugins.mjs | 21 ++++++++++++++++++--- src/index.js | 13 ++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index eccfd89f3..cbd0edec3 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -26,15 +26,29 @@ const plugins = [ if (!resolveDir.endsWith('minecraft-data')) return return { namespace: 'load-global-minecraft-data', - path + path, + pluginData: { + resolveDir + }, } }) build.onLoad({ filter: /.+/, namespace: 'load-global-minecraft-data', - }, () => { + }, ({ pluginData: { resolveDir } }) => { + const defaultVersionsObj = { + // default protocol data, needed for auto-select + "1.20.1": { + version: { + "minecraftVersion": "1.20.1", + "version": 763, + "majorVersion": "1.20" + }, + protocol: JSON.parse(fs.readFileSync(join(resolveDir, 'minecraft-data/data/pc/1.20/protocol.json'), 'utf8')), + } + } return { - contents: 'window.mcData ??= {};module.exports = { pc: window.mcData }', + contents: `window.mcData ??= ${JSON.stringify(defaultVersionsObj)};module.exports = { pc: window.mcData }`, loader: 'js', } }) @@ -230,6 +244,7 @@ const plugins = [ polyfillNode({ polyfills: { fs: false, + dns: false, crypto: false, events: false, http: false, diff --git a/src/index.js b/src/index.js index 550b60d76..dc7ebab65 100644 --- a/src/index.js +++ b/src/index.js @@ -364,12 +364,16 @@ async function connect (connectOptions) { let localServer try { Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) - let version = connectOptions.botVersion ?? serverOptions.version - if (version) { + const downloadMcData = async (version) => { setLoadingScreenStatus(`Downloading data for ${version}`) await loadScript(`./mc-data/${toMajorVersion(version)}.js`) } + const version = connectOptions.botVersion ?? serverOptions.version + if (version) { + downloadMcData(version) + } + if (singeplayer) { // SINGLEPLAYER EXPLAINER: // Note 1: here we have custom sync communication between server Client (flying-squid) and game client (mineflayer) @@ -411,7 +415,10 @@ async function connect (connectOptions) { viewDistance: 'tiny', checkTimeoutInterval: 240 * 1000, noPongTimeout: 240 * 1000, - closeTimeout: 240 * 1000 + closeTimeout: 240 * 1000, + async versionSelectedHook (client) { + await downloadMcData(client.version) + } }) if (singeplayer) { const _supportFeature = bot.supportFeature From bfd827990ddaea6bf7967cbb603c5d26af0dc069 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:40:28 +0300 Subject: [PATCH 185/318] fix regression (hopefully have tests) --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index dc7ebab65..82dc8b8ca 100644 --- a/src/index.js +++ b/src/index.js @@ -371,7 +371,7 @@ async function connect (connectOptions) { const version = connectOptions.botVersion ?? serverOptions.version if (version) { - downloadMcData(version) + await downloadMcData(version) } if (singeplayer) { From dfa45a57716dcad05afc477305fc1e212edb11c2 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:42:08 +0300 Subject: [PATCH 186/318] not sure how but try to disable automatic deploys on vercel --- vercel.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..4e1bcc7fa --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "github": false +} From 78d4b62d8b3f7b74310986bdfdabd15fc3e1d675 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:47:30 +0300 Subject: [PATCH 187/318] rn workflow, fixes --- .github/workflows/preview.yml | 2 +- vercel.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index ee83ba249..2294132ea 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -1,4 +1,4 @@ -name: CI Deploy Preview +name: Vercel Deploy Preview env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/vercel.json b/vercel.json index 4e1bcc7fa..1513c3ec8 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,6 @@ { - "github": false + "github": { + "enabled": false, + "silent": true + } } From bc51c9f674c4274e1fa1f8edd3e1143e7412fbbe Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:48:39 +0300 Subject: [PATCH 188/318] disable silent --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 1513c3ec8..5219613ed 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "github": { "enabled": false, - "silent": true + "silent": false } } From 29b47fb115c0b36c986db0c4d69bbb7f0271b094 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 11:56:43 +0300 Subject: [PATCH 189/318] try to increaese snapshot timeout --- cypress/integration/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/index.spec.ts b/cypress/integration/index.spec.ts index 53505b3c7..d8cac44d0 100644 --- a/cypress/integration/index.spec.ts +++ b/cypress/integration/index.spec.ts @@ -20,7 +20,7 @@ it('Loads & renders singleplayer', () => { // todo replace with data-test cy.get('#title-screen').find('.menu > div:nth-child(2) > pmui-button:nth-child(1)', { includeShadowDom: true, }).click() // todo implement load event - cy.wait(6000) + cy.wait(8000) //@ts-ignore cy.get('body').toMatchImageSnapshot({ // imageConfig: { From 6f2c030f3d6efc3d84d7df07de3de9806adde217 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 12:16:23 +0300 Subject: [PATCH 190/318] better download ui & fix qs conflict --- src/downloadAndOpenWorld.ts | 45 ++++++++++++++++++---------- src/loadSave.ts | 2 +- src/menus/loading_or_error_screen.js | 3 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/downloadAndOpenWorld.ts b/src/downloadAndOpenWorld.ts index e14e4deb1..61d7fbc7d 100644 --- a/src/downloadAndOpenWorld.ts +++ b/src/downloadAndOpenWorld.ts @@ -15,21 +15,36 @@ window.addEventListener('load', async (e) => { const response = await fetch(mapUrl) const contentLength = +response.headers.get('Content-Length') setLoadingScreenStatus(`Downloading world ${name}: have to download ${filesize(contentLength)}...`) - // const reader = response.body!.getReader() - // let doneValue - // while (true) { - // // done is true for the last chunk - // // value is Uint8Array of the chunk bytes - // const { done, value } = await reader.read() - - // if (done) { - // doneValue = value - // break - // } - - // setLoadingScreenStatus(`Downloading world ${name}: ${filesize(value.length)} / ${filesize(contentLength)}MB...`) - // } - await openWorldZip(await response.arrayBuffer()) + + let downloadedBytes = 0 + const buffer = await new Response( + new ReadableStream({ + async start(controller) { + const reader = response.body.getReader() + + while (true) { + const { done, value } = await reader.read() + + if (done) { + controller.close() + break + } + + downloadedBytes += value.byteLength + + // Calculate download progress as a percentage + const progress = (downloadedBytes / contentLength) * 100 + + // Update your progress bar or display the progress value as needed + setLoadingScreenStatus(`Download Progress: ${Math.floor(progress)}% (${filesize(downloadedBytes, { round: 2, })} / ${filesize(contentLength, { round: 2, })}))`, false, true) + + // Pass the received data to the controller + controller.enqueue(value) + } + }, + }) + ).arrayBuffer() + await openWorldZip(buffer) }) export default async () => { diff --git a/src/loadSave.ts b/src/loadSave.ts index 5ed8b967c..49ebd05d2 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -50,7 +50,7 @@ export const loadSave = async (root = '/world') => { const levelDat: import('./mcTypes').LevelDat = nbt.simplify(parsedRaw).Data const qs = new URLSearchParams(window.location.search) - version = levelDat.Version?.Name ?? qs.get('version') + version = levelDat.Version?.Name ?? qs.get('mapVersion') if (!version) { const newVersion = disablePrompts ? '1.8.8' : prompt(`In 1.8 and before world save doesn\'t contain version info, please enter version you want to use to load the world.\nSupported versions ${supportedVersions.join(', ')}`, '1.8.8') if (!newVersion) return diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index 23aa2cd97..ab23be0b3 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -40,6 +40,7 @@ class LoadingErrorScreen extends LitElement { constructor () { super() + this.hideDots = false this.hasError = false this.maybeRecoverable = true this.status = 'Waiting for JS load' @@ -73,7 +74,7 @@ class LoadingErrorScreen extends LitElement { return html`
    -
    ${this.status}${this.hasError ? '' : this._loadingDots}

    ${this.hasError ? guessProblem(this.status) : ''}

    +
    ${this.status}${this.hasError && !this.hideDots ? '' : this._loadingDots}

    ${this.hasError ? guessProblem(this.status) : ''}

    ${this.hasError ? html`
    { From 5044e4e95b4ed4f486a0efece71247e15e6edec5 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 12:21:22 +0300 Subject: [PATCH 191/318] dont test before publish --- .github/workflows/publish.yml | 18 +++++++++--------- cypress/integration/index.spec.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 17e718ca3..e316c3319 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,15 @@ jobs: run: npm i -g pnpm - run: pnpm install - run: pnpm build - - uses: cypress-io/github-action@v5 - with: - install: false - start: pnpm prod-start - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: cypress-images - path: cypress/integration/__image_snapshots__/ + # - uses: cypress-io/github-action@v5 + # with: + # install: false + # start: pnpm prod-start + # - uses: actions/upload-artifact@v3 + # if: failure() + # with: + # name: cypress-images + # path: cypress/integration/__image_snapshots__/ - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/cypress/integration/index.spec.ts b/cypress/integration/index.spec.ts index d8cac44d0..b215bc285 100644 --- a/cypress/integration/index.spec.ts +++ b/cypress/integration/index.spec.ts @@ -20,7 +20,7 @@ it('Loads & renders singleplayer', () => { // todo replace with data-test cy.get('#title-screen').find('.menu > div:nth-child(2) > pmui-button:nth-child(1)', { includeShadowDom: true, }).click() // todo implement load event - cy.wait(8000) + cy.wait(12000) //@ts-ignore cy.get('body').toMatchImageSnapshot({ // imageConfig: { From f58903231dbcdffdc83afcc5a90e6d1efddc4eec Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 12:54:48 +0300 Subject: [PATCH 192/318] fix: excellent in-game gamepad support speed packages install --- package.json | 2 +- src/controls.ts | 14 ++++++++++++-- src/index.js | 9 +++++++++ src/utils.js | 3 ++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 971fff78c..282838426 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "buffer": "^6.0.3", "clean-webpack-plugin": "^4.0.0", "constants-browserify": "^1.0.0", - "contro-max": "^0.1.0", + "contro-max": "^0.1.1", "copy-webpack-plugin": "^11.0.0", "crypto-browserify": "^3.12.0", "css-loader": "^6.8.1", diff --git a/src/controls.ts b/src/controls.ts index 36374ea38..fe883fbae 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -50,7 +50,8 @@ export const contro = new ControMax({ storeProvider: { load: () => customKeymaps, save() { }, - } + }, + gamepadPollingInterval: 10 }) export type Command = CommandEventArgument['command'] @@ -135,6 +136,9 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => { setSprinting(pressed) } break + case 'general.attackDestroy': + document.dispatchEvent(new MouseEvent(pressed ? 'mousedown' : 'mouseup', { button: 0 })) + break } } } @@ -184,7 +188,13 @@ contro.on('trigger', ({ command }) => { case 'general.command': document.getElementById('hud').shadowRoot.getElementById('chat').enableChat('/') break - // todo place / destroy + case 'general.interactPlace': + document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) + setTimeout(() => { + // todo cleanup + document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) + }) + break } } }) diff --git a/src/index.js b/src/index.js index 82dc8b8ca..67ce54540 100644 --- a/src/index.js +++ b/src/index.js @@ -67,6 +67,7 @@ const { default: updateTime } = require('./updateTime') const { options } = require('./optionsStorage') const { subscribeKey } = require('valtio/utils') const _ = require('lodash') +const { contro } = require('./controls') if ('serviceWorker' in navigator && !isCypress()) { window.addEventListener('load', () => { @@ -613,6 +614,14 @@ async function connect (connectOptions) { capturedPointer.y = e.pageY }, { passive: false }) + contro.on('stickMovement', ({ stick, vector }) => { + if (stick !== 'right') return + let { x, z } = vector + if (Math.abs(x) < 0.18) x = 0 + if (Math.abs(z) < 0.18) z = 0 + onMouseMove({ movementX: x * 10, movementY: z * 10, type: 'touchmove' }) + }) + registerListener(document, 'lostpointercapture', (e) => { if (e.pointerId === undefined || e.pointerId !== capturedPointer?.id) return clearTimeout(virtualClickTimeout) diff --git a/src/utils.js b/src/utils.js index 284a883a8..4d989f598 100644 --- a/src/utils.js +++ b/src/utils.js @@ -134,7 +134,7 @@ export function nameToMcOfflineUUID (name) { return (new UUID(javaUUID('OfflinePlayer:' + name))).toString() } -export const setLoadingScreenStatus = function (/** @type {string} */status, isError = false) { +export const setLoadingScreenStatus = function (/** @type {string} */status, isError = false, hideDots = false) { const loadingScreen = document.getElementById('loading-error-screen') // todo update in component instead @@ -143,6 +143,7 @@ export const setLoadingScreenStatus = function (/** @type {string} */status, isE miscUiState.gameLoaded = false return } + loadingScreen.hideDots = hideDots loadingScreen.hasError = isError loadingScreen.status = status } From c41c010fb05a7291e7fd49676b8a2b6b321a5084 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 7 Sep 2023 06:17:32 +0300 Subject: [PATCH 193/318] forcefully disconnect from server --- src/menus/pause_screen.js | 2 +- src/utils.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/menus/pause_screen.js b/src/menus/pause_screen.js index d6091b0f1..97f8706bd 100644 --- a/src/menus/pause_screen.js +++ b/src/menus/pause_screen.js @@ -71,7 +71,7 @@ class PauseScreen extends LitElement { openURL('https://discord.gg/4Ucm684Fq3')}>
    showModal(document.getElementById('options-screen'))}> - { + { disconnect() }}> diff --git a/src/utils.js b/src/utils.js index 4d989f598..682d40fdb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -153,8 +153,12 @@ export const disconnect = async () => { if (window.localServer) { await saveWorld() localServer.quit() + } else { + // workaround bot.end doesn't end the socket and emit end event + bot.end() + bot._client.socket.end() } - bot._client.emit('end') + bot._client.emit('end', 'You left the server') miscUiState.gameLoaded = false } From 83627a503e8340829cb3c677286ab9cd7b07a3d1 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 7 Sep 2023 06:18:31 +0300 Subject: [PATCH 194/318] small cleanup, don't specify default version --- config.json | 2 +- package.json | 2 +- src/createLocalServer.js | 3 ++- src/index.js | 2 +- src/loadSave.ts | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 05bc6538f..c7e8b61c8 100644 --- a/config.json +++ b/config.json @@ -3,5 +3,5 @@ "defaultHostPort": 25565, "defaultProxy": "", "defaultProxyPort": 0, - "defaultVersion": "1.18.2" + "defaultVersion": "" } diff --git a/package.json b/package.json index 282838426..9c8a965ec 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-refresh": "^0.14.0", - "space-squid": "github:zardoy/space-squid#everything", + "flying-squid": "github:zardoy/space-squid#everything", "speed-measure-webpack-plugin": "^1.5.0", "stats.js": "^0.17.0", "url": "^0.11.1", diff --git a/src/createLocalServer.js b/src/createLocalServer.js index d60c49ff7..931336290 100644 --- a/src/createLocalServer.js +++ b/src/createLocalServer.js @@ -1,4 +1,5 @@ -import mcServer from 'space-squid' +//@ts-check +import mcServer from 'flying-squid' import serverOptions from './defaultLocalServerOptions' import { LocalServer } from './customServer' diff --git a/src/index.js b/src/index.js index c6e70a4b1..8c5038f09 100644 --- a/src/index.js +++ b/src/index.js @@ -385,7 +385,7 @@ async function connect (connectOptions) { // tcpDns() skipped since we define connect option // in setProtocol: we emit 'connect' here below so in that file we send set_protocol and login_start (onLogin handler) // Client (class) of flying-squid (in server/login.js of mc-protocol): onLogin handler: skip most logic & go to loginClient() which assigns uuid and sends 'success' back to client (onLogin handler) and emits 'login' on the server (login.js in flying-squid handler) - // flying-squid: 'login' -> player.login -> now sends 'login' event to the client (handled in many plugins in mineflayer) -> then 'update_health' is sent which emits 'spawn' + // flying-squid: 'login' -> player.login -> now sends 'login' event to the client (handled in many plugins in mineflayer) -> then 'update_health' is sent which emits 'spawn' in mineflayer setLoadingScreenStatus('Starting local server') window.serverDataChannel ??= {} diff --git a/src/loadSave.ts b/src/loadSave.ts index 49ebd05d2..e2c5cab28 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -1,5 +1,5 @@ import fs from 'fs' -import { supportedVersions } from 'space-squid/src/lib/version' +import { supportedVersions } from 'flying-squid/src/lib/version' import * as nbt from 'prismarine-nbt' import { promisify } from 'util' import { options } from './optionsStorage' From ffaa52314ba5d7fcbc642ca4d613069799a758ad Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 7 Sep 2023 06:23:11 +0300 Subject: [PATCH 195/318] propose to remove world only for on-device saves --- minecraft-server.mjs | 2 +- src/browserfs.js | 2 ++ src/builtinCommands.ts | 3 ++- src/loadSave.ts | 1 + src/menus/loading_or_error_screen.js | 2 +- src/menus/title_screen.js | 7 +++---- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/minecraft-server.mjs b/minecraft-server.mjs index e2830a8b8..6b07c35f4 100644 --- a/minecraft-server.mjs +++ b/minecraft-server.mjs @@ -1,5 +1,5 @@ //@ts-check -import mcServer from 'space-squid' +import mcServer from 'flying-squid' /** @type {import('minecraft-protocol').ServerOptions & Record} */ const serverOptions = { diff --git a/src/browserfs.js b/src/browserfs.js index 5ea250a1c..a87d1e6a6 100644 --- a/src/browserfs.js +++ b/src/browserfs.js @@ -150,6 +150,7 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} fsState.isReadonly = !writeAccess fsState.syncFs = false + fsState.inMemorySave = false loadSave() } @@ -175,6 +176,7 @@ export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = fsState.isReadonly = true fsState.syncFs = true + fsState.inMemorySave = false if (fs.existsSync('/world/level.dat')) { loadSave() diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index a3bc13d87..d3cf1a0b5 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -63,9 +63,10 @@ const commands = [ { command: '/reset-world -y', invoke: async () => { - if (fsState.isReadonly || !fsState.syncFs) return + if (fsState.inMemorySave) return // todo for testing purposes sessionStorage.oldData = localStorage + console.log('World removed. Old data saved to sessionStorage.oldData') localServer.quit() // todo browserfs bug fs.rmdirSync(localServer.options.worldFolder, { recursive: true }) diff --git a/src/loadSave.ts b/src/loadSave.ts index e2c5cab28..c523500b1 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -14,6 +14,7 @@ const parseNbt = promisify(nbt.parse) export const fsState = proxy({ isReadonly: false, syncFs: false, + inMemorySave: false, }) const PROPOSE_BACKUP = true diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index ab23be0b3..f04bad38a 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -87,7 +87,7 @@ class LoadingErrorScreen extends LitElement { } document.getElementById('play-screen').style.display = 'block' addPanoramaCubeMap() - }}> { + }}> { if (!confirm('Are you sure you want to delete all local world content?')) return for (const key of Object.keys(localStorage)) { if (/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/g.test(key) || key === '/') { diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index a3d4effa5..b6d83f4c0 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -160,10 +160,9 @@ class TitleScreen extends LitElement {
    { this.style.display = 'none' - Object.assign(fsState, { - isReadonly: false, - syncFs: true, - }) + fsState.isReadonly = false + fsState.syncFs = true + fsState.inMemorySave = true const notFirstTime = fs.existsSync('./world/level.dat') if (notFirstTime && !options.localServerOptions.version) { options.localServerOptions.version = '1.16.1' // legacy version, now we use 1.8.8 From 33ddfa1239fb2e634b679599439996bbc6f20acc Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 7 Sep 2023 06:24:37 +0300 Subject: [PATCH 196/318] fix modal restore on title screen --- src/globalState.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/globalState.js b/src/globalState.js index 86d6720f8..7c5b884fe 100644 --- a/src/globalState.js +++ b/src/globalState.js @@ -36,6 +36,9 @@ const defaultModalActions = { } } +/** + * @returns true if operation was successful + */ const showModalInner = (/** @type {Modal} */ modal) => { const cancel = modal.elem?.show?.() if (cancel && cancel !== customDisplayManageKeyword) return false @@ -54,8 +57,8 @@ export const showModal = (/** @type {HTMLElement & Record | {reactT /** * * @param {*} data - * @param {{ force?: boolean, restorePrevious?: boolean, }} options - * @returns + * @param {{ force?: boolean, restorePrevious?: boolean }} options + * @returns true if previous modal was restored */ export const hideModal = (modal = activeModalStack.slice(-1)[0], data = undefined, options = {}) => { const { force = false, restorePrevious = true } = options @@ -77,13 +80,15 @@ export const hideModal = (modal = activeModalStack.slice(-1)[0], data = undefine } } -export const hideCurrentModal = (_data = undefined, preActions = undefined) => { +export const hideCurrentModal = (_data = undefined, restoredActions = undefined) => { if (hideModal(undefined, undefined)) { - preActions?.() - if (!isGameActive()) { - showModal(document.getElementById('title-screen')) - } else { - pointerLock.requestPointerLock() // will work only if no modals left + restoredActions?.() + if (activeModalStack.length === 0) { + if (!isGameActive()) { + showModal(document.getElementById('title-screen')) + } else { + pointerLock.requestPointerLock() + } } } } From e2670a566aa7b109c95133c6fe40da62edcf682c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 7 Sep 2023 15:58:02 +0300 Subject: [PATCH 197/318] refactor: migrate new code to typescript! --- esbuild.mjs | 3 +- src/{browserfs.js => browserfs.ts} | 15 +- ...ateLocalServer.js => createLocalServer.ts} | 0 src/cursor.js | 1 - src/customServer.js | 36 ---- src/customServer.ts | 33 +++ src/{globalState.js => globalState.ts} | 37 ++-- src/{guessProblem.js => guessProblem.ts} | 0 src/importsWorkaround.js | 2 + src/{index.js => index.ts} | 191 +++++++++--------- src/{optionsStorage.js => optionsStorage.ts} | 16 +- src/optionsStorageTypes.ts | 3 - src/{updateTime.js => updateTime.ts} | 3 +- src/{utils.js => utils.ts} | 18 +- src/{watchOptions.js => watchOptions.ts} | 1 - 15 files changed, 177 insertions(+), 182 deletions(-) rename src/{browserfs.js => browserfs.ts} (93%) rename src/{createLocalServer.js => createLocalServer.ts} (100%) delete mode 100644 src/customServer.js create mode 100644 src/customServer.ts rename src/{globalState.js => globalState.ts} (77%) rename src/{guessProblem.js => guessProblem.ts} (100%) create mode 100644 src/importsWorkaround.js rename src/{index.js => index.ts} (83%) rename src/{optionsStorage.js => optionsStorage.ts} (78%) delete mode 100644 src/optionsStorageTypes.ts rename src/{updateTime.js => updateTime.ts} (95%) rename src/{utils.js => utils.ts} (92%) rename src/{watchOptions.js => watchOptions.ts} (94%) diff --git a/esbuild.mjs b/esbuild.mjs index ad0d3e293..7eff754a9 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -18,6 +18,7 @@ let baseConfig = {} // } try { + //@ts-ignore await import('./localSettings.mjs') } catch { } @@ -39,7 +40,7 @@ const buildingVersion = new Date().toISOString().split(':')[0] const ctx = await esbuild.context({ bundle: true, - entryPoints: ['src/index.js'], + entryPoints: ['src/index.ts'], target: ['es2020'], jsx: 'automatic', jsxDev: dev, diff --git a/src/browserfs.js b/src/browserfs.ts similarity index 93% rename from src/browserfs.js rename to src/browserfs.ts index a87d1e6a6..d94cfbb0e 100644 --- a/src/browserfs.js +++ b/src/browserfs.ts @@ -78,7 +78,7 @@ fs.promises.open = async (...args) => { fd, filename: args[0], close: () => { - return new Promise(resolve => { + return new Promise(resolve => { fs.close(fd, (err) => { if (err) { throw err @@ -110,9 +110,8 @@ window.removeFileRecursiveSync = removeFileRecursiveSync const SUPPORT_WRITE = true -export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} */dragndropHandle = undefined) => { - /** @type {FileSystemDirectoryHandle} */ - let _directoryHandle +export const openWorldDirectory = async (dragndropHandle?: FileSystemDirectoryHandle) => { + let _directoryHandle: FileSystemDirectoryHandle if (dragndropHandle) { _directoryHandle = dragndropHandle } else { @@ -130,7 +129,7 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} const doContinue = writeAccess || !SUPPORT_WRITE || options.disableLoadPrompts || confirm('Continue in readonly mode?') if (!doContinue) return - await new Promise(resolve => { + await new Promise(resolve => { browserfs.configure({ // todo fs: 'MountableFileSystem', @@ -154,8 +153,8 @@ export const openWorldDirectory = async (/** @type {FileSystemDirectoryHandle?} loadSave() } -export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = file['name']) => { - await new Promise(async resolve => { +export const openWorldZip = async (file: File | ArrayBuffer, name = file['name']) => { + await new Promise(async resolve => { browserfs.configure({ // todo fs: 'MountableFileSystem', @@ -205,7 +204,7 @@ export const openWorldZip = async (/** @type {File | ArrayBuffer} */file, name = } } -export async function generateZipAndWorld () { +export async function generateZipAndWorld() { const zip = new JSZip() zip.folder('world') diff --git a/src/createLocalServer.js b/src/createLocalServer.ts similarity index 100% rename from src/createLocalServer.js rename to src/createLocalServer.ts diff --git a/src/cursor.js b/src/cursor.js index bb3c1bbe1..65a3d4ed8 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -1,4 +1,3 @@ -//@ts-check /* global THREE performance */ // wouldn't better to create atlas instead? diff --git a/src/customServer.js b/src/customServer.js deleted file mode 100644 index d4206839d..000000000 --- a/src/customServer.js +++ /dev/null @@ -1,36 +0,0 @@ -//@ts-check -const EventEmitter = require('events').EventEmitter - -const Client = require('minecraft-protocol/src/client') - -const customCommunication = { - sendData: function (data) { - window.serverDataChannel[this.isServer ? 'emitClient' : 'emitServer'](data) - }, - receiverSetup: function (processData) { - window.serverDataChannel[this.isServer ? 'emitServer' : 'emitClient'] = (data) => { - processData(data) - } - } -} - -module.exports.customCommunication = customCommunication - -module.exports.LocalServer = class LocalServer extends EventEmitter { - constructor (version, customPackets, hideErrors = false) { - super() - this.version = version - this.socketServer = null - this.cipher = null - this.decipher = null - this.clients = {} - this.customPackets = customPackets - this.hideErrors = hideErrors - } - - listen () { - this.emit('connection', new Client(true, this.version, this.customPackets, this.hideErrors, customCommunication)) - } - - close () { } -} diff --git a/src/customServer.ts b/src/customServer.ts new file mode 100644 index 000000000..c2a6c8229 --- /dev/null +++ b/src/customServer.ts @@ -0,0 +1,33 @@ +import EventEmitter from 'events' + +import Client from 'minecraft-protocol/src/client' + +export const customCommunication = { + sendData(data) { + //@ts-ignore + window.serverDataChannel[this.isServer ? 'emitClient' : 'emitServer'](data) + }, + receiverSetup(processData) { + //@ts-ignore + window.serverDataChannel[this.isServer ? 'emitServer' : 'emitClient'] = (data) => { + processData(data) + } + } +} + +export class LocalServer extends EventEmitter.EventEmitter { + socketServer = null + cipher = null + decipher = null + clients = {} + + constructor(public version, public customPackets, public hideErrors = false) { + super() + } + + listen() { + this.emit('connection', new Client(true, this.version, this.customPackets, this.hideErrors, customCommunication)) + } + + close() { } +} diff --git a/src/globalState.js b/src/globalState.ts similarity index 77% rename from src/globalState.js rename to src/globalState.ts index 7c5b884fe..e53700df5 100644 --- a/src/globalState.js +++ b/src/globalState.ts @@ -6,32 +6,29 @@ import { options } from './optionsStorage' // todo: refactor structure with support of hideNext=false -/** - * @typedef {({elem?: HTMLElement & Record} & {reactType?: string})} Modal - * @typedef {{callback, label}} ContextMenuItem - */ +type Modal = ({ elem?: HTMLElement & Record } & { reactType?: string }) + +type ContextMenuItem = { callback; label } -/** @type {Modal[]} */ -export const activeModalStack = proxy([]) +export const activeModalStack: Modal[] = proxy([]) -export const replaceActiveModalStack = (name, newModalStack = activeModalStacks[name]) => { +export const replaceActiveModalStack = (name: string, newModalStack = activeModalStacks[name]) => { hideModal(undefined, undefined, { restorePrevious: false, force: true, }) activeModalStack.splice(0, activeModalStack.length, ...newModalStack) // todo restore previous } -/** @type {Record} */ -export const activeModalStacks = {} +export const activeModalStacks: Record = {} window.activeModalStack = activeModalStack export const customDisplayManageKeyword = 'custom' const defaultModalActions = { - show (/** @type {Modal} */modal) { + show(modal: Modal) { if (modal.elem) modal.elem.style.display = 'block' }, - hide (/** @type {Modal} */modal) { + hide(modal: Modal) { if (modal.elem) modal.elem.style.display = 'none' } } @@ -39,14 +36,14 @@ const defaultModalActions = { /** * @returns true if operation was successful */ -const showModalInner = (/** @type {Modal} */ modal) => { +const showModalInner = (modal: Modal) => { const cancel = modal.elem?.show?.() if (cancel && cancel !== customDisplayManageKeyword) return false if (cancel !== 'custom') defaultModalActions.show(modal) return true } -export const showModal = (/** @type {HTMLElement & Record | {reactType: string}} */ elem) => { +export const showModal = (elem: (HTMLElement & Record) | { reactType: string }) => { const resolved = elem instanceof HTMLElement ? { elem: ref(elem) } : elem const curModal = activeModalStack.slice(-1)[0] if (elem === curModal?.elem || !showModalInner(resolved)) return @@ -56,11 +53,9 @@ export const showModal = (/** @type {HTMLElement & Record | {reactT /** * - * @param {*} data - * @param {{ force?: boolean, restorePrevious?: boolean }} options * @returns true if previous modal was restored */ -export const hideModal = (modal = activeModalStack.slice(-1)[0], data = undefined, options = {}) => { +export const hideModal = (modal = activeModalStack.slice(-1)[0], data: any = undefined, options: { force?: boolean; restorePrevious?: boolean } = {}) => { const { force = false, restorePrevious = true } = options if (!modal) return let cancel = modal.elem?.hide?.(data) @@ -84,7 +79,7 @@ export const hideCurrentModal = (_data = undefined, restoredActions = undefined) if (hideModal(undefined, undefined)) { restoredActions?.() if (activeModalStack.length === 0) { - if (!isGameActive()) { + if (!isGameActive(false)) { showModal(document.getElementById('title-screen')) } else { pointerLock.requestPointerLock() @@ -95,9 +90,9 @@ export const hideCurrentModal = (_data = undefined, restoredActions = undefined) // --- -export const currentContextMenu = proxy({ items: /** @type {ContextMenuItem[] | null} */[], x: 0, y: 0, }) +export const currentContextMenu = proxy({ items: [] as ContextMenuItem[] | null, x: 0, y: 0, }) -export const showContextmenu = (/** @type {ContextMenuItem[]} */items, { clientX, clientY }) => { +export const showContextmenu = (items: ContextMenuItem[], { clientX, clientY }) => { Object.assign(currentContextMenu, { items, x: clientX, @@ -108,12 +103,12 @@ export const showContextmenu = (/** @type {ContextMenuItem[]} */items, { clientX // --- export const miscUiState = proxy({ - currentTouch: null, + currentTouch: null as boolean | null, singleplayer: false, gameLoaded: false, }) -export const isGameActive = (foregroundCheck) => { +export const isGameActive = (foregroundCheck: boolean) => { if (foregroundCheck && activeModalStack.length) return false return miscUiState.gameLoaded } diff --git a/src/guessProblem.js b/src/guessProblem.ts similarity index 100% rename from src/guessProblem.js rename to src/guessProblem.ts diff --git a/src/importsWorkaround.js b/src/importsWorkaround.js new file mode 100644 index 000000000..33815d6b2 --- /dev/null +++ b/src/importsWorkaround.js @@ -0,0 +1,2 @@ +// workaround for mineflayer +process.versions.node = '18.0.0' diff --git a/src/index.js b/src/index.ts similarity index 83% rename from src/index.js rename to src/index.ts index 8c5038f09..2ae94c94f 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,73 +1,88 @@ -//@ts-check -/* global THREE */ -// todo cant ignore fs thus doesnt work in browser -// const soureMapSupport = require('source-map-support') -// soureMapSupport.install({ -// environment: 'browser', -// }) -require('./styles.css') -require('iconify-icon') -require('./chat') -require('./inventory') -//@ts-ignore -require('./globals.js') -require('./watchOptions') - -// workaround for mineflayer -process.versions.node = '18.0.0' - -require('./menus/components/button') -require('./menus/components/edit_box') -require('./menus/components/slider') -require('./menus/components/hotbar') -require('./menus/components/health_bar') -require('./menus/components/food_bar') -require('./menus/components/breath_bar') -require('./menus/components/debug_overlay') -require('./menus/components/playerlist_overlay') -require('./menus/components/bossbars_overlay') -require('./menus/hud') -require('./menus/play_screen') -require('./menus/pause_screen') -require('./menus/loading_or_error_screen') -require('./menus/keybinds_screen') -require('./menus/options_screen') -require('./menus/advanced_options_screen') -require('./menus/notification') -require('./menus/title_screen') - -require('./optionsStorage') -require('./reactUi.jsx') -require('./controls') -require('./dragndrop') -require('./browserfs') -require('./eruda') -require('./downloadAndOpenWorld') - -const net = require('net') -const Stats = require('stats.js') - -const mineflayer = require('mineflayer') -const { WorldView, Viewer } = require('prismarine-viewer/viewer') -const pathfinder = require('mineflayer-pathfinder') -const { Vec3 } = require('vec3') - -const Cursor = require('./cursor').default +import './importsWorkaround' +import './styles.css' +import './globals' +import 'iconify-icon' +import './chat' +import './inventory' + +import './menus/components/button' +import './menus/components/edit_box' +import './menus/components/slider' +import './menus/components/hotbar' +import './menus/components/health_bar' +import './menus/components/food_bar' +import './menus/components/breath_bar' +import './menus/components/debug_overlay' +import './menus/components/playerlist_overlay' +import './menus/components/bossbars_overlay' +import './menus/hud' +import './menus/play_screen' +import './menus/pause_screen' +import './menus/loading_or_error_screen' +import './menus/keybinds_screen' +import './menus/options_screen' +import './menus/advanced_options_screen' +import { notification } from './menus/notification' +import './menus/title_screen' + +import './optionsStorage' +import './reactUi.jsx' +import './controls' +import './dragndrop' +import './browserfs' +import './eruda' +import './downloadAndOpenWorld' + +import net from 'net' +import Stats from 'stats.js' +import mineflayer from 'mineflayer' +import { WorldView, Viewer } from 'prismarine-viewer/viewer' +import pathfinder from 'mineflayer-pathfinder' +import { Vec3 } from 'vec3' + +import Cursor from './cursor' + +import * as THREE from 'three' + +import { initVR } from './vr' +import { + activeModalStack, + showModal, + hideCurrentModal, + activeModalStacks, + replaceActiveModalStack, + isGameActive, + miscUiState, + gameAdditionalState +} from './globalState' + +import { + pointerLock, + goFullscreen, + toNumber, + isCypress, + loadScript, + toMajorVersion, + setLoadingScreenStatus +} from './utils' + +import { + removePanorama, + addPanoramaCubeMap, + initPanoramaOptions +} from './panorama' + +import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' +import serverOptions from './defaultLocalServerOptions' +import { customCommunication } from './customServer' +import updateTime from './updateTime' +import { options } from './optionsStorage' +import { subscribeKey } from 'valtio/utils' +import _ from 'lodash' +import { contro } from './controls' + //@ts-ignore -global.THREE = require('three') -const { initVR } = require('./vr') -const { activeModalStack, showModal, hideModal, hideCurrentModal, activeModalStacks, replaceActiveModalStack, isGameActive, miscUiState, gameAdditionalState } = require('./globalState') -const { pointerLock, goFullscreen, toNumber, isCypress, loadScript, toMajorVersion, setLoadingScreenStatus } = require('./utils') -const { notification } = require('./menus/notification') -const { removePanorama, addPanoramaCubeMap, initPanoramaOptions } = require('./panorama') -const { startLocalServer, unsupportedLocalServerFeatures } = require('./createLocalServer') -const serverOptions = require('./defaultLocalServerOptions') -const { customCommunication } = require('./customServer') -const { default: updateTime } = require('./updateTime') -const { options } = require('./optionsStorage') -const { subscribeKey } = require('valtio/utils') -const _ = require('lodash') -const { contro } = require('./controls') +window.THREE = THREE if ('serviceWorker' in navigator && !isCypress()) { window.addEventListener('load', () => { @@ -121,13 +136,12 @@ renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) // Create viewer -/** @type {import('../prismarine-viewer/viewer/lib/viewer').Viewer} */ -const viewer = new Viewer(renderer, options.numWorkers) +const viewer: import('../prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers) initPanoramaOptions(viewer) const frameLimit = toNumber(localStorage.frameLimit) let interval = frameLimit && 1000 / frameLimit -window.addEventListener('option-change', (/** @type {any} */{ detail }) => { +window.addEventListener('option-change', ({ detail }: any) => { if (detail.name === 'frameLimit') interval = toNumber(detail.value) && 1000 / toNumber(detail.value) }) @@ -135,7 +149,7 @@ let nextFrameFn = [] let postRenderFrameFn = () => { } let delta = 0 let lastTime = performance.now() -const renderFrame = (/** @type {DOMHighResTimeStamp} */ time) => { +const renderFrame = (time: DOMHighResTimeStamp) => { if (window.stopLoop) return window.requestAnimationFrame(renderFrame) if (window.stopRender) return @@ -179,7 +193,7 @@ const pauseMenu = document.getElementById('pause-screen') let mouseMovePostHandle = (e) => { } let lastMouseCall -function onMouseMove (e) { +function onMouseMove(e) { if (e.type !== 'touchmove' && !pointerLock.hasPointerLock) return e.stopPropagation?.() const now = performance.now() @@ -197,12 +211,12 @@ function onMouseMove (e) { window.addEventListener('mousemove', onMouseMove, { capture: true }) -function hideCurrentScreens () { +function hideCurrentScreens() { activeModalStacks['main-menu'] = activeModalStack replaceActiveModalStack('', []) } -async function main () { +async function main() { const menu = document.getElementById('play-screen') menu.addEventListener('connect', e => { const options = e.detail @@ -231,8 +245,7 @@ let timeouts = [] let intervals = [] // only for dom listeners (no removeAllListeners) // todo refactor them out of connect fn instead -/** @type {import('./utilsTs').RegisterListener} */ -const registerListener = (target, event, callback) => { +const registerListener: import('./utilsTs').RegisterListener = (target, event, callback) => { target.addEventListener(event, callback) listeners.push({ target, event, callback }) } @@ -250,7 +263,7 @@ const removeAllListeners = () => { /** * @param {{ server: any; port?: string; singleplayer: any; username: any; password: any; proxy: any; botVersion?: any; serverOverrides? }} connectOptions */ -async function connect (connectOptions) { +async function connect(connectOptions) { const menu = document.getElementById('play-screen') menu.style = 'display: none;' removePanorama() @@ -305,8 +318,7 @@ async function connect (connectOptions) { setLoadingScreenStatus('Logging in') - /** @type {mineflayer.Bot} */ - let bot + let bot: mineflayer.Bot const destroyAll = () => { viewer.resetAll() window.localServer = undefined @@ -407,7 +419,7 @@ async function connect (connectOptions) { version: connectOptions.botVersion === '' ? false : connectOptions.botVersion, ...singeplayer ? { version: serverOptions.version, - connect () { }, + connect() { }, keepAlive: false, customCommunication } : {}, @@ -417,7 +429,7 @@ async function connect (connectOptions) { checkTimeoutInterval: 240 * 1000, noPongTimeout: 240 * 1000, closeTimeout: 240 * 1000, - async versionSelectedHook (client) { + async versionSelectedHook(client) { await downloadMcData(client.version) } }) @@ -459,8 +471,7 @@ async function connect (connectOptions) { bot.once('login', () => { // server is ok, add it to the history - /** @type {string[]} */ - const serverHistory = JSON.parse(localStorage.getItem('serverHistory') || '[]') + const serverHistory: string[] = JSON.parse(localStorage.getItem('serverHistory') || '[]') serverHistory.unshift(connectOptions.server) localStorage.setItem('serverHistory', JSON.stringify([...new Set(serverHistory)])) @@ -480,8 +491,7 @@ async function connect (connectOptions) { const center = bot.entity.position - /** @type {import('../prismarine-viewer/viewer/lib/worldView').WorldView} */ - const worldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) + const worldView: import('../prismarine-viewer/viewer/lib/worldView').WorldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) let fovSetting = optionsScrn.fov const updateFov = () => { @@ -542,7 +552,7 @@ async function connect (connectOptions) { updateTime(bot) // Bot position callback - function botPosition () { + function botPosition() { // this might cause lag, but not sure viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) worldView.updatePosition(bot.entity.position) @@ -558,7 +568,7 @@ async function connect (connectOptions) { bot.entity.yaw -= x } - function changeCallback () { + function changeCallback() { notification.show = false if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { showModal(pauseMenu) @@ -573,8 +583,7 @@ async function connect (connectOptions) { const touchStartBreakingBlockMs = 500 let virtualClickActive = false let virtualClickTimeout - /** @type {{id,x,y,sourceX,sourceY,activateCameraMove,time}?} */ - let capturedPointer + let capturedPointer: { id; x; y; sourceX; sourceY; activateCameraMove; time; } | null registerListener(document, 'pointerdown', (e) => { const clickedEl = e.composedPath()[0] if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || capturedPointer || e.pointerId === undefined) { diff --git a/src/optionsStorage.js b/src/optionsStorage.ts similarity index 78% rename from src/optionsStorage.js rename to src/optionsStorage.ts index 8165140ac..f344f485b 100644 --- a/src/optionsStorage.js +++ b/src/optionsStorage.ts @@ -1,15 +1,14 @@ -//@ts-check // todo implement async options storage import { proxy, subscribe } from 'valtio/vanilla' -import { mergeAny } from './optionsStorageTypes' // weird webpack configuration bug: it cant import valtio/utils in this file import { subscribeKey } from 'valtio/utils' +const mergeAny: (arg1: T, arg2: any) => T = Object.assign + const defaultOptions = { renderDistance: 4, - /** @type {boolean | undefined | null} */ - alwaysBackupWorldBeforeLoading: undefined, + alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null, alwaysShowMobileControls: false, maxMultiplayerRenderDistance: 6, excludeCommunicationDebugEvents: [], @@ -34,11 +33,12 @@ subscribe(options, () => { localStorage.options = JSON.stringify(options) }) -/** @type {import('./optionsStorageTypes').WatchValue} */ -export const watchValue = (proxy, callback) => { - const watchedProps = new Set() +type WatchValue = >(proxy: T, callback: (p: T) => void) => void + +export const watchValue: WatchValue = (proxy, callback) => { + const watchedProps = new Set() callback(new Proxy(proxy, { - get (target, p, receiver) { + get(target, p, receiver) { watchedProps.add(p.toString()) return Reflect.get(target, p, receiver) }, diff --git a/src/optionsStorageTypes.ts b/src/optionsStorageTypes.ts deleted file mode 100644 index b554da4d4..000000000 --- a/src/optionsStorageTypes.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const mergeAny: (arg1: T, arg2: any) => T = Object.assign - -export type WatchValue = >(proxy: T, callback: (p: T) => void) => void diff --git a/src/updateTime.js b/src/updateTime.ts similarity index 95% rename from src/updateTime.js rename to src/updateTime.ts index 79d90044a..3d871ff5f 100644 --- a/src/updateTime.js +++ b/src/updateTime.ts @@ -1,5 +1,4 @@ -//@ts-check -export default (/** @type {import('mineflayer').Bot} */bot) => { +export default (bot: import('mineflayer').Bot) => { bot.on('time', () => { // 0 morning const dayTotal = 24_000 diff --git a/src/utils.js b/src/utils.ts similarity index 92% rename from src/utils.js rename to src/utils.ts index 682d40fdb..6ca9b6b09 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -1,4 +1,3 @@ -//@ts-check import { activeModalStack, miscUiState, showModal } from './globalState' import { notification } from './menus/notification' import * as crypto from 'crypto' @@ -24,11 +23,11 @@ export const toNumber = (val) => { } export const pointerLock = { - get hasPointerLock () { + get hasPointerLock() { return document.pointerLockElement }, justHitEscape: false, - async requestPointerLock () { + async requestPointerLock() { if (document.getElementById('hud').style.display === 'none' || activeModalStack.length || !document.documentElement.requestPointerLock || miscUiState.currentTouch) { return } @@ -44,9 +43,8 @@ export const pointerLock = { if (!(document.fullscreenElement && navigator['keyboard']) && this.justHitEscape) { displayBrowserProblem() } else { - /** @type {any} */ //@ts-ignore - const promise = document.documentElement.requestPointerLock({ + const promise: any = document.documentElement.requestPointerLock({ unadjustedMovement: options.mouseRawInput }) promise?.catch((error) => { @@ -70,7 +68,7 @@ window.getScreenRefreshRate = getScreenRefreshRate /** * Allows to obtain the estimated Hz of the primary monitor in the system. */ -export function getScreenRefreshRate () { +export function getScreenRefreshRate() { let requestId = null let callbackTriggered = false let resolve @@ -121,7 +119,7 @@ export const isCypress = () => { } // https://github.com/PrismarineJS/node-minecraft-protocol/blob/cf1f67117d586b5e6e21f0d9602da12e9fcf46b6/src/server/login.js#L170 -function javaUUID (s) { +function javaUUID(s: string) { const hash = crypto.createHash('md5') hash.update(s, 'utf8') const buffer = hash.digest() @@ -130,11 +128,11 @@ function javaUUID (s) { return buffer } -export function nameToMcOfflineUUID (name) { +export function nameToMcOfflineUUID(name) { return (new UUID(javaUUID('OfflinePlayer:' + name))).toString() } -export const setLoadingScreenStatus = function (/** @type {string} */status, isError = false, hideDots = false) { +export const setLoadingScreenStatus = function (status: string, isError = false, hideDots = false) { const loadingScreen = document.getElementById('loading-error-screen') // todo update in component instead @@ -162,7 +160,7 @@ export const disconnect = async () => { miscUiState.gameLoaded = false } -export const loadScript = async function (/** @type {string} */ scriptSrc) { +export const loadScript = async function (scriptSrc: string) { if (document.querySelector(`script[src="${scriptSrc}"]`)) { return } diff --git a/src/watchOptions.js b/src/watchOptions.ts similarity index 94% rename from src/watchOptions.js rename to src/watchOptions.ts index 7757208f5..de5450330 100644 --- a/src/watchOptions.js +++ b/src/watchOptions.ts @@ -1,4 +1,3 @@ -//@ts-check // not all options are watched here import { subscribeKey } from 'valtio/utils' From 6e8e3cabb49eec1e85cb8d6fbe3922486a7850bf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 7 Sep 2023 16:46:48 +0300 Subject: [PATCH 198/318] correctly display cursor mesh using collision data --- src/cursor.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/cursor.js b/src/cursor.js index 65a3d4ed8..b52493528 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -110,6 +110,7 @@ class Cursor { update (/** @type {import('mineflayer').Bot} */bot) { /** diggable block */ let cursorBlock = bot.blockAtCursor(6) + let cursorBlockOriginal = cursorBlock if (!bot.canDigBlock(cursorBlock)) cursorBlock = null let cursorChanged = !cursorBlock !== !this.cursorBlock @@ -146,26 +147,40 @@ class Cursor { } catch (e) { } // to be reworked in mineflayer, then remove the try here } + // Show cursor + if (!cursorBlock) { + this.cursorMesh.visible = false + } else { + for (const collisionData of cursorBlockOriginal.shapes.slice(0, 1) ?? []) { + const width = collisionData[3] - collisionData[0] + const height = collisionData[4] - collisionData[1] + const depth = collisionData[5] - collisionData[2] + + const initialSize = 1.001 + this.cursorMesh.scale.set(width * initialSize, height * initialSize, depth * initialSize) + this.blockBreakMesh.scale.set(width * initialSize, height * initialSize, depth * initialSize) + // this.cursorMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5) + const centerX = (collisionData[3] + collisionData[0]) / 2 + const centerY = (collisionData[4] + collisionData[1]) / 2 + const centerZ = (collisionData[5] + collisionData[2]) / 2 + this.cursorMesh.position.set(cursorBlock.position.x + centerX, cursorBlock.position.y + centerY, cursorBlock.position.z + centerZ) + this.blockBreakMesh.position.set(cursorBlock.position.x + centerX, cursorBlock.position.y + centerY, cursorBlock.position.z + centerZ) + } + this.cursorMesh.visible = true + // change + } + // Show break animation if (cursorBlock && this.buttons[0]) { const elapsed = performance.now() - this.breakStartTime const time = bot.digTime(cursorBlock) const state = Math.floor((elapsed / time) * 10) - this.blockBreakMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5) this.blockBreakMesh.material.map = this.breakTextures[state] this.blockBreakMesh.visible = true } else { this.blockBreakMesh.visible = false } - // Show cursor - if (!cursorBlock) { - this.cursorMesh.visible = false - } else { - this.cursorMesh.visible = true - this.cursorMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5) - } - // Update state this.cursorBlock = cursorBlock this.lastButtons[0] = this.buttons[0] From b8b8f7bfd8573ee15a2cefa4ed3391fb2e62db11 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 8 Sep 2023 16:43:40 +0300 Subject: [PATCH 199/318] upstream server changes --- src/createLocalServer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/createLocalServer.ts b/src/createLocalServer.ts index 931336290..ec5cb073b 100644 --- a/src/createLocalServer.ts +++ b/src/createLocalServer.ts @@ -5,6 +5,8 @@ import { LocalServer } from './customServer' export const startLocalServer = () => { const server = mcServer.createMCServer({ ...serverOptions, Server: LocalServer }) + server.formatMessage = (message) => `[server] ${message}` + server.options = serverOptions return server } From 3decbe3626eec9e047a54b43c7148c55d4e7a5cc Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 8 Sep 2023 15:49:19 +0300 Subject: [PATCH 200/318] up anvil for singleplayer compatibility --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c8a965ec..c4ce1af6c 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "pnpm": { "overrides": { "minecraft-data": "latest", - "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#chunk-impl-require-fix", + "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra" } } From 2895a1fcc03c3a194e04f68ecebe90b5cb186c2a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 8 Sep 2023 19:39:42 +0300 Subject: [PATCH 201/318] fix too fast block placing --- src/cursor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cursor.js b/src/cursor.js index b52493528..4526e4376 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -126,7 +126,7 @@ class Cursor { // check instead? //@ts-ignore bot._placeBlockWithOptions(cursorBlock, vecArray[cursorBlock.face], { delta, forceLook: 'ignore' }).catch(console.warn) - // this.lastBlockPlaced = 0 + this.lastBlockPlaced = 0 } // Start break From 898648d849f7a890f7d8b7e956ba6c6ea22df00f Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 8 Sep 2023 19:42:18 +0300 Subject: [PATCH 202/318] fix: display non-breakable blocks allow to break anything in creative --- src/cursor.js | 14 +++++++------- src/index.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cursor.js b/src/cursor.js index 4526e4376..c3166d34d 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -1,3 +1,4 @@ +//@ts-check /* global THREE performance */ // wouldn't better to create atlas instead? @@ -108,10 +109,9 @@ class Cursor { // todo this shouldnt be done in the render loop, migrate the code to dom events to avoid delays on lags update (/** @type {import('mineflayer').Bot} */bot) { - /** diggable block */ - let cursorBlock = bot.blockAtCursor(6) - let cursorBlockOriginal = cursorBlock - if (!bot.canDigBlock(cursorBlock)) cursorBlock = null + const cursorBlock = bot.blockAtCursor(5) + let cursorBlockDiggable = cursorBlock + if (!bot.canDigBlock(cursorBlock) && bot.game.gameMode !== 'creative') cursorBlockDiggable = null let cursorChanged = !cursorBlock !== !this.cursorBlock if (cursorBlock && this.cursorBlock) { @@ -131,7 +131,7 @@ class Cursor { // Start break // todo last check doesnt work as cursorChanged happens once (after that check is false) - if (cursorBlock && this.buttons[0] && (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100))) { + if (cursorBlockDiggable && this.buttons[0] && (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100))) { this.breakStartTime = performance.now() bot.dig(cursorBlock, 'ignore').catch((err) => { if (err.message === 'Digging aborted') return @@ -151,7 +151,7 @@ class Cursor { if (!cursorBlock) { this.cursorMesh.visible = false } else { - for (const collisionData of cursorBlockOriginal.shapes.slice(0, 1) ?? []) { + for (const collisionData of cursorBlock.shapes.slice(0, 1) ?? []) { const width = collisionData[3] - collisionData[0] const height = collisionData[4] - collisionData[1] const depth = collisionData[5] - collisionData[2] @@ -171,7 +171,7 @@ class Cursor { } // Show break animation - if (cursorBlock && this.buttons[0]) { + if (cursorBlockDiggable && this.buttons[0]) { const elapsed = performance.now() - this.breakStartTime const time = bot.digTime(cursorBlock) const state = Math.floor((elapsed / time) * 10) diff --git a/src/index.ts b/src/index.ts index 2ae94c94f..42f09d9df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -531,9 +531,9 @@ async function connect(connectOptions) { const cursor = new Cursor(viewer, renderer, bot) postRenderFrameFn = () => { - debugMenu.cursorBlock = cursor.cursorBlock viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) cursor.update(bot) + debugMenu.cursorBlock = cursor.cursorBlock } try { From 3008be0ca6a151b21ea06f829b3c8a64b6982cec Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 9 Sep 2023 23:49:49 +0300 Subject: [PATCH 203/318] select hotbar on touch --- src/menus/components/hotbar.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/menus/components/hotbar.js b/src/menus/components/hotbar.js index 7f65681e5..94ead334c 100644 --- a/src/menus/components/hotbar.js +++ b/src/menus/components/hotbar.js @@ -187,7 +187,11 @@ class Hotbar extends LitElement {

    ${this.activeItemName}

    -
    +
    { + if (!e.target.id.startsWith('hotbar')) return + const slot = +e.target.id.split('-')[1] + this.reloadHotbarSelected(slot) + }}>
    From bd98795059dec8b1005fdcd69bd8f0b46af34071 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 9 Sep 2023 23:58:38 +0300 Subject: [PATCH 204/318] temp fixes for ios scaling & chat issues --- src/controls.ts | 12 ++++++++++++ src/reactUi.jsx | 2 +- src/styles.css | 6 +++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index fe883fbae..7e02b88fe 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -302,3 +302,15 @@ const toggleFly = () => { gameAdditionalState.isFlying = isFlying() } // #endregion +addEventListener('mousedown', (e) => { + // wheel click + // todo support ctrl+wheel (+nbt) + if (e.button === 1) { + const block = bot.blockAtCursor(/* 6 */5) + if (!block) return + const Item = require('prismarine-item')(bot.version); + const item = new Item(block.type, 1, 0); + bot.creative.setInventorySlot(bot.inventory.hotbarStart + bot.quickBarSlot, item) + bot.updateHeldItem() + } +}) diff --git a/src/reactUi.jsx b/src/reactUi.jsx index b2730b031..e479fb092 100644 --- a/src/reactUi.jsx +++ b/src/reactUi.jsx @@ -13,7 +13,7 @@ import { contro } from './controls' useInterfaceState.setState({ isFlying: false, uiCustomization: { - touchButtonSize: isProbablyIphone() ? 55 : 40, + touchButtonSize: isProbablyIphone() ? 30 : 40, }, updateCoord: ([coord, state]) => { const coordToAction = [ diff --git a/src/styles.css b/src/styles.css index b04f4d55b..50070d66f 100644 --- a/src/styles.css +++ b/src/styles.css @@ -118,8 +118,8 @@ canvas { @media only screen and (max-height: 430px) { #ui-root { - transform: scale(1); - width: calc(100% / 1); - height: calc(100% / 1); + transform: scale(1.5); + width: calc(100% / 1.5); + height: calc(100% / 1.5); } } From 0929e600819e2414c451c7d4fac269504528457a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 9 Sep 2023 23:58:48 +0300 Subject: [PATCH 205/318] select block on middle mouse --- src/chat.js | 1 + src/menus/hud.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/chat.js b/src/chat.js index bd36b8374..91b60e993 100644 --- a/src/chat.js +++ b/src/chat.js @@ -63,6 +63,7 @@ class ChatBox extends LitElement { width: var(--chatWidth); transform-origin: bottom left; transform: scale(var(--chatScale)); + pointer-events: none; } .chat-input-wrapper { diff --git a/src/menus/hud.js b/src/menus/hud.js index fa82f86f2..42dbb6f36 100644 --- a/src/menus/hud.js +++ b/src/menus/hud.js @@ -314,6 +314,9 @@ class Hud extends LitElement { render () { return html`
    + From 16c42de1f667c1f03fceb4355f5b5999d4f44863 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 9 Sep 2023 23:59:01 +0300 Subject: [PATCH 206/318] grass issues in inventory --- src/inventory.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/inventory.ts b/src/inventory.ts index 7ec34773c..dc0e7a491 100644 --- a/src/inventory.ts +++ b/src/inventory.ts @@ -45,6 +45,7 @@ const getBlockData = (name) => { const getSpriteBlockSide = (side) => { const d = data[side] + if (!d) return const spriteSide = [d.u * blocksImg.width, d.v * blocksImg.height, d.su * blocksImg.width, d.sv * blocksImg.height] const blockSideData = { slice: spriteSide, @@ -54,9 +55,10 @@ const getBlockData = (name) => { } return { - top: getSpriteBlockSide('up'), - left: getSpriteBlockSide('east'), - right: getSpriteBlockSide('north'), + // todo look at grass bug + top: getSpriteBlockSide('up') || getSpriteBlockSide('top'), + left: getSpriteBlockSide('east') || getSpriteBlockSide('side'), + right: getSpriteBlockSide('north') || getSpriteBlockSide('side'), } } From c3a189d8e1d11ba39d942a633bddae53b2e8a424 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 9 Sep 2023 23:59:15 +0300 Subject: [PATCH 207/318] fix build --- esbuild.mjs | 1 + scripts/esbuildPlugins.mjs | 1 + 2 files changed, 2 insertions(+) diff --git a/esbuild.mjs b/esbuild.mjs index 7eff754a9..2ae544175 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -66,6 +66,7 @@ const ctx = await esbuild.context({ crypto: './src/crypto.js', stream: 'stream-browserify', net: 'net-browserify', + assert: 'assert', dns: './src/dns.js' }, inject: [ diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index cbd0edec3..d12c0a243 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -252,6 +252,7 @@ const plugins = [ buffer: false, perf_hooks: false, net: false, + assert: false, }, }) ] From 659b089d9cc7e4b62fb6e3242f32102c724dfaa4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 10 Sep 2023 17:41:51 +0300 Subject: [PATCH 208/318] more require to import --- src/browserfs.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/browserfs.ts b/src/browserfs.ts index d94cfbb0e..f0cd5fc54 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -5,9 +5,9 @@ import JSZip from 'jszip' import { join } from 'path' import { options } from './optionsStorage' -const { promisify } = require('util') -const browserfs = require('browserfs') -const fs = require('fs') +import { promisify } from 'util' +import browserfs from 'browserfs' +import * as fs from 'fs' browserfs.install(window) // todo migrate to StorageManager API for localsave as localstorage has only 5mb limit, when localstorage is fallback test limit warning on 4mb @@ -181,7 +181,7 @@ export const openWorldZip = async (file: File | ArrayBuffer, name = file['name'] loadSave() } else { const dirs = fs.readdirSync('/world') - let availableWorlds = [] + let availableWorlds: string[] = [] for (const dir of dirs) { if (fs.existsSync(`/world/${dir}/level.dat`)) { availableWorlds.push(dir) From 7bd9a5cfeeaa97221d978f189c37e19dea277506 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 12 Sep 2023 20:10:54 +0300 Subject: [PATCH 209/318] download map improvements --- src/browserfs.ts | 4 ++-- src/downloadAndOpenWorld.ts | 9 +++++---- src/index.ts | 21 +++++++++++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/browserfs.ts b/src/browserfs.ts index f0cd5fc54..4934b51dd 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -6,8 +6,8 @@ import { join } from 'path' import { options } from './optionsStorage' import { promisify } from 'util' -import browserfs from 'browserfs' -import * as fs from 'fs' +import * as browserfs from 'browserfs' +import fs from 'fs' browserfs.install(window) // todo migrate to StorageManager API for localsave as localstorage has only 5mb limit, when localstorage is fallback test limit warning on 4mb diff --git a/src/downloadAndOpenWorld.ts b/src/downloadAndOpenWorld.ts index 61d7fbc7d..dbdaef8cb 100644 --- a/src/downloadAndOpenWorld.ts +++ b/src/downloadAndOpenWorld.ts @@ -2,7 +2,7 @@ import { openWorldZip } from './browserfs' import { setLoadingScreenStatus } from './utils' import { filesize } from 'filesize' -window.addEventListener('load', async (e) => { +export default async () => { const qs = new URLSearchParams(window.location.search) const mapUrl = qs.get('map') if (!mapUrl) return @@ -13,6 +13,10 @@ window.addEventListener('load', async (e) => { setLoadingScreenStatus(`Downloading world ${name}...`) const response = await fetch(mapUrl) + const contentType = response.headers.get('Content-Type') + if (!contentType || !contentType.startsWith('application/zip')) { + alert('Invalid map file') + } const contentLength = +response.headers.get('Content-Length') setLoadingScreenStatus(`Downloading world ${name}: have to download ${filesize(contentLength)}...`) @@ -45,7 +49,4 @@ window.addEventListener('load', async (e) => { }) ).arrayBuffer() await openWorldZip(buffer) -}) - -export default async () => { } diff --git a/src/index.ts b/src/index.ts index 42f09d9df..9380de32a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ import './controls' import './dragndrop' import './browserfs' import './eruda' -import './downloadAndOpenWorld' +import downloadAndOpenWorld from './downloadAndOpenWorld' import net from 'net' import Stats from 'stats.js' @@ -583,10 +583,18 @@ async function connect(connectOptions) { const touchStartBreakingBlockMs = 500 let virtualClickActive = false let virtualClickTimeout + let screenTouches = 0 let capturedPointer: { id; x; y; sourceX; sourceY; activateCameraMove; time; } | null registerListener(document, 'pointerdown', (e) => { const clickedEl = e.composedPath()[0] - if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || capturedPointer || e.pointerId === undefined) { + if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || e.pointerId === undefined) { + return + } + screenTouches++ + if (screenTouches === 3) { + window.dispatchEvent(new MouseEvent('mousedown', { button: 1, })) + } + if (capturedPointer) { return } cameraControlEl.setPointerCapture(e.pointerId) @@ -649,6 +657,14 @@ async function connect(connectOptions) { capturedPointer = undefined }, { passive: false }) + registerListener(document, 'pointerup', (e) => { + const clickedEl = e.composedPath()[0] + if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || e.pointerId === undefined) { + return + } + screenTouches-- + }) + registerListener(document, 'contextmenu', (e) => e.preventDefault(), false) registerListener(document, 'blur', (e) => { @@ -707,3 +723,4 @@ window.addEventListener('keydown', (e) => { addPanoramaCubeMap() showModal(document.getElementById('title-screen')) main() +downloadAndOpenWorld() From f2677eea4e5ab63d4bed9995074811ba0a68248e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 12 Sep 2023 23:39:35 +0300 Subject: [PATCH 210/318] improve downloading progress reporting --- package.json | 2 +- src/downloadAndOpenWorld.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c4ce1af6c..bc8532f1b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lit": "^2.8.0", "minecraft-data": "^3.0.0", "net-browserify": "github:PrismarineJS/net-browserify", + "pretty-bytes": "^6.1.1", "prismarine-world": "^3.6.2", "querystring": "^0.2.1", "react": "^18.2.0", @@ -69,7 +70,6 @@ "cypress": "^9.5.4", "cypress-esbuild-preprocessor": "^1.0.2", "events": "^3.3.0", - "filesize": "^10.0.12", "html-webpack-plugin": "^5.5.3", "http-browserify": "^1.7.0", "http-server": "^14.1.1", diff --git a/src/downloadAndOpenWorld.ts b/src/downloadAndOpenWorld.ts index dbdaef8cb..54c80599e 100644 --- a/src/downloadAndOpenWorld.ts +++ b/src/downloadAndOpenWorld.ts @@ -1,6 +1,10 @@ import { openWorldZip } from './browserfs' import { setLoadingScreenStatus } from './utils' -import { filesize } from 'filesize' +import prettyBytes from 'pretty-bytes' + +const getConstantFilesize = (bytes: number) => { + return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) +} export default async () => { const qs = new URLSearchParams(window.location.search) @@ -18,7 +22,7 @@ export default async () => { alert('Invalid map file') } const contentLength = +response.headers.get('Content-Length') - setLoadingScreenStatus(`Downloading world ${name}: have to download ${filesize(contentLength)}...`) + setLoadingScreenStatus(`Downloading world ${name}: have to download ${getConstantFilesize(contentLength)}...`) let downloadedBytes = 0 const buffer = await new Response( @@ -40,7 +44,9 @@ export default async () => { const progress = (downloadedBytes / contentLength) * 100 // Update your progress bar or display the progress value as needed - setLoadingScreenStatus(`Download Progress: ${Math.floor(progress)}% (${filesize(downloadedBytes, { round: 2, })} / ${filesize(contentLength, { round: 2, })}))`, false, true) + if (contentLength) { + setLoadingScreenStatus(`Download Progress: ${Math.floor(progress)}% (${getConstantFilesize(downloadedBytes)} / ${getConstantFilesize(contentLength)}))`, false, true) + } // Pass the received data to the controller controller.enqueue(value) From e99faef044b8946cf87166e67e577c00b346dc47 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 14 Sep 2023 04:15:15 +0300 Subject: [PATCH 211/318] add blockInteractionShape, make cursor singleton --- package.json | 2 ++ src/cursor.js | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bc8532f1b..3dd8c3f06 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,8 @@ }, "pnpm": { "overrides": { + "prismarine-block": "github:zardoy/prismarine-block#next-era", + "prismarine-world": "github:zardoy/prismarine-world#next-era", "minecraft-data": "latest", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra" diff --git a/src/cursor.js b/src/cursor.js index c3166d34d..25865ae69 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -25,7 +25,11 @@ function getViewDirection (pitch, yaw) { } class Cursor { + static instance = null + constructor (viewer, renderer, /** @type {import('mineflayer').Bot} */bot) { + if (Cursor.instance) return Cursor.instance + // Init state this.buttons = [false, false, false] this.lastButtons = [false, false, false] @@ -151,7 +155,7 @@ class Cursor { if (!cursorBlock) { this.cursorMesh.visible = false } else { - for (const collisionData of cursorBlock.shapes.slice(0, 1) ?? []) { + for (const collisionData of [...cursorBlock.shapes, ...cursorBlock['interactionShapes'] ?? []].slice(0, 1) ?? []) { const width = collisionData[3] - collisionData[0] const height = collisionData[4] - collisionData[1] const depth = collisionData[5] - collisionData[2] From d414996c719b953239922b598d70283b1e77c9dd Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 14 Sep 2023 18:48:48 +0300 Subject: [PATCH 212/318] always display target, meaningful error on proxy unavailable --- src/index.ts | 23 ++++++++++++++++------- src/menus/components/debug_overlay.js | 5 ++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9380de32a..285cde0d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -310,12 +310,6 @@ async function connect(connectOptions) { } console.log(`connecting to ${host} ${port} with ${username}`) - if (proxy) { - console.log(`using proxy ${proxy} ${proxyport}`) - //@ts-ignore - net.setProxy({ hostname: proxy, port: proxyport }) - } - setLoadingScreenStatus('Logging in') let bot: mineflayer.Bot @@ -374,6 +368,21 @@ async function connect(connectOptions) { }, { signal: errorAbortController.signal }) + + if (proxy) { + console.log(`using proxy ${proxy} ${proxyport}`) + + // check proxy server availability for proper error message + try { + await fetch(`http://${proxy}:${proxyport}/api/vm/net`, { method: 'GET' }) + } catch (err) { + console.error(err) + throw new Error(`Proxy server ${proxy}:${proxyport} is not available`) + } + //@ts-ignore + net.setProxy({ hostname: proxy, port: proxyport }) + } + let localServer try { Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) @@ -584,7 +593,7 @@ async function connect(connectOptions) { let virtualClickActive = false let virtualClickTimeout let screenTouches = 0 - let capturedPointer: { id; x; y; sourceX; sourceY; activateCameraMove; time; } | null + let capturedPointer: { id; x; y; sourceX; sourceY; activateCameraMove; time } | null registerListener(document, 'pointerdown', (e) => { const clickedEl = e.composedPath()[0] if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || e.pointerId === undefined) { diff --git a/src/menus/components/debug_overlay.js b/src/menus/components/debug_overlay.js index 7eb3293ec..50b97573e 100644 --- a/src/menus/components/debug_overlay.js +++ b/src/menus/components/debug_overlay.js @@ -95,7 +95,6 @@ class DebugOverlay extends LitElement { } const target = this.cursorBlock - const targetDiggable = (target && this.bot.canDigBlock(target)) const pos = this.bot.entity.position const rot = [this.bot.entity.yaw, this.bot.entity.pitch] @@ -140,8 +139,8 @@ class DebugOverlay extends LitElement {

    Renderer: ${this.rendererDevice} powered by three.js r${global.THREE.REVISION}

    - ${targetDiggable ? html`

    ${target.name}

    ${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}` : ''} - ${targetDiggable ? html`

    Looking at: ${target.position.x} ${target.position.y} ${target.position.z}

    ` : ''} + ${target ? html`

    ${target.name}

    ${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}` : ''} + ${target ? html`

    Looking at: ${target.position.x} ${target.position.y} ${target.position.z}

    ` : ''}
    ` } From 2bceab9384983981a63555a058a41a806c81a5a0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 14 Sep 2023 19:04:56 +0300 Subject: [PATCH 213/318] adjust seo a bit --- index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index bf5dc9c4c..dc72ca576 100644 --- a/index.html +++ b/index.html @@ -25,13 +25,14 @@ - + + + - + - From af288d31431bc0b21d09aa91d80c5d93d029dbac Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 4 Sep 2023 15:36:51 +0300 Subject: [PATCH 214/318] feat: always include source-map on prod --- esbuild.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esbuild.mjs b/esbuild.mjs index 9c55251c2..ad0d3e293 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -46,7 +46,7 @@ const ctx = await esbuild.context({ // logLevel: 'debug', logLevel: 'info', platform: 'browser', - sourcemap: dev, + sourcemap: true, outdir: 'dist', mainFields: [ 'browser', 'module', 'main' From 5e6cd63cc07adff44f9c70e57d7a085cc1810a8a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 14 Sep 2023 19:07:18 +0300 Subject: [PATCH 215/318] restore filesize --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3dd8c3f06..ec039e22d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "esbuild-loader": "^4.0.0", "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", + "flying-squid": "github:zardoy/space-squid#everything", "fs-extra": "^11.1.1", "iconify-icon": "^1.0.8", "jszip": "^3.10.1", @@ -49,7 +50,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-refresh": "^0.14.0", - "flying-squid": "github:zardoy/space-squid#everything", "speed-measure-webpack-plugin": "^1.5.0", "stats.js": "^0.17.0", "url": "^0.11.1", @@ -94,6 +94,7 @@ "three": "0.128.0", "timers-browserify": "^2.0.12", "url-loader": "^4.1.1", + "filesize": "^10.0.12", "use-typed-event-listener": "^4.0.2", "vite": "^4.4.9", "webpack": "^5.88.2", From 8adee6aa6e46c885e92ef37e61003bc57c78f4ab Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 4 Sep 2023 15:56:58 +0300 Subject: [PATCH 216/318] improve eruda check & sw index.html --- index.html | 10 ++++++++-- scripts/build.js | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 4054509a2..bf5dc9c4c 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,19 @@ window.startLoad = Date.now() Prismarine Web Client diff --git a/scripts/build.js b/scripts/build.js index 53d669bc6..15cf4b060 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -46,6 +46,7 @@ exports.getSwAdditionalEntries = () => { // need to be careful with this const singlePlayerVersion = defaultLocalServerOptions.version const filesToCachePatterns = [ + 'index.html', 'index.js', 'index.css', 'favicon.ico', From e6354f681c7d8545e77bfb288915f6165f6ad970 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 4 Sep 2023 13:35:43 +0300 Subject: [PATCH 217/318] [skip ci] upstream changes --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 67ce54540..c6e70a4b1 100644 --- a/src/index.js +++ b/src/index.js @@ -393,9 +393,9 @@ async function connect (connectOptions) { // todo need just to call quit if started // loadingScreen.maybeRecoverable = false // init world, todo: do it for any async plugins - if (!localServer.worldsReady) { + if (!localServer.pluginsReady) { await new Promise(resolve => { - localServer.once('worldsReady', resolve) + localServer.once('pluginsReady', resolve) }) } } From 8a093b21cdab88a0073492fb2cc307a89ac6414f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 03:21:24 +0300 Subject: [PATCH 218/318] initial resource pack support (blocks only)! support custom size --- src/browserfs.ts | 91 ++++++++-- src/globalState.ts | 1 + src/index.ts | 15 +- src/inventory.ts | 19 ++- src/loadSave.ts | 2 + src/menus/loading_or_error_screen.js | 2 +- src/menus/options_screen.js | 30 +++- src/menus/title_screen.js | 25 +-- src/texturePack.ts | 245 +++++++++++++++++++++++++++ src/utils.ts | 40 ++++- 10 files changed, 427 insertions(+), 43 deletions(-) create mode 100644 src/texturePack.ts diff --git a/src/browserfs.ts b/src/browserfs.ts index 4934b51dd..ed2f7eba7 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -8,24 +8,27 @@ import { options } from './optionsStorage' import { promisify } from 'util' import * as browserfs from 'browserfs' import fs from 'fs' +import { installTexturePack, updateTexturePackInstalledState } from './texturePack' browserfs.install(window) // todo migrate to StorageManager API for localsave as localstorage has only 5mb limit, when localstorage is fallback test limit warning on 4mb +const deafultMountablePoints = { + "/world": { fs: "LocalStorage" }, + '/userData': { fs: 'IndexedDB' }, +}; browserfs.configure({ - // todo change to localstorage: mkdir doesnt work for some reason fs: 'MountableFileSystem', - options: { - "/world": { fs: "LocalStorage" } - }, + options: deafultMountablePoints, }, (e) => { if (e) throw e + updateTexturePackInstalledState() }) export const forceCachedDataPaths = {} //@ts-ignore -fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir', 'rename', /* 'copyFile', */'readdir'].map(key => [key, promisify(fs[key])])), { - get (target, p, receiver) { +fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mkdir', 'rmdir', 'unlink', 'rename', /* 'copyFile', */'readdir'].map(key => [key, promisify(fs[key])])), { + get(target, p, receiver) { //@ts-ignore if (!target[p]) throw new Error(`Not implemented fs.promises.${p}`) return (...args) => { @@ -108,6 +111,28 @@ const removeFileRecursiveSync = (path) => { window.removeFileRecursiveSync = removeFileRecursiveSync +// todo it still doesnt clean the storage, need to debug +export async function removeFileRecursiveAsync(path) { + try { + const files = await fs.promises.readdir(path); + for (const file of files) { + const curPath = join(path, file); + const stats = await fs.promises.stat(curPath); + if (stats.isDirectory()) { + // Recurse + await removeFileRecursiveAsync(curPath); + } else { + // Delete file + await fs.promises.unlink(curPath); + } + } + await fs.promises.rmdir(path); + } catch (error) { + throw error; + } +} + + const SUPPORT_WRITE = true export const openWorldDirectory = async (dragndropHandle?: FileSystemDirectoryHandle) => { @@ -153,12 +178,48 @@ export const openWorldDirectory = async (dragndropHandle?: FileSystemDirectoryHa loadSave() } -export const openWorldZip = async (file: File | ArrayBuffer, name = file['name']) => { +const tryToDetectResourcePack = async (file: File | ArrayBuffer) => { + const askInstall = async () => { + return alert('ATM You can install texturepacks only via options menu. WIll be fixed') + if (confirm('Resource pack detected, do you want to install it?')) { + await installTexturePack(file) + } + } + + if (fs.existsSync('/world/pack.mcmeta')) { + askInstall() + return true + } + // const jszip = new JSZip() + // let loaded = await jszip.loadAsync(file) + // if (loaded.file('pack.mcmeta')) { + // loaded = null + // askInstall() + // return true + // } + // loaded = null +} + +export const possiblyCleanHandle = () => { + if (!fsState.saveLoaded) { + // todo clean handle + browserfs.configure({ + fs: 'MountableFileSystem', + options: deafultMountablePoints, + }, (e) => { + if (e) throw e + }) + } +} + +// todo rename method +const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name']) => { await new Promise(async resolve => { browserfs.configure({ // todo fs: 'MountableFileSystem', options: { + ...deafultMountablePoints, "/world": { fs: "ZipFS", options: { @@ -173,12 +234,13 @@ export const openWorldZip = async (file: File | ArrayBuffer, name = file['name'] }) }) + fsState.saveLoaded = false fsState.isReadonly = true fsState.syncFs = true fsState.inMemorySave = false if (fs.existsSync('/world/level.dat')) { - loadSave() + await loadSave() } else { const dirs = fs.readdirSync('/world') let availableWorlds: string[] = [] @@ -189,12 +251,13 @@ export const openWorldZip = async (file: File | ArrayBuffer, name = file['name'] } if (availableWorlds.length === 0) { + if (await tryToDetectResourcePack(file)) return alert('No worlds found in the zip') return } if (availableWorlds.length === 1) { - loadSave(`/world/${availableWorlds[0]}`) + await loadSave(`/world/${availableWorlds[0]}`) return } @@ -204,7 +267,15 @@ export const openWorldZip = async (file: File | ArrayBuffer, name = file['name'] } } -export async function generateZipAndWorld() { +export const openWorldZip = async (...args: Parameters) => { + try { + return await openWorldZipInner(...args) + } finally { + possiblyCleanHandle() + } +} + +export async function generateAndDownloadWorldZip() { const zip = new JSZip() zip.folder('world') diff --git a/src/globalState.ts b/src/globalState.ts index e53700df5..076cddbb2 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -106,6 +106,7 @@ export const miscUiState = proxy({ currentTouch: null as boolean | null, singleplayer: false, gameLoaded: false, + resourcePackInstalled: false, }) export const isGameActive = (foregroundCheck: boolean) => { diff --git a/src/index.ts b/src/index.ts index 285cde0d6..121266044 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,11 +80,12 @@ import { options } from './optionsStorage' import { subscribeKey } from 'valtio/utils' import _ from 'lodash' import { contro } from './controls' +import { genTexturePackTextures, watchTexturepackInViewer } from './texturePack' //@ts-ignore window.THREE = THREE -if ('serviceWorker' in navigator && !isCypress()) { +if ('serviceWorker' in navigator && !isCypress() && process.env.NODE_ENV !== 'development') { window.addEventListener('load', () => { navigator.serviceWorker.register('./service-worker.js').then(registration => { console.log('SW registered: ', registration) @@ -138,6 +139,7 @@ document.body.appendChild(renderer.domElement) // Create viewer const viewer: import('../prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers) initPanoramaOptions(viewer) +watchTexturepackInViewer(viewer) const frameLimit = toNumber(localStorage.frameLimit) let interval = frameLimit && 1000 / frameLimit @@ -388,6 +390,15 @@ async function connect(connectOptions) { Object.assign(serverOptions, _.defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, serverOptions)) const downloadMcData = async (version) => { setLoadingScreenStatus(`Downloading data for ${version}`) + try { + await genTexturePackTextures(version) + } catch (err) { + console.error(err) + const doContinue = prompt('Failed to apply texture pack. See errors in the console. Continue?') + if (!doContinue) { + setLoadingScreenStatus(undefined) + } + } await loadScript(`./mc-data/${toMajorVersion(version)}.js`) } @@ -488,7 +499,6 @@ async function connect(connectOptions) { }) bot.once('spawn', () => { - miscUiState.gameLoaded = true // todo display notification if not critical const mcData = require('minecraft-data')(bot.version) @@ -681,6 +691,7 @@ async function connect(connectOptions) { }, false) setLoadingScreenStatus('Done!') + miscUiState.gameLoaded = true console.log('Done!') hud.init(renderer, bot, host) diff --git a/src/inventory.ts b/src/inventory.ts index dc0e7a491..2efce7fca 100644 --- a/src/inventory.ts +++ b/src/inventory.ts @@ -9,7 +9,22 @@ import invspriteJson from './invsprite.json' import { getVersion } from 'prismarine-viewer/viewer/lib/version' const loadedImages = new Map() -let blockStates: Record }> +export type BlockStates = Record +}> + +let blockStates: BlockStates let lastInventory let mcData let version @@ -74,7 +89,7 @@ const getItemSlice = (name) => { const getImageSrc = (path) => { switch (path) { case 'gui/container/inventory': return InventoryGui - case 'blocks': return `textures/${version}.png` + case 'blocks': return globalThis.texturePackDataUrl || `textures/${version}.png` case 'invsprite': return `invsprite.png` } return Dirt diff --git a/src/loadSave.ts b/src/loadSave.ts index c523500b1..4edcafd44 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -15,6 +15,7 @@ export const fsState = proxy({ isReadonly: false, syncFs: false, inMemorySave: false, + saveLoaded: false }) const PROPOSE_BACKUP = true @@ -120,6 +121,7 @@ export const loadSave = async (root = '/world') => { alert("Note: the world is saved only on /save or disconnect! ENSURE YOU HAVE BACKUP!") } + fsState.saveLoaded = true document.querySelector('#title-screen').dispatchEvent(new CustomEvent('singleplayer', { // todo check gamemode level.dat data etc detail: { diff --git a/src/menus/loading_or_error_screen.js b/src/menus/loading_or_error_screen.js index f04bad38a..45ced09ff 100644 --- a/src/menus/loading_or_error_screen.js +++ b/src/menus/loading_or_error_screen.js @@ -74,7 +74,7 @@ class LoadingErrorScreen extends LitElement { return html`
    -
    ${this.status}${this.hasError && !this.hideDots ? '' : this._loadingDots}

    ${this.hasError ? guessProblem(this.status) : ''}

    +
    ${this.status}${this.hasError || this.hideDots ? '' : this._loadingDots}

    ${this.hasError ? guessProblem(this.status) : ''}

    ${this.hasError ? html`
    { diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index 9ace424b0..81cfd309c 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -2,10 +2,12 @@ const { LitElement, html, css } = require('lit') const { commonCss, isMobile } = require('./components/common') const { showModal, hideCurrentModal, isGameActive, miscUiState } = require('../globalState') const { CommonOptionsScreen } = require('./options_store') -const { toNumber } = require('../utils') +const { toNumber, openFilePicker, setLoadingScreenStatus } = require('../utils') const { options } = require('../optionsStorage') const { subscribe } = require('valtio') const { subscribeKey } = require('valtio/utils') +const { getResourcePackName, uninstallTexturePack } = require('../texturePack') +const { fsState } = require('../loadSave') class OptionsScreen extends CommonOptionsScreen { static get styles () { @@ -76,6 +78,9 @@ class OptionsScreen extends CommonOptionsScreen { subscribeKey(miscUiState, 'singleplayer', () => { this.requestUpdate() }) + subscribeKey(miscUiState, 'resourcePackInstalled', () => { + this.requestUpdate() + }) } render () { @@ -145,12 +150,31 @@ class OptionsScreen extends CommonOptionsScreen { options.autoFullScreen = !options.autoFullScreen } }> - - { + + { options.autoExitFullscreen = !options.autoExitFullscreen } }>
    +
    + { + if (miscUiState.resourcePackInstalled) { + const resourcePackName = await getResourcePackName() + if (confirm(`Uninstall ${resourcePackName} resource pack?`)) { + // todo make hidable + setLoadingScreenStatus('Uninstalling texturepack...') + await uninstallTexturePack() + setLoadingScreenStatus(undefined) + } + } else { + if (!fsState.inMemorySave) { + alert('Unable to install resource pack in loaded save for now') + return + } + openFilePicker('resourcepack') + } + }}> +
    hideCurrentModal()}> diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index b6d83f4c0..482961bb3 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -8,6 +8,7 @@ const fs = require('fs') const mcImage = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/title/minecraft.png') const { options } = require('../optionsStorage') const defaultLocalServerOptions = require('../defaultLocalServerOptions') +const { openFilePicker } = require('../utils') // const SUPPORT_WORLD_LOADING = !!window.showDirectoryPicker const SUPPORT_WORLD_LOADING = true @@ -175,29 +176,7 @@ class TitleScreen extends LitElement { if (!!window.showDirectoryPicker && !e.shiftKey) { openWorldDirectory() } else { - // create and show input picker - /** @type {HTMLInputElement} */ - let picker = document.body.querySelector('input#file-zip-picker') - if (!picker) { - picker = document.createElement('input') - picker.type = 'file' - picker.accept = '.zip' - - picker.addEventListener('change', () => { - const file = picker.files[0] - picker.value = '' - if (!file) return - if (!file.name.endsWith('.zip')) { - const doContinue = confirm(`Are you sure ${file.name.slice(-20)} is .zip file? Only .zip files are supported. Continue?`) - if (!doContinue) return - } - openWorldZip(file) - }) - picker.hidden = true - document.body.appendChild(picker) - } - - picker.click() + openFilePicker() } }}>` : ''}
    diff --git a/src/texturePack.ts b/src/texturePack.ts new file mode 100644 index 000000000..520e1c2fd --- /dev/null +++ b/src/texturePack.ts @@ -0,0 +1,245 @@ +import { setLoadingScreenStatus } from './utils' +import blocksFileNames from '../generated/blocks.json' +import JSZip from 'jszip' +import { join, dirname } from 'path' +import fs from 'fs' +import type { BlockStates } from './inventory' +import type { Viewer } from 'prismarine-viewer/viewer/lib/viewer' +import { removeFileRecursiveAsync } from './browserfs' +import { miscUiState } from './globalState' +import { subscribeKey } from 'valtio/utils' + +function nextPowerOfTwo(n) { + if (n === 0) return 1 + n-- + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + return n + 1 +} + +const mkdirRecursive = async (path) => { + const parts = path.split('/') + let current = '' + for (const part of parts) { + current += part + '/' + try { + await fs.promises.mkdir(current) + } catch (err) { + } + } +} + +const texturePackBasePath = '/userData/resourcePacks/default' +export const uninstallTexturePack = async () => { + await removeFileRecursiveAsync(texturePackBasePath) + setCustomTexturePackData(undefined, undefined) +} + +export const getResourcePackName = async () => { + // temp + try { + return await fs.promises.readFile(join(texturePackBasePath, 'name.txt'), 'utf8') + } catch (err) { + return '???' + } +} + +export const updateTexturePackInstalledState = async () => { + try { + miscUiState.resourcePackInstalled = await existsAsync(texturePackBasePath) + } catch { + } +} + +export const installTexturePack = async (file: File | ArrayBuffer) => { + try { + await uninstallTexturePack() + } catch (err) { + } + const status = 'Installing resource pack: copying all files'; + setLoadingScreenStatus(status) + // extract the zip and write to fs every file in it + const zip = new JSZip() + const zipFile = await zip.loadAsync(file) + if (!zipFile.file('pack.mcmeta')) throw new Error('Not a resource pack: missing pack.mcmeta') + await mkdirRecursive(texturePackBasePath) + + const allFilesArr = Object.entries(zipFile.files); + let i = 0 + for (const [path, file] of allFilesArr) { + const writePath = join(texturePackBasePath, path); + if (path.endsWith('/')) continue + await mkdirRecursive(dirname(writePath)) + await fs.promises.writeFile(writePath, Buffer.from(await file.async('arraybuffer'))) + setLoadingScreenStatus(`${status} ${Math.round(++i / allFilesArr.length * 100)}%`) + } + await fs.promises.writeFile(join(texturePackBasePath, 'name.txt'), file['name'] ?? '??', 'utf8') + + if (viewer?.world.active) { + await genTexturePackTextures(viewer.version) + } + setLoadingScreenStatus(undefined) +} + +const existsAsync = async (path) => { + try { + await fs.promises.stat(path) + return true + } catch (err) { + return false + } +} + +type TextureResolvedData = { + blockSize: number + // itemsUrlContent: string +} + +const arrEqual = (a: any[], b: any[]) => a.length === b.length && a.every((x) => b.includes(x)) + +const applyTexturePackData = async (version: string, { blockSize }: TextureResolvedData, blocksUrlContent: string) => { + const result = await fetch(`blocksStates/${version}.json`) + const blockStates: BlockStates = await result.json() + const factor = blockSize / 16 + + // this will be refactored with prerender refactor + const processObj = (x) => { + if (typeof x !== 'object' || !x) return + if (Array.isArray(x)) { + for (const v of x) { + processObj(v) + } + return + } else { + const actual = Object.keys(x) + const needed = ['u', 'v', 'su', 'sv'] + + if (!arrEqual(actual, needed)) { + for (const v of Object.values(x)) { + processObj(v) + } + return + } + for (const k of needed) { + x[k] *= factor + } + } + } + processObj(blockStates) + setCustomTexturePackData(blocksUrlContent, blockStates) +} + +const setCustomTexturePackData = (blockTextures, blockStates) => { + globalThis.texturePackDataBlockStates = blockStates + globalThis.texturePackDataUrl = blockTextures + miscUiState.resourcePackInstalled = blockTextures !== undefined +} + +const getSizeFromImage = async (filePath: string) => { + const probeImg = new Image() + const file = await fs.promises.readFile(filePath, 'base64'); + probeImg.src = `data:image/png;base64,${file}` + await new Promise((resolve, reject) => { + probeImg.onload = resolve + }) + if (probeImg.width !== probeImg.height) throw new Error(`Probe texture ${filePath} is not square`) + return probeImg.width +} + +export const genTexturePackTextures = async (version: string) => { + setCustomTexturePackData(undefined, undefined) + const blocksBasePath = '/userData/resourcePacks/default/assets/minecraft/textures/blocks'; + const blocksGenereatedPath = `/userData/resourcePacks/default/${version}.png` + const genereatedPathData = `/userData/resourcePacks/default/${version}.json` + if (await existsAsync(blocksBasePath) === false) { + return + } + if (await existsAsync(blocksGenereatedPath) === true) { + applyTexturePackData(version, JSON.parse(await fs.promises.readFile(genereatedPathData, 'utf8')), await fs.promises.readFile(blocksGenereatedPath, 'utf8')) + return + } + + setLoadingScreenStatus('Generating custom textures') + + const textureFiles = blocksFileNames.indexes[version].map(k => blocksFileNames.blockNames[k]) + textureFiles.unshift('missing_texture.png') + + const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length))) + const originalTileSize = 16 + + const firstBlockFile = (await fs.promises.readdir(blocksBasePath)).find(f => f.endsWith('.png')) + if (!firstBlockFile) { + return + } + + // we get the size of image from the first block file, which is not ideal but works in 99% cases + const tileSize = await getSizeFromImage(join(blocksBasePath, firstBlockFile)) + + const imgSize = texSize * tileSize + + const canvas = document.createElement('canvas') + canvas.width = imgSize + canvas.height = imgSize + const src = `textures/${version}.png` + const ctx = canvas.getContext('2d') + ctx.imageSmoothingEnabled = false + const img = new Image() + img.src = src + await new Promise((resolve, reject) => { + img.onerror = reject + img.onload = resolve + }) + for (const [i, fileName] of textureFiles.entries()) { + const x = (i % texSize) * tileSize + const y = Math.floor(i / texSize) * tileSize + const xOrig = (i % texSize) * originalTileSize + const yOrig = Math.floor(i / texSize) * originalTileSize + let imgCustom: HTMLImageElement + try { + const fileBase64 = await fs.promises.readFile(join(blocksBasePath, fileName), 'base64') + const _imgCustom = new Image() + await new Promise(resolve => { + _imgCustom.onload = () => { + imgCustom = _imgCustom + resolve(); + } + _imgCustom.onerror = () => { + console.log('Skipping issued texture', fileName) + resolve() + } + _imgCustom.src = `data:image/png;base64,${fileBase64}` + }) + } catch { + console.log('Skipping not found texture', fileName) + } + + if (imgCustom) { + ctx.drawImage(imgCustom, x, y, tileSize, tileSize) + } else { + ctx.drawImage(img, xOrig, yOrig, originalTileSize, originalTileSize, x, y, tileSize, tileSize) + } + } + const blockDataUrl = canvas.toDataURL('image/png'); + const newData: TextureResolvedData = { + blockSize: tileSize, + }; + await fs.promises.writeFile(genereatedPathData, JSON.stringify(newData), 'utf8') + await fs.promises.writeFile(blocksGenereatedPath, blockDataUrl, 'utf8') + await applyTexturePackData(version, newData, blockDataUrl) + + // const a = document.createElement('a') + // a.href = dataUrl + // a.download = 'pack.png' + // a.click() +} + +export const watchTexturepackInViewer = (viewer: Viewer) => { + subscribeKey(miscUiState, 'resourcePackInstalled', () => { + if (!viewer?.world.active) return + console.log('reloading world data') + viewer.world.updateData() + }) +} diff --git a/src/utils.ts b/src/utils.ts index 6ca9b6b09..d0d093ae6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,11 @@ -import { activeModalStack, miscUiState, showModal } from './globalState' +import { activeModalStack, hideModal, miscUiState, showModal } from './globalState' import { notification } from './menus/notification' import * as crypto from 'crypto' import UUID from 'uuid-1345' import { options } from './optionsStorage' import { saveWorld } from './builtinCommands' +import { openWorldZip } from './browserfs' +import { installTexturePack } from './texturePack' export const goFullscreen = async (doToggle = false) => { if (!document.fullscreenElement) { @@ -132,9 +134,14 @@ export function nameToMcOfflineUUID(name) { return (new UUID(javaUUID('OfflinePlayer:' + name))).toString() } -export const setLoadingScreenStatus = function (status: string, isError = false, hideDots = false) { +export const setLoadingScreenStatus = function (status: string | undefined, isError = false, hideDots = false) { const loadingScreen = document.getElementById('loading-error-screen') + if (status === undefined) { + hideModal({ elem: loadingScreen, }, null, { force: true }) + return + } + // todo update in component instead showModal(loadingScreen) if (loadingScreen.hasError) { @@ -197,3 +204,32 @@ export const reloadChunks = () => { worldView.updatePosition(bot.entity.position, true) prevRenderDistance = options.renderDistance } + +export const openFilePicker = (specificCase?: 'resourcepack') => { + // create and show input picker + let picker: HTMLInputElement = document.body.querySelector('input#file-zip-picker') + if (!picker) { + picker = document.createElement('input') + picker.type = 'file' + picker.accept = '.zip' + + picker.addEventListener('change', () => { + const file = picker.files[0] + picker.value = '' + if (!file) return + if (!file.name.endsWith('.zip')) { + const doContinue = confirm(`Are you sure ${file.name.slice(-20)} is .zip file? Only .zip files are supported. Continue?`) + if (!doContinue) return + } + if (specificCase === 'resourcepack') { + installTexturePack(file) + } else { + openWorldZip(file) + } + }) + picker.hidden = true + document.body.appendChild(picker) + } + + picker.click() +} From 9947c13bd815f74233005023d157c02b935a74a2 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 04:00:02 +0300 Subject: [PATCH 219/318] support panorama from resourcepack --- src/menus/options_screen.js | 2 +- src/panorama.js | 68 +++++++++++++++++++++++++++++++------ src/texturePack.ts | 5 +++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/menus/options_screen.js b/src/menus/options_screen.js index 81cfd309c..333e39b83 100644 --- a/src/menus/options_screen.js +++ b/src/menus/options_screen.js @@ -167,7 +167,7 @@ class OptionsScreen extends CommonOptionsScreen { setLoadingScreenStatus(undefined) } } else { - if (!fsState.inMemorySave) { + if (!fsState.inMemorySave && isGameActive(false)) { alert('Unable to install resource pack in loaded save for now') return } diff --git a/src/panorama.js b/src/panorama.js index f1bf8e191..5bbd1df35 100644 --- a/src/panorama.js +++ b/src/panorama.js @@ -1,14 +1,61 @@ //@ts-check +import { join } from 'path' +import { miscUiState } from './globalState' +import { fromTexturePackPath } from './texturePack' +import fs from 'fs' +import { subscribeKey } from 'valtio/utils' + let panoramaCubeMap +let panoramaUsesResourePack = false let viewer export const initPanoramaOptions = (_viewer) => { viewer = _viewer } +const panoramaFiles = [ + 'panorama_1.png', // WS + 'panorama_3.png', // ES + 'panorama_4.png', // Up + 'panorama_5.png', // Down + 'panorama_0.png', // NS + 'panorama_2.png' // SS +] + +const panoramaResourcePackPath = 'assets/minecraft/textures/gui/title/background' +const possiblyLoadPanoramaFromResourcePack = async (file) => { + let base64Texture + if (panoramaUsesResourePack) { + try { + base64Texture = await fs.promises.readFile(fromTexturePackPath(join(panoramaResourcePackPath, file)), 'base64') + } catch (err) { + panoramaUsesResourePack = false + } + } + if (base64Texture) return `data:image/png;base64,${base64Texture}` + else return join('extra-textures/background', file) +} + +const updateResourecePackSupportPanorama = async () => { + try { + await fs.promises.readFile(fromTexturePackPath(join(panoramaResourcePackPath, panoramaFiles[0])), 'base64') + panoramaUsesResourePack = true + } catch (err) { + panoramaUsesResourePack = false + } +} + +subscribeKey(miscUiState, 'resourcePackInstalled', async () => { + const oldState = panoramaUsesResourePack + const newState = miscUiState.resourcePackInstalled && (await updateResourecePackSupportPanorama(), panoramaUsesResourePack) + if (newState === oldState) return + removePanorama() + addPanoramaCubeMap() +}) + // Menu panorama background -export function addPanoramaCubeMap () { +export async function addPanoramaCubeMap () { // remove all existing object in the viewer.scene // viewer.scene.children = [] @@ -20,14 +67,15 @@ export function addPanoramaCubeMap () { const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) const loader = new THREE.TextureLoader() - const panorMaterials = [ - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_1.png'), transparent: true, side: THREE.DoubleSide }), // WS - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_3.png'), transparent: true, side: THREE.DoubleSide }), // ES - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_4.png'), transparent: true, side: THREE.DoubleSide }), // Up - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_5.png'), transparent: true, side: THREE.DoubleSide }), // Down - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_0.png'), transparent: true, side: THREE.DoubleSide }), // NS - new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_2.png'), transparent: true, side: THREE.DoubleSide }) // SS - ] + let panorMaterials = [] + await updateResourecePackSupportPanorama() + for (const file of panoramaFiles) { + panorMaterials.push(new THREE.MeshBasicMaterial({ + map: loader.load(await possiblyLoadPanoramaFromResourcePack(file)), + transparent: true, + side: THREE.DoubleSide + })) + } const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) @@ -58,7 +106,7 @@ export function addPanoramaCubeMap () { } export function removePanorama () { - if (!panoramaCubeMap) throw new Error('panorama is not there') + if (!panoramaCubeMap) return viewer.camera = new THREE.PerspectiveCamera(document.getElementById('options-screen').fov, window.innerWidth / window.innerHeight, 0.1, 1000) viewer.camera.updateProjectionMatrix() viewer.scene.remove(panoramaCubeMap) diff --git a/src/texturePack.ts b/src/texturePack.ts index 520e1c2fd..15cec53fa 100644 --- a/src/texturePack.ts +++ b/src/texturePack.ts @@ -47,6 +47,10 @@ export const getResourcePackName = async () => { } } +export const fromTexturePackPath = (path) => { + return join(texturePackBasePath, path) +} + export const updateTexturePackInstalledState = async () => { try { miscUiState.resourcePackInstalled = await existsAsync(texturePackBasePath) @@ -219,6 +223,7 @@ export const genTexturePackTextures = async (version: string) => { if (imgCustom) { ctx.drawImage(imgCustom, x, y, tileSize, tileSize) } else { + // todo this involves incorrect mappings for existing textures when the size is different ctx.drawImage(img, xOrig, yOrig, originalTileSize, originalTileSize, x, y, tileSize, tileSize) } } From 1f14154c6340fab3251621fb42041a005a82f1b3 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 04:49:20 +0300 Subject: [PATCH 220/318] add blocks generate script for resourcepack --- .gitignore | 1 + package.json | 3 +- scripts/gen-texturepack-files.mjs | 51 ++++++++++++++++++++++++++++++ scripts/test-texturepack-files.mjs | 16 ++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 scripts/gen-texturepack-files.mjs create mode 100644 scripts/test-texturepack-files.mjs diff --git a/.gitignore b/.gitignore index 950306c9a..131dc688c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ world out *.iml .vercel +generated diff --git a/package.json b/package.json index ec039e22d..b01f6fce9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test:cypress": "cypress run", "test:e2e": "start-test http-get://localhost:8080 test:cypress", "prod-start": "node server.js", + "postinstall": "node scripts/gen-texturepack-files.mjs", "prepublishOnly": "npm run build" }, "keywords": [ @@ -108,7 +109,7 @@ "overrides": { "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "latest", + "minecraft-data": "3.45.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra" } diff --git a/scripts/gen-texturepack-files.mjs b/scripts/gen-texturepack-files.mjs new file mode 100644 index 000000000..ccac9569d --- /dev/null +++ b/scripts/gen-texturepack-files.mjs @@ -0,0 +1,51 @@ +//@ts-check +import fs from 'fs' +import minecraftAssets from 'minecraft-assets' + +// why store another data? +// 1. want to make it compatible (at least for now) +// 2. don't want to read generated blockStates as it might change in future, and the current way was faster to implement + +const blockNames = [] +const indexesPerVersion = {} +/** @type {Map} */ +const allBlocksMap = new Map() +const getBlockIndex = (block) => { + if (allBlocksMap.has(block)) { + return allBlocksMap.get(block) + } + + const index = blockNames.length + allBlocksMap.set(block, index) + blockNames.push(block) + return index +} + +// const blocksFull = [] +// const allBlocks = [] +// // we can even optimize it even futher by doing prev-step resolving +// const blocksDiff = {} + +for (const [i, version] of minecraftAssets.versions.reverse().entries()) { + const assets = minecraftAssets(version) + const blocksDir = assets.directory + '/blocks' + const blocks = fs.readdirSync(blocksDir) + indexesPerVersion[version] = blocks.map(block => { + if (!block.endsWith('.png')) return undefined + return getBlockIndex(block) + }).filter(i => i !== undefined) + + // if (!blocksFull.length) { + // // first iter + // blocksFull.push(...blocks) + // } else { + // const missing = blocksFull.map((b, i) => !blocks.includes(b) ? i : -1).filter(i => i !== -1) + // const added = blocks.filter(b => !blocksFull.includes(b)) + // blocksDiff[version] = { + // missing, + // added + // } + // } +} + +fs.writeFileSync('./generated/blocks.json', JSON.stringify({ blockNames: blockNames, indexes: indexesPerVersion })) diff --git a/scripts/test-texturepack-files.mjs b/scripts/test-texturepack-files.mjs new file mode 100644 index 000000000..0446a2fe8 --- /dev/null +++ b/scripts/test-texturepack-files.mjs @@ -0,0 +1,16 @@ +import fs from 'fs' +import minecraftAssets from 'minecraft-assets' + +const gen = JSON.parse(fs.readFileSync('./blocks.json', 'utf8')) + +const version = '1.8.8' +const { blockNames, indexes } = gen + +const blocksActual = indexes[version].map((i) => blockNames[i]) + +const blocksExpected = fs.readdirSync(minecraftAssets(version).directory + '/blocks') +for (const [i, item] of blocksActual.entries()) { + if (item !== blocksExpected[i]) { + console.log('not equal at', i) + } +} From 6074e187f134947ee49e8dd64d88f2f04b7cf48e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 04:59:40 +0300 Subject: [PATCH 221/318] specify default interaction box --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 121266044..344c218ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,6 +84,7 @@ import { genTexturePackTextures, watchTexturepackInViewer } from './texturePack' //@ts-ignore window.THREE = THREE +globalThis.emptyShapeReplacer = [[0.0, 0.0, 0.0, 1.0, 1.0, 1.0]] if ('serviceWorker' in navigator && !isCypress() && process.env.NODE_ENV !== 'development') { window.addEventListener('load', () => { From f475364c18033552d5f3f9cc61319d17d3743f89 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 07:06:26 +0300 Subject: [PATCH 222/318] should fix build --- scripts/gen-texturepack-files.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gen-texturepack-files.mjs b/scripts/gen-texturepack-files.mjs index ccac9569d..d996236e3 100644 --- a/scripts/gen-texturepack-files.mjs +++ b/scripts/gen-texturepack-files.mjs @@ -48,4 +48,5 @@ for (const [i, version] of minecraftAssets.versions.reverse().entries()) { // } } +fs.mkdirSync('./generated', { recursive: true, }) fs.writeFileSync('./generated/blocks.json', JSON.stringify({ blockNames: blockNames, indexes: indexesPerVersion })) From 7edddf8a9de4009fc7d3a83aed8906d1b6f61189 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 07:14:54 +0300 Subject: [PATCH 223/318] add experimental release posting --- .github/workflows/publish.yml | 2 ++ .gitpod | 2 -- package.json | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 .gitpod diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e316c3319..42da85208 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,6 +22,8 @@ jobs: # with: # name: cypress-images # path: cypress/integration/__image_snapshots__/ + # todo link vercel deployment + - run: pnpx zardoy-release node - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitpod b/.gitpod deleted file mode 100644 index 38fc373b5..000000000 --- a/.gitpod +++ /dev/null @@ -1,2 +0,0 @@ -tasks: -- command: npm install diff --git a/package.json b/package.json index b01f6fce9..ad2c79806 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,13 @@ ], "author": "PrismarineJS", "license": "MIT", + "release": { + "initialVersion": { + "version": "0.1.0", + "releaseNotes": "First GH release", + "releaseNotesWithExisting": "The start of something new..." + } + }, "dependencies": { "@dimaka/interface": "0.0.1", "@emotion/css": "^11.11.2", From 38e0c0832cf0beaa3e6d831b77130b30ed1b2252 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 15 Sep 2023 07:16:40 +0300 Subject: [PATCH 224/318] use 1.14.4 (post-flatenning) as default for stable state rendering --- src/defaultLocalServerOptions.js | 4 ++-- src/menus/title_screen.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/defaultLocalServerOptions.js b/src/defaultLocalServerOptions.js index 3ad6cebba..fbd8bf22c 100644 --- a/src/defaultLocalServerOptions.js +++ b/src/defaultLocalServerOptions.js @@ -31,6 +31,6 @@ module.exports = { }, 'everybody-op': true, 'max-entities': 100, - 'version': '1.8.8', - versionMajor: '1.8' + 'version': '1.14.4', + versionMajor: '1.14' } diff --git a/src/menus/title_screen.js b/src/menus/title_screen.js index 482961bb3..0bcb71e7d 100644 --- a/src/menus/title_screen.js +++ b/src/menus/title_screen.js @@ -166,7 +166,7 @@ class TitleScreen extends LitElement { fsState.inMemorySave = true const notFirstTime = fs.existsSync('./world/level.dat') if (notFirstTime && !options.localServerOptions.version) { - options.localServerOptions.version = '1.16.1' // legacy version, now we use 1.8.8 + options.localServerOptions.version = '1.16.1' // legacy version } else { options.localServerOptions.version ??= defaultLocalServerOptions.version } From 111c97c1d32d1437c93c4404c5d393bacee616d5 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 15 Sep 2023 07:23:34 +0300 Subject: [PATCH 225/318] ci: pass GITHUB_TOKEN --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 42da85208..9c9418bbe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,6 +24,8 @@ jobs: # path: cypress/integration/__image_snapshots__/ # todo link vercel deployment - run: pnpx zardoy-release node + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 06ea7ddc6da3974b58e1374ab9e74ce36703f47c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 15 Sep 2023 07:23:47 +0300 Subject: [PATCH 226/318] ci: pass default GITHUB_TOKEN --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9c9418bbe..0a31d6e7a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: # todo link vercel deployment - run: pnpx zardoy-release node env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From c6b78c3663460943df0f7c13cb66d881521c758d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 17 Sep 2023 22:03:26 +0300 Subject: [PATCH 227/318] add contributing, speed start, cleanup esbuild and don't use minify in dev as it breaks breakpoints since minify inlines code --- CONTRIBUTING.md | 14 ++++++++++++++ esbuild.mjs | 23 ++++------------------- package.json | 2 +- scripts/prepareData.mjs | 6 ++++++ 4 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..407fd03fe --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Contributing Guide + +After forking the repository, run the following commands to get started: + +0. Ensure you have [Node.js](https://nodejs.org) and `pnpm` installed. To install pnpm run `npm i -g pnpm`. +1. Install dependencies: `pnpm i` +2. Start the project in development mode: `pnpm start` + +A few notes: + +- It's recommended to use debugger for debugging. VSCode has a great debugger built-in. If debugger is slow, you can use `--no-sources` flag that would allow browser to speedup .map file parsing. +- Some data are cached between restarts. If you see something doesn't work after upgrading dependencies, try to clear the by simply removing the `dist` folder. +- The same folder `dist` is used for both development and production builds, so be careful when deploying the project. +- Use `start-prod` script to start the project in production mode after running `build` script to build the project. diff --git a/esbuild.mjs b/esbuild.mjs index 2ae544175..9d9be44ca 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -7,20 +7,8 @@ import { clients, plugins } from './scripts/esbuildPlugins.mjs' import { generateSW } from 'workbox-build' import { getSwAdditionalEntries } from './scripts/build.js' -/** @type {import('esbuild').BuildOptions} */ -let baseConfig = {} - -// // testing config -// baseConfig = { -// entryPoints: ['files/index.js'], -// outfile: 'out.js', -// outdir: undefined, -// } - -try { - //@ts-ignore - await import('./localSettings.mjs') -} catch { } +//@ts-ignore +try { await import('./localSettings.mjs') } catch { } fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replace('', ''), 'utf8') @@ -53,7 +41,6 @@ const ctx = await esbuild.context({ 'browser', 'module', 'main' ], keepNames: true, - ...baseConfig, banner: { js: banner.join('\n'), }, @@ -73,10 +60,8 @@ const ctx = await esbuild.context({ './src/shims.js' ], metafile: true, - plugins: [ - ...plugins, - ...baseConfig.plugins ?? [], - ], + plugins, + sourcesContent: process.argv.includes('--no-sources'), minify: process.argv.includes('--minify'), define: { 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), diff --git a/package.json b/package.json index b05c52d11..5aff7d1cb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0-dev", "description": "A minecraft client running in a browser", "scripts": { - "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --minify --watch", + "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch", "start-watch-script": "nodemon -w esbuild.mjs esbuild.mjs", "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs && node esbuild.mjs --minify --prod", "watch": "node scripts/build.js copyFilesDev && webpack serve --config webpack.dev.js --progress", diff --git a/scripts/prepareData.mjs b/scripts/prepareData.mjs index f128f3110..6d13156cf 100644 --- a/scripts/prepareData.mjs +++ b/scripts/prepareData.mjs @@ -1,8 +1,14 @@ //@ts-check import { build } from 'esbuild' +import { existsSync } from 'node:fs' import Module from "node:module" import { dirname } from 'node:path' +if (existsSync('dist/mc-data')) { + console.log('using cached prepared data') + process.exit(0) +} + const require = Module.createRequire(import.meta.url) const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json') From 214ec566290c1b8b2a87fda8a797b5acef937525 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 17 Sep 2023 22:03:43 +0300 Subject: [PATCH 228/318] fix cypress schema --- cypress.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress.json b/cypress.json index bcc9d5837..63bfb3510 100644 --- a/cypress.json +++ b/cypress.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/cypress-io/cypress/develop/cli/schema/cypress.schema.json", + "$schema": "https://raw.githubusercontent.com/cypress-io/cypress/188b9a742ee2ef51102167bfd84b3696a3f72a26/cli/schema/cypress.schema.json", "baseUrl": "http://localhost:8080", "testFiles": "**/*.spec.ts", "video": false, From b7c6a8f3eece6c646dc0d675f65879947aa19ddb Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 17 Sep 2023 23:28:06 +0300 Subject: [PATCH 229/318] all most missing integration cypress tests --- cypress/integration/index.spec.ts | 57 +++++++++++++++++++++++++------ minecraft-server.mjs | 19 +++-------- src/menus/components/button.js | 6 ++++ src/menus/play_screen.js | 2 +- src/menus/title_screen.js | 6 ++-- 5 files changed, 61 insertions(+), 29 deletions(-) diff --git a/cypress/integration/index.spec.ts b/cypress/integration/index.spec.ts index b215bc285..f2aa90c00 100644 --- a/cypress/integration/index.spec.ts +++ b/cypress/integration/index.spec.ts @@ -1,13 +1,16 @@ /// +const setLocalStorageSettings = () => { + window.localStorage.cypress = 'true' + window.localStorage.server = 'localhost' +} + +// todo use ssl + it('Loads & renders singleplayer', () => { // todo use