From 2277020de7cdc59b31c77f404721481a4b1dd2c0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 18 Mar 2025 21:46:44 +0300 Subject: [PATCH] fix swing animations and improve replay server functions --- package.json | 4 +- pnpm-lock.yaml | 57 ++++++++++++++++++++++------- src/controls.ts | 19 +++++++--- src/downloadAndOpenFile.ts | 34 ++++++++++++++++- src/index.ts | 8 ++-- src/packetsReplay/replayPackets.ts | 20 ++++++++++ src/react/PacketsReplayProvider.tsx | 2 +- 7 files changed, 118 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 9dd43eaf..cd8c25ff 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", - "mcraft-fun-mineflayer": "^0.1.12", + "mcraft-fun-mineflayer": "^0.1.14", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", @@ -151,7 +151,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.1.1", + "mineflayer-mouse": "^0.1.2", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8d28545..9093dfc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 mcraft-fun-mineflayer: - specifier: ^0.1.12 - version: 0.1.12(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) + specifier: ^0.1.14 + version: 0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -362,8 +362,8 @@ importers: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.1 - version: 0.1.1(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.1.2 + version: 0.1.2(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6694,9 +6694,9 @@ packages: resolution: {integrity: sha512-j2D1RNYtB5Z9gFu9MVjyDBbiALI0mWZ3xW/A3PPefVAHm3HJ2T1vH+1XBov1spBGPl7u+Zo7mRXza3X0egbeOg==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.12: - resolution: {integrity: sha512-BhfkagVJX+QmD/dt3qNQS5f7g3/7NI//OfSW4VnRolCnZtrLU8ekr59bLRcNmUWsvtTjkg+wbMeXwclHshSWOA==} - version: 0.1.12 + mcraft-fun-mineflayer@0.1.14: + resolution: {integrity: sha512-q/qXQaNbkGJIvXjRvudUT7/k0EsJgphFcvYjrSRWYyGDJeb61MKRVqq1hhMjqx7UK7FMfBKvjfPSxq/QlAP7WQ==} + version: 0.1.14 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -6904,6 +6904,11 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8} version: 1.0.1 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1} + version: 1.54.0 + engines: {node: '>=22'} + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d} version: 1.54.0 @@ -6920,8 +6925,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.1: - resolution: {integrity: sha512-7jKN+6pIGtQVfYxEIm4tA9CYwTS8Mgn/qJ2wyhrAoIEW8smCHUu0kj5Sdo0TwTCdlOQClKt8aEBZ13E7MGqOhg==} + mineflayer-mouse@0.1.2: + resolution: {integrity: sha512-QPGEXkF9PurZEpRq0xakKE8SV6sMY/6kCM9cdMeFbtq95IpYeh8ZJdD/twX2A3g3s8MooxlGovfxbpeHdWcOEQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -13607,7 +13612,7 @@ snapshots: flatmap: 0.0.3 long: 5.2.3 minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -17588,11 +17593,11 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.12(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.0 @@ -17901,6 +17906,32 @@ snapshots: - '@types/react' - react + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13): + dependencies: + '@types/node-rsa': 1.1.4 + '@types/readable-stream': 4.0.12 + aes-js: 3.1.2 + buffer-equal: 1.0.1 + debug: 4.4.0(supports-color@8.1.1) + endian-toggle: 0.0.0 + lodash.get: 4.4.2 + lodash.merge: 4.6.2 + minecraft-data: 3.83.1 + minecraft-folder-path: 1.2.0 + node-fetch: 2.7.0(encoding@0.1.13) + node-rsa: 0.4.2 + prismarine-auth: 2.4.2(encoding@0.1.13) + prismarine-chat: 1.10.1 + prismarine-nbt: 2.5.0 + prismarine-realms: 1.3.2(encoding@0.1.13) + protodef: 1.18.0 + readable-stream: 4.5.2 + uuid-1345: 1.0.2 + yggdrasil: 1.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - supports-color + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 @@ -17958,7 +17989,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.1(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + mineflayer-mouse@0.1.2(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): dependencies: change-case: 5.4.4 debug: 4.4.0(supports-color@8.1.1) diff --git a/src/controls.ts b/src/controls.ts index 69b94636..98d32062 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -8,6 +8,7 @@ import { ControMax } from 'contro-max/build/controMax' import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types' import { stringStartsWith } from 'contro-max/build/stringUtils' import { UserOverrideCommand, UserOverridesConfig } from 'contro-max/build/types/store' +import { GameMode } from 'mineflayer' import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal, miscUiState, hideModal, hideAllModals } from './globalState' import { goFullscreen, isInRealGameSession, pointerLock, reloadChunks } from './utils' import { options } from './optionsStorage' @@ -26,6 +27,7 @@ import { lastConnectOptions } from './react/AppStatusProvider' import { onCameraMove, onControInit } from './cameraRotationControls' import { createNotificationProgressReporter } from './core/progressReporter' import { appStorage } from './react/appStorageProvider' +import { switchGameMode } from './packetsReplay/replayPackets' export const customKeymaps = proxy(appStorage.keybindings) @@ -637,29 +639,35 @@ export const f3Keybinds: Array<{ { key: 'F4', async action () { + let nextGameMode: GameMode switch (bot.game.gameMode) { case 'creative': { - bot.chat('/gamemode survival') + nextGameMode = 'survival' break } case 'survival': { - bot.chat('/gamemode adventure') + nextGameMode = 'adventure' break } case 'adventure': { - bot.chat('/gamemode spectator') + nextGameMode = 'spectator' break } case 'spectator': { - bot.chat('/gamemode creative') + nextGameMode = 'creative' break } // No default } + if (lastConnectOptions.value?.worldStateFileContents) { + switchGameMode(nextGameMode) + } else { + bot.chat(`/gamemode ${nextGameMode}`) + } }, mobileTitle: 'Cycle Game Mode' }, @@ -810,15 +818,16 @@ let allowFlying = false export const onBotCreate = () => { let wasSpectatorFlying = false bot._client.on('abilities', ({ flags }) => { + allowFlying = !!(flags & 4) if (flags & 2) { // flying toggleFly(true, false) } else { toggleFly(false, false) } - allowFlying = !!(flags & 4) }) const gamemodeCheck = () => { if (bot.game.gameMode === 'spectator') { + allowFlying = true toggleFly(true, false) wasSpectatorFlying = true } else if (wasSpectatorFlying) { diff --git a/src/downloadAndOpenFile.ts b/src/downloadAndOpenFile.ts index 870a70b1..78cd6984 100644 --- a/src/downloadAndOpenFile.ts +++ b/src/downloadAndOpenFile.ts @@ -13,13 +13,43 @@ export const getFixedFilesize = (bytes: number) => { const inner = async () => { const { replayFileUrl } = appQueryParams if (replayFileUrl) { - setLoadingScreenStatus('Downloading replay file...') + setLoadingScreenStatus('Downloading replay file') const response = await fetch(replayFileUrl) const contentLength = response.headers?.get('Content-Length') const size = contentLength ? +contentLength : undefined const filename = replayFileUrl.split('/').pop() - const contents = await response.text() + let downloadedBytes = 0 + const buffer = await new Response(new ReadableStream({ + async start (controller) { + if (!response.body) throw new Error('Server returned no response!') + const reader = response.body.getReader() + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read() + + if (done) { + controller.close() + break + } + + downloadedBytes += value.byteLength + + // Calculate download progress as a percentage + const progress = size ? (downloadedBytes / size) * 100 : undefined + setLoadingScreenStatus(`Download replay file progress: ${progress === undefined ? '?' : Math.floor(progress)}% (${getFixedFilesize(downloadedBytes)} / ${size && getFixedFilesize(size)})`, false, true) + + // Pass the received data to the controller + controller.enqueue(value) + } + }, + })).arrayBuffer() + + // Convert buffer to text, handling any compression automatically + const decoder = new TextDecoder() + const contents = decoder.decode(buffer) + openFile({ contents, filename, diff --git a/src/index.ts b/src/index.ts index 83f9f289..45593447 100644 --- a/src/index.ts +++ b/src/index.ts @@ -781,11 +781,13 @@ export async function connect (connectOptions: ConnectOptions) { setLoadingScreenStatus('Placing blocks (starting viewer)') if (!connectOptions.worldStateFileContents || connectOptions.worldStateFileContents.length < 3 * 1024 * 1024) { localStorage.lastConnectOptions = JSON.stringify(connectOptions) + if (process.env.NODE_ENV === 'development' && !localStorage.lockUrl && !Object.keys(window.debugQueryParams).length) { + lockUrl() + } + } else { + localStorage.removeItem('lastConnectOptions') } connectOptions.onSuccessfulPlay?.() - if (process.env.NODE_ENV === 'development' && !localStorage.lockUrl && !Object.keys(window.debugQueryParams).length) { - lockUrl() - } updateDataAfterJoin() if (connectOptions.autoLoginPassword) { bot.chat(`/login ${connectOptions.autoLoginPassword}`) diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index 13891899..f1c85a94 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -3,6 +3,7 @@ import { createServer, ServerClient } from 'minecraft-protocol' import { ParsedReplayPacket, parseReplayContents } from 'mcraft-fun-mineflayer/build/packetsLogger' import { PACKETS_REPLAY_FILE_EXTENSION, WORLD_STATE_FILE_EXTENSION } from 'mcraft-fun-mineflayer/build/worldState' import MinecraftData from 'minecraft-data' +import { GameMode } from 'mineflayer' import { LocalServer } from '../customServer' import { UserError } from '../mineflayer/userError' import { packetsReplayState } from '../react/state/packetsReplayState' @@ -231,6 +232,25 @@ const mainPacketsReplayer = async (client: ServerClient, packets: ParsedReplayPa } } +export const switchGameMode = (gameMode: GameMode) => { + const gamemodes = { + survival: 0, + creative: 1, + adventure: 2, + spectator: 3 + } + if (gameMode === 'spectator') { + bot._client.emit('abilities', { + // can fly + is flying + flags: 6 + }) + } + bot._client.emit('game_state_change', { + reason: 3, + gameMode: gamemodes[gameMode] + }) +} + interface PacketsWaiterOptions { unexpectedPacketReceived?: (name: string, params: any) => void expectedPacketReceived?: (name: string, params: any) => void diff --git a/src/react/PacketsReplayProvider.tsx b/src/react/PacketsReplayProvider.tsx index 782134c2..8ffd1f6b 100644 --- a/src/react/PacketsReplayProvider.tsx +++ b/src/react/PacketsReplayProvider.tsx @@ -22,7 +22,7 @@ export default function PacketsReplayProvider () { return (