From 2a64d874219ac5ea759ed31be90e9f97a012ae2f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Jul 2024 03:39:35 +0300 Subject: [PATCH 001/909] feat: improve scaling in portrait mobile. Add close icon for mobile chat --- .storybook/preview.tsx | 3 ++- src/microsoftAuthflow.ts | 11 +++++++--- src/react/AppStatus.tsx | 1 + src/react/Chat.css | 9 +++++++- src/react/Chat.tsx | 5 +++++ src/react/EnterFullscreenButton.tsx | 18 +++++++++++++--- src/react/Screen.tsx | 5 +++-- src/react/mainMenu.module.css | 2 +- src/react/utilsApp.ts | 5 +++++ src/scaleInterface.ts | 20 +++++++++++------- src/screens.css | 12 +++++++++++ src/styles.css | 32 ----------------------------- 12 files changed, 73 insertions(+), 50 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 74b0f05c..05c36eba 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,8 +2,9 @@ import React from 'react' import type { Preview } from "@storybook/react" -import '../src/styles.css' import './storybook.css' +import '../src/styles.css' +import '../src/scaleInterface' const preview: Preview = { decorators: [ diff --git a/src/microsoftAuthflow.ts b/src/microsoftAuthflow.ts index 9bb352ed..36f4a121 100644 --- a/src/microsoftAuthflow.ts +++ b/src/microsoftAuthflow.ts @@ -4,10 +4,15 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { // const sessionEndpoint = 'http://localhost:3000/session' let authEndpoint = '' let sessionEndpoint = '' + if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}` + const url = proxyBaseUrl + '/api/vm/net/connect' + let result: Response + try { + result = await fetch(url) + } catch (err) { + throw new Error(`Selected proxy server ${proxyBaseUrl} most likely is down`) + } try { - if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}` - const url = proxyBaseUrl + '/api/vm/net/connect' - const result = await fetch(url) const json = await result.json() authEndpoint = urlWithBase(json.capabilities.authEndpoint, proxyBaseUrl) sessionEndpoint = urlWithBase(json.capabilities.sessionEndpoint, proxyBaseUrl) diff --git a/src/react/AppStatus.tsx b/src/react/AppStatus.tsx index 10cc5793..9ebecf93 100644 --- a/src/react/AppStatus.tsx +++ b/src/react/AppStatus.tsx @@ -28,6 +28,7 @@ export default ({ status, isError, hideDots = false, lastStatus = '', backAction return ( diff --git a/src/react/Chat.css b/src/react/Chat.css index 41917783..138267ac 100644 --- a/src/react/Chat.css +++ b/src/react/Chat.css @@ -24,6 +24,13 @@ div.chat-wrapper { left: 1px; box-sizing: border-box; background-color: rgba(0, 0, 0, 0); + display: flex; + align-items: center; + gap: 1px; +} + +.chat-input-wrapper form { + display: flex; } .chat-input { @@ -103,7 +110,7 @@ div.chat-wrapper { } .input-mobile #chatinput { - height: 20px; + height: 25px; } .display-mobile { diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx index 89b2aa8b..60467171 100644 --- a/src/react/Chat.tsx +++ b/src/react/Chat.tsx @@ -4,6 +4,8 @@ import { MessageFormatPart } from '../botUtils' import { MessagePart } from './MessageFormatted' import './Chat.css' import { isIos, reactKeyForMessage } from './utils' +import Button from './Button' +import { pixelartIcons } from './PixelartIcon' export type Message = { parts: MessageFormatPart[], @@ -221,6 +223,8 @@ export default ({ diff --git a/src/react/EnterFullscreenButton.tsx b/src/react/EnterFullscreenButton.tsx index ad78ddad..3901b9ae 100644 --- a/src/react/EnterFullscreenButton.tsx +++ b/src/react/EnterFullscreenButton.tsx @@ -1,6 +1,11 @@ import { useEffect, useState } from 'react' +import { useSnapshot } from 'valtio' +import { activeModalStack, miscUiState } from '../globalState' import Button from './Button' import { useUsingTouch } from './utilsApp' +import { pixelartIcons } from './PixelartIcon' + +const hideOnModals = new Set(['chat']) export default () => { const [fullScreen, setFullScreen] = useState(false) @@ -9,16 +14,23 @@ export default () => { setFullScreen(!!document.fullscreenElement) }) }, []) + const { gameLoaded } = useSnapshot(miscUiState) + + const activeStack = useSnapshot(activeModalStack) + + const inMainMenu = activeStack.length === 0 && !gameLoaded const usingTouch = useUsingTouch() - if (!usingTouch || !document.documentElement.requestFullscreen || fullScreen) return null + const hideButton = activeStack.some(x => hideOnModals.has(x.reactType)) + + if (hideButton || !usingTouch || !document.documentElement.requestFullscreen || fullScreen) return null return + {!lockConnect && <> + + } } From d02008c2ef4bfff93fad0edbeaffca3d60399bcc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Jul 2024 18:33:16 +0300 Subject: [PATCH 011/909] fix(regression): signs lighting was compltely broken --- prismarine-viewer/viewer/lib/mesher/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index ab77d516..c978c240 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -624,7 +624,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delete attr.t_uvs attr.positions = new Float32Array(attr.positions) as any - attr.normals = new Int8Array(attr.normals) as any + attr.normals = new Float32Array(attr.normals) as any attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any From d0205a970b823ed49e4a01dea237e175a096d777 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Jul 2024 20:08:10 +0300 Subject: [PATCH 012/909] up mineflayer for better vehicle move --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ebdda4..9d370f17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,7 +314,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848(encoding@0.1.13) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6086,8 +6086,8 @@ packages: resolution: {integrity: sha512-QMMNPx4IyZE7ydAzjvGLQLCnQNUOfkk1qVZKxTTS9q3qPTAewz4GhsVUBtbQ8LSbHthe5RcQ1Sgxs4wlIma/Qw==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848} version: 4.20.1 engines: {node: '>=18'} @@ -15825,7 +15825,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848(encoding@0.1.13): dependencies: minecraft-data: 3.65.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) From 07002a743748899f58767011c3bf99da2658c63b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 13 Jul 2024 05:49:38 +0300 Subject: [PATCH 013/909] fix: remove entities from the scene on login packet (usually on the world switch) --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d370f17..9deb9a7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,7 +314,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/7f65e46a048f1bc2b57775d84b32400dce707321(encoding@0.1.13) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6086,8 +6086,8 @@ packages: resolution: {integrity: sha512-QMMNPx4IyZE7ydAzjvGLQLCnQNUOfkk1qVZKxTTS9q3qPTAewz4GhsVUBtbQ8LSbHthe5RcQ1Sgxs4wlIma/Qw==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/7f65e46a048f1bc2b57775d84b32400dce707321: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/7f65e46a048f1bc2b57775d84b32400dce707321} version: 4.20.1 engines: {node: '>=18'} @@ -15825,7 +15825,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c03c76ef2acfc747bfe8cd1e290106c81f399848(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/7f65e46a048f1bc2b57775d84b32400dce707321(encoding@0.1.13): dependencies: minecraft-data: 3.65.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) From 04b5d1ac3f03e5053df6a6142ede7d0c4539d480 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 13 Jul 2024 06:17:47 +0300 Subject: [PATCH 014/909] add handled packets stats TODO make auto-updated --- docs-assets/handled-packets.md | 169 +++++++++++++++++++++++++++++++ scripts/updateHandledPackets.mjs | 60 +++++++++++ 2 files changed, 229 insertions(+) create mode 100644 docs-assets/handled-packets.md create mode 100644 scripts/updateHandledPackets.mjs diff --git a/docs-assets/handled-packets.md b/docs-assets/handled-packets.md new file mode 100644 index 00000000..0671987c --- /dev/null +++ b/docs-assets/handled-packets.md @@ -0,0 +1,169 @@ +# Handled Packets + +## Server -> Client + +❌ statistics +❌ advancements +❌ face_player +❌ nbt_query_response +❌ chat_suggestions +❌ trade_list +❌ vehicle_move +❌ open_book +❌ craft_recipe_response +❌ end_combat_event +❌ enter_combat_event +❌ unlock_recipes +❌ camera +❌ update_view_position +❌ update_view_distance +❌ entity_sound_effect +❌ stop_sound +❌ feature_flags +❌ select_advancement_tab +❌ declare_recipes +❌ tags +❌ acknowledge_player_digging +❌ initialize_world_border +❌ world_border_center +❌ world_border_lerp_size +❌ world_border_size +❌ world_border_warning_delay +❌ world_border_warning_reach +❌ simulation_distance +❌ chunk_biomes +❌ damage_event +❌ hurt_animation +✅ spawn_entity +✅ spawn_entity_experience_orb +✅ named_entity_spawn +✅ animation +✅ block_break_animation +✅ tile_entity_data +✅ block_action +✅ block_change +✅ boss_bar +✅ difficulty +✅ tab_complete +✅ declare_commands +✅ multi_block_change +✅ close_window +✅ open_window +✅ window_items +✅ craft_progress_bar +✅ set_slot +✅ set_cooldown +✅ custom_payload +✅ hide_message +✅ kick_disconnect +✅ profileless_chat +✅ entity_status +✅ explosion +✅ unload_chunk +✅ game_state_change +✅ open_horse_window +✅ keep_alive +✅ map_chunk +✅ world_event +✅ world_particles +✅ update_light +✅ login +✅ map +✅ rel_entity_move +✅ entity_move_look +✅ entity_look +✅ open_sign_entity +✅ abilities +✅ player_chat +✅ death_combat_event +✅ player_remove +✅ player_info +✅ position +✅ entity_destroy +✅ remove_entity_effect +✅ resource_pack_send +✅ respawn +✅ entity_head_rotation +✅ held_item_slot +✅ scoreboard_display_objective +✅ entity_metadata +✅ attach_entity +✅ entity_velocity +✅ entity_equipment +✅ experience +✅ update_health +✅ scoreboard_objective +✅ set_passengers +✅ teams +✅ scoreboard_score +✅ spawn_position +✅ update_time +✅ sound_effect +✅ system_chat +✅ playerlist_header +✅ collect +✅ entity_teleport +✅ entity_update_attributes +✅ entity_effect +✅ server_data +✅ clear_titles +✅ action_bar +✅ ping +✅ set_title_subtitle +✅ set_title_text +✅ set_title_time +✅ packet + +## Client -> Server + +❌ query_block_nbt +❌ set_difficulty +❌ query_entity_nbt +❌ pick_item +❌ set_beacon_effect +❌ update_command_block_minecart +❌ update_structure_block +❌ generate_structure +❌ lock_difficulty +❌ craft_recipe_request +❌ displayed_recipe +❌ recipe_book +❌ update_jigsaw_block +❌ spectate +❌ advancement_tab +✅ teleport_confirm +✅ chat_command +✅ chat_message +✅ message_acknowledgement +✅ edit_book +✅ name_item +✅ select_trade +✅ update_command_block +✅ tab_complete +✅ client_command +✅ settings +✅ enchant_item +✅ window_click +✅ close_window +✅ custom_payload +✅ use_entity +✅ keep_alive +✅ position +✅ position_look +✅ look +✅ flying +✅ vehicle_move +✅ steer_boat +✅ abilities +✅ block_dig +✅ entity_action +✅ steer_vehicle +✅ resource_pack_receive +✅ held_item_slot +✅ set_creative_slot +✅ update_sign +✅ arm_animation +✅ block_place +✅ use_item +✅ pong +✅ chat_session_update diff --git a/scripts/updateHandledPackets.mjs b/scripts/updateHandledPackets.mjs new file mode 100644 index 00000000..080eaf44 --- /dev/null +++ b/scripts/updateHandledPackets.mjs @@ -0,0 +1,60 @@ +import fs from 'fs' +import path from 'path' +import minecraftData from 'minecraft-data' + +const lastVersion = minecraftData.versions.pc[0] +// console.log('last proto ver', lastVersion.minecraftVersion) +const allPackets = minecraftData(lastVersion.minecraftVersion).protocol +const getPackets = ({ types }) => { + return Object.keys(types).map(type => type.replace('packet_', '')) +} +// todo test against all versions +const allFromServerPackets = getPackets(allPackets.play.toClient) +const allToServerPackets = getPackets(allPackets.play.toServer).filter(x => !['packet'].includes(x)) + +const buildFile = './dist/index.js' + +const file = fs.readFileSync(buildFile, 'utf8') + +const packetsReceiveRegex = /client\.on\("(\w+)"/g +const packetsReceiveSend = /client\.write\("(\w+)"/g + +let allSupportedReceive = [...new Set([...file.matchAll(packetsReceiveRegex)].map(x => x[1]))] +let allSupportedSend = [...new Set([...file.matchAll(packetsReceiveSend)].map(x => x[1]))] + +let md = '# Handled Packets\n' + +md += '\n## Server -> Client\n\n' +let notSupportedRows = [] +let supportedRows = [] +for (const packet of allFromServerPackets) { + const includes = allSupportedReceive.includes(packet); + (includes ? supportedRows : notSupportedRows).push(packet) +} + +for (const row of notSupportedRows) { + md += `❌ ${row}\n` +} +for (const row of supportedRows) { + md += `✅ ${row}\n` +} + +md += '\n' + +notSupportedRows = [] +supportedRows = [] + +md += '## Client -> Server\n\n' +for (const packet of allToServerPackets) { + const includes = allSupportedSend.includes(packet); + (includes ? supportedRows : notSupportedRows).push(packet) +} + +for (const row of notSupportedRows) { + md += `❌ ${row}\n` +} +for (const row of supportedRows) { + md += `✅ ${row}\n` +} + +fs.writeFileSync('./docs-assets/handled-packets.md', md) From 6c5f72e1f286f2b73f8b940d117b9cec4b197ae1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 13 Jul 2024 20:31:27 +0300 Subject: [PATCH 015/909] feat: implement pitch which was required for note blocks to work properly --- prismarine-viewer/viewer/lib/viewer.ts | 5 +++-- src/soundSystem.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index c95127f2..504c4637 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -32,7 +32,7 @@ export class Viewer { this.world.camera = camera } - constructor(public renderer: THREE.WebGLRenderer, worldConfig = defaultWorldRendererConfig) { + constructor (public renderer: THREE.WebGLRenderer, worldConfig = defaultWorldRendererConfig) { // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 THREE.ColorManagement.enabled = false renderer.outputColorSpace = THREE.LinearSRGBColorSpace @@ -118,7 +118,7 @@ export class Viewer { this.world.updateCamera(pos?.offset(0, yOffset, 0) ?? null, yaw, pitch) } - playSound (position: Vec3, path: string, volume = 1) { + playSound (position: Vec3, path: string, volume = 1, pitch = 1) { if (!this.audioListener) { this.audioListener = new THREE.AudioListener() this.camera.add(this.audioListener) @@ -134,6 +134,7 @@ export class Viewer { sound.setBuffer(buffer) sound.setRefDistance(20) sound.setVolume(volume) + sound.setPlaybackRate(pitch) // set the pitch this.scene.add(sound) // set sound position sound.position.set(position.x, position.y, position.z) diff --git a/src/soundSystem.ts b/src/soundSystem.ts index e7fdaee7..d49b6a8c 100644 --- a/src/soundSystem.ts +++ b/src/soundSystem.ts @@ -50,7 +50,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { const isMuted = options.mutedSounds.includes(soundKey) || options.volume === 0 if (position) { if (!isMuted) { - viewer.playSound(position, url, soundVolume * Math.max(Math.min(volume, 1), 0) * (options.volume / 100)) + viewer.playSound(position, url, soundVolume * Math.max(Math.min(volume, 1), 0) * (options.volume / 100), Math.max(Math.min(pitch ?? 1, 2), 0.5)) } if (getDistance(bot.entity.position, position) < 4 * 16) { lastPlayedSounds.lastServerPlayed[soundKey] ??= { count: 0, last: 0 } From b13ef443bb84d4f2f84a4d11bc0f25ffc8d4840c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 13 Jul 2024 21:30:44 +0300 Subject: [PATCH 016/909] feat: rework sound system. Now sounds are 100% implemented for all current & future supported versions --- prismarine-viewer/viewer/prepare/utils.ts | 14 ++++++ src/globals.d.ts | 2 +- src/soundSystem.ts | 53 ++++++++++++----------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/prismarine-viewer/viewer/prepare/utils.ts b/prismarine-viewer/viewer/prepare/utils.ts index a33909a9..958273f3 100644 --- a/prismarine-viewer/viewer/prepare/utils.ts +++ b/prismarine-viewer/viewer/prepare/utils.ts @@ -2,3 +2,17 @@ export const versionToNumber = (ver: string) => { const [x, y = '0', z = '0'] = ver.split('.') return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}` } + +export const versionToMajor = (version: string) => { + const [x, y = '0'] = version.split('.') + return `${x.padStart(2, '0')}.${y.padStart(2, '0')}` +} + +export const versionsMapToMajor = (versionsMap: Record) => { + const majorVersions = {} as Record + for (const [ver, data] of Object.entries(versionsMap)) { + const major = versionToMajor(ver) + majorVersions[major] = data + } + return majorVersions +} diff --git a/src/globals.d.ts b/src/globals.d.ts index 05b29a14..f8699f21 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -17,7 +17,7 @@ declare const worldView: import('prismarine-viewer/viewer/lib/worldDataEmitter') declare const localServer: import('flying-squid/dist/index').FullServer & { options } | undefined /** all currently loaded mc data */ declare const mcData: Record -declare const loadedData: import('minecraft-data').IndexedData +declare const loadedData: import('minecraft-data').IndexedData & { sounds: Record } declare const customEvents: import('typed-emitter').default<{ /** Singleplayer load requested */ singleplayer (): void diff --git a/src/soundSystem.ts b/src/soundSystem.ts index d49b6a8c..0187a9b0 100644 --- a/src/soundSystem.ts +++ b/src/soundSystem.ts @@ -1,6 +1,6 @@ import { subscribeKey } from 'valtio/utils' import { Vec3 } from 'vec3' -import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' +import { versionToMajor, versionToNumber, versionsMapToMajor } from 'prismarine-viewer/viewer/prepare/utils' import { loadScript } from 'prismarine-viewer/viewer/lib/utils' import type { Block } from 'prismarine-block' import { miscUiState } from './globalState' @@ -22,32 +22,26 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { return } - // todo also use major versioned hardcoded sounds - const soundsMap = allSoundsMap[bot.version] + const allSoundsMajor = versionsMapToMajor(allSoundsMap) + const soundsMap = allSoundsMajor[versionToMajor(bot.version)] ?? Object.values(allSoundsMajor)[0] - if (!soundsMap || !miscUiState.gameLoaded || !soundsMap) { + if (!soundsMap || !miscUiState.gameLoaded || !loadedData.sounds) { return } - const soundsPerId = Object.fromEntries(Object.entries(soundsMap).map(([id, sound]) => [+id.split(';')[0], sound])) + // const soundsPerId = Object.fromEntries(Object.entries(soundsMap).map(([id, sound]) => [+id.split(';')[0], sound])) const soundsPerName = Object.fromEntries(Object.entries(soundsMap).map(([id, sound]) => [id.split(';')[1], sound])) - const soundIdToName = Object.fromEntries(Object.entries(soundsMap).map(([id, sound]) => [+id.split(';')[0], id.split(';')[1]])) - const playGeneralSound = async (soundId: string, soundString: string | undefined, position?: Vec3, volume = 1, pitch?: number) => { - if (!soundString) { - console.warn('Unknown sound received from server to play', soundId) - return - } + const playGeneralSound = async (soundKey: string, position?: Vec3, volume = 1, pitch?: number) => { if (!options.volume) return - const parts = soundString.split(';') - const soundVolume = +parts[0]! - const soundName = parts[1]! - // console.log('pitch', pitch) - const versionedSound = getVersionedSound(bot.version, soundName, Object.entries(soundsLegacyMap)) + const soundStaticData = soundsPerName[soundKey]?.split(';') + if (!soundStaticData) return + const soundVolume = +soundStaticData[0]! + const soundPath = soundStaticData[1]! + const versionedSound = getVersionedSound(bot.version, soundPath, Object.entries(soundsLegacyMap)) // todo test versionedSound - const url = allSoundsMeta.baseUrl.replace(/\/$/, '') + (versionedSound ? `/${versionedSound}` : '') + '/minecraft/sounds/' + soundName + '.' + allSoundsMeta.format - const soundKey = soundIdToName[+soundId] ?? soundId - const isMuted = options.mutedSounds.includes(soundKey) || options.volume === 0 + const url = allSoundsMeta.baseUrl.replace(/\/$/, '') + (versionedSound ? `/${versionedSound}` : '') + '/minecraft/sounds/' + soundPath + '.' + allSoundsMeta.format + const isMuted = options.mutedSounds.includes(soundKey) || options.mutedSounds.includes(soundPath) || options.volume === 0 if (position) { if (!isMuted) { viewer.playSound(position, url, soundVolume * Math.max(Math.min(volume, 1), 0) * (options.volume / 100), Math.max(Math.min(pitch ?? 1, 2), 0.5)) @@ -67,17 +61,24 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } } } - const playHardcodedSound = async (soundId: string, position?: Vec3, volume = 1, pitch?: number) => { - const sound = soundsPerName[soundId] - await playGeneralSound(soundId, sound, position, volume, pitch) + const playHardcodedSound = async (soundKey: string, position?: Vec3, volume = 1, pitch?: number) => { + await playGeneralSound(soundKey, position, volume, pitch) } bot.on('soundEffectHeard', async (soundId, position, volume, pitch) => { - console.debug('soundEffectHeard', soundId, volume) await playHardcodedSound(soundId, position, volume, pitch) }) - bot.on('hardcodedSoundEffectHeard', async (soundId, soundCategory, position, volume, pitch) => { - const sound = soundsPerId[soundId] - await playGeneralSound(soundId.toString(), sound, position, volume, pitch) + bot.on('hardcodedSoundEffectHeard', async (soundIdNum, soundCategory, position, volume, pitch) => { + const fixOffset = versionToNumber('1.20.4') === versionToNumber(bot.version) ? -1 : 0 + const soundKey = loadedData.sounds[soundIdNum + fixOffset]?.name + if (soundKey === undefined) return + await playGeneralSound(soundKey, position, volume, pitch) + }) + // workaround as mineflayer doesn't support soundEvent + bot._client.on('sound_effect', async (packet) => { + const soundResource = packet['soundEvent']?.resource as string | undefined + if (packet.soundId !== 0 || !soundResource) return + const pos = new Vec3(packet.x / 8, packet.y / 8, packet.z / 8) + await playHardcodedSound(soundResource.replace('minecraft:', ''), pos, packet.volume, packet.pitch) }) bot.on('entityHurt', async (entity) => { if (entity.id === bot.entity.id) { From 698779f6b5302165dd3dff731de0b223ac837513 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 13 Jul 2024 21:31:29 +0300 Subject: [PATCH 017/909] fix: toggle entity visiiblity even after it's creation --- prismarine-viewer/viewer/lib/entities.js | 10 +++++----- prismarine-viewer/viewer/lib/worldDataEmitter.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 94f06e0c..d36b6936 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -405,14 +405,14 @@ export class Entities extends EventEmitter { } //@ts-ignore + // set visibility const isInvisible = entity.metadata?.[0] & 0x20 - if (isInvisible) { - for (const child of this.entities[entity.id].children.find(c => c.name === 'mesh').children) { - if (child.name !== 'nametag') { - child.visible = false - } + for (const child of this.entities[entity.id].children.find(c => c.name === 'mesh').children) { + if (child.name !== 'nametag') { + child.visible = !isInvisible } } + // --- // not player const displayText = entity.metadata?.[3] && this.parseEntityLabel(entity.metadata[2]) if (entity.name !== 'player' && displayText) { diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index 5df5cc73..761509c2 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -44,7 +44,14 @@ export class WorldDataEmitter extends EventEmitter { listenToBot (bot: typeof __type_bot) { const emitEntity = (e) => { if (!e || e === bot.entity) return - this.emitter.emit('entity', { ...e, pos: e.position, username: e.username }) + this.emitter.emit('entity', { + ...e, + pos: e.position, + username: e.username, + // set debugTree (obj) { + // e.debugTree = obj + // } + }) } this.eventListeners[bot.username] = { From 512bc09477aa96352f4d19a165a7e110d5c76f81 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 14 Jul 2024 01:55:41 +0300 Subject: [PATCH 018/909] make main docker build 5x smaller, add only proxy dockerfile --- Dockerfile | 32 +++++++++++++++++++++----------- Dockerfile.proxy | 11 +++++++++++ README.MD | 15 +++++++++++---- 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 Dockerfile.proxy diff --git a/Dockerfile b/Dockerfile index 086240b5..0ae1c6e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,32 @@ -FROM node:18-alpine +# ---- Build Stage ---- +FROM node:18-alpine AS build # Without git installing the npm packages fails -RUN apk add git -RUN mkdir /app +RUN apk add --no-cache git python3 make g++ cairo-dev pango-dev jpeg-dev giflib-dev librsvg-dev WORKDIR /app COPY . /app -# install python and other dependencies -RUN apk add python3 make g++ cairo-dev pango-dev jpeg-dev giflib-dev librsvg-dev # install pnpm RUN npm i -g pnpm@9.0.4 RUN pnpm install -# only for prod -RUN pnpm run build -# --- -EXPOSE 8080 -# uncomment for development + +# TODO for development # EXPOSE 9090 # VOLUME /app/src # VOLUME /app/prismarine-viewer # ENTRYPOINT ["pnpm", "run", "run-all"] + # only for prod -ENTRYPOINT ["npm", "run", "prod-start"] +RUN pnpm run build + +# ---- Run Stage ---- +FROM node:18-alpine +RUN apk add git +WORKDIR /app +# Copy build artifacts from the build stage +COPY --from=build /app/dist /app/dist +COPY server.js /app/server.js +# Install express +RUN npm i -g pnpm@9.0.4 +RUN npm init -yp +RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors +EXPOSE 8080 +ENTRYPOINT ["node", "server.js"] diff --git a/Dockerfile.proxy b/Dockerfile.proxy new file mode 100644 index 00000000..746eef72 --- /dev/null +++ b/Dockerfile.proxy @@ -0,0 +1,11 @@ +# ---- Run Stage ---- +FROM node:18-alpine +RUN apk add git +WORKDIR /app +COPY server.js /app/server.js +# Install server dependencies +RUN npm i -g pnpm@9.0.4 +RUN npm init -yp +RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors +EXPOSE 8080 +ENTRYPOINT ["node", "server.js"] diff --git a/README.MD b/README.MD index 9a4ba24a..7576f6ba 100644 --- a/README.MD +++ b/README.MD @@ -44,6 +44,13 @@ See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the li There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. MS account authentication will be supported soon. +### Docker Files + +- [Main Dockerfile](./Dockerfile) - for production build & offline/private usage. Includes full web-app + proxy server for connecting to Minecraft servers. +- [Proxy Dockerfile](./Dockerfile.proxy) - for proxy server only - that one you would be able to specify in the proxy field on the client (`docker build . -f Dockerfile.proxy -t minecraft-web-proxy`) + +In case of using docker, you don't have to follow preparation steps from CONTRIBUTING.MD, like installing Node.js, pnpm, etc. + ### Rendering #### Three.js Renderer @@ -55,10 +62,6 @@ MS account authentication will be supported soon. -### Things that are not planned yet - -- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines - ### Advanced Settings There are many many settings, that are not exposed in the UI yet. You can find or change them by opening the browser console and typing `options`. You can also change them by typing `options. = `. @@ -131,6 +134,10 @@ Press `Y` to set query parameters to url of your current game state. - [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan) - [Three.js](https://threejs.org/) - Helping in 3D rendering +### Things that are not planned yet + +- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines + ### Alternatives - [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true) From b49e9880906f269b1be6456abcdb24f78e45f21e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 16 Jul 2024 10:35:01 +0300 Subject: [PATCH 019/909] fix: inventory didn't update after dropping an item --- src/controls.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/controls.ts b/src/controls.ts index a3a22029..98cc6afb 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -353,7 +353,7 @@ contro.on('trigger', ({ command }) => { document.exitPointerLock?.() openPlayerInventory() break - case 'general.drop': + case 'general.drop': { // if (bot.heldItem/* && ctrl */) bot.tossStack(bot.heldItem) bot._client.write('block_dig', { 'status': 4, @@ -365,7 +365,14 @@ contro.on('trigger', ({ command }) => { 'face': 0, sequence: 0 }) + const slot = bot.inventory.hotbarStart + bot.quickBarSlot + const item = bot.inventory.slots[slot] + if (item) { + item.count-- + bot.inventory.updateSlot(slot, item.count > 0 ? item : null!) + } break + } case 'general.chat': showModal({ reactType: 'chat' }) break From 9ee67cc8270e79e5b2d9025a30148bb083e8c36d Mon Sep 17 00:00:00 2001 From: Wolf2323 Date: Tue, 16 Jul 2024 10:37:01 +0200 Subject: [PATCH 020/909] fix: ignoring exception ResizeObserver loop exceptions (#161) --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index e34e81ab..2a190785 100644 --- a/src/index.ts +++ b/src/index.ts @@ -330,6 +330,9 @@ async function connect (connectOptions: ConnectOptions) { } const handleError = (err) => { console.error(err) + if (err === 'ResizeObserver loop completed with undelivered notifications.') { + return + } errorAbortController.abort() if (isCypress()) throw err miscUiState.hasErrors = true From cda1d59d3bd59580d91f4107a8b4a8bfae57c976 Mon Sep 17 00:00:00 2001 From: Wolf2323 Date: Tue, 16 Jul 2024 11:05:57 +0200 Subject: [PATCH 021/909] improved query parameters handling (#162) Co-authored-by: Vitaly --- README.MD | 25 ++++++++++------- src/react/AddServerOrConnect.tsx | 46 +++++++++++++++++++------------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/README.MD b/README.MD index 7576f6ba..cafa3ac9 100644 --- a/README.MD +++ b/README.MD @@ -110,19 +110,26 @@ world chunks have a *yellow* border, hostile mobs have a *red* outline, passive Press `Y` to set query parameters to url of your current game state. -- `?ip=` - Display connect screen to the server on load -- `?username=` - Set the username for server -- `?proxy=` - Set the proxy server address to use for server -- `?version=` - Set the version for server -- `?lockConnect=true` - Disable cancel / save buttons, useful for integrates iframes +There are some parameters you can set in the url to archive some specific behaviors: + +Server specific: +- `?ip=` - Display connect screen to the server on load with predefined server ip. `:` is optional and can be added to the ip. +- `?name=` - Set the server name for saving to the server list +- `?version=` - Set the version for the server +- `?proxy=` - Set the proxy server address to use for the server +- `?username=` - Set the username for the server +- `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes. - `?reconnect=true` - Reconnect to the server on page reloads. Available in **dev mode only** and very useful on server testing. + +Single player specific: - `?loadSave=` - Load the save on load with the specified folder name (not title) - `?singleplayer=1` - Create empty world on load. Nothing will be saved -- `?noSave=true` - Disable auto save on unload / disconnect / export. Only manual save with `/save` command will work - - +- `?version=` - Set the version for the singleplayer world (when used with `?singleplayer=1`) +- `?noSave=true` - Disable auto save on unload / disconnect / export whenever a world is loaded. Only manual save with `/save` command will work. - `?map=` - Load the map from ZIP. You can use any url, but it must be CORS enabled. -- `?setting=:` - Set the and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4` + +General: +- `?setting=:` - Set and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4` ### Notable Things that Power this Project diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 729f1ec2..535e3ee9 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -30,19 +30,25 @@ const ELEMENTS_WIDTH = 190 export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, defaults, accounts, authenticatedAccounts }: Props) => { const qsParams = parseQs ? new URLSearchParams(window.location.search) : undefined + const qsParamName = qsParams?.get('name') + const qsParamIp = qsParams?.get('ip') + const qsParamVersion = qsParams?.get('version') + const qsParamProxy = qsParams?.get('proxy') + const qsParamUsername = qsParams?.get('username') + const qsParamLockConnect = qsParams?.get('lockConnect') - const [serverName, setServerName] = React.useState(initialData?.name ?? qsParams?.get('name') ?? '') + const qsIpParts = qsParamIp?.split(':') + const ipParts = initialData?.ip.split(':') - const ipWithoutPort = initialData?.ip.split(':')[0] - const port = initialData?.ip.split(':')[1] + const [serverName, setServerName] = React.useState(initialData?.name ?? qsParamName ?? '') + const [serverIp, setServerIp] = React.useState(ipParts?.[0] ?? qsIpParts?.[0] ?? '') + const [serverPort, setServerPort] = React.useState(ipParts?.[1] ?? qsIpParts?.[1] ?? '') + const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParamVersion ?? '') + const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParamProxy ?? '') + const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParamUsername ?? '') + const lockConnect = qsParamLockConnect === 'true' - const [serverIp, setServerIp] = React.useState(ipWithoutPort ?? qsParams?.get('ip') ?? '') - const [serverPort, setServerPort] = React.useState(port ?? '') - const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParams?.get('version') ?? '') - const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParams?.get('proxy') ?? '') - const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParams?.get('username') ?? '') const smallWidth = useIsSmallWidth() - const lockConnect = qsParams?.get('lockConnect') === 'true' const initialAccount = initialData?.authenticatedAccountOverride const [accountIndex, setAccountIndex] = React.useState(initialAccount === true ? -2 : initialAccount ? (accounts?.includes(initialAccount) ? accounts.indexOf(initialAccount) : -2) : -1) @@ -61,7 +67,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ authenticatedAccountOverride, } - return + return
-
- setServerName(value)} placeholder='Defaults to IP' /> -
- setServerIp(value)} /> - setServerPort(value)} placeholder='25565' /> + {!lockConnect && <> +
+ setServerName(value)} placeholder='Defaults to IP' /> +
+ } + setServerIp(value)} /> + setServerPort(value)} placeholder='25565' />
Overrides:
- setVersionOverride(value)} placeholder='Optional, but recommended to specify' /> - setProxyOverride(value)} placeholder={defaults?.proxyOverride} /> - setUsernameOverride(value)} placeholder={defaults?.usernameOverride} disabled={!noAccountSelected} /> + setVersionOverride(value)} placeholder='Optional, but recommended to specify' /> + setProxyOverride(value)} placeholder={defaults?.proxyOverride} /> + setUsernameOverride(value)} placeholder={defaults?.usernameOverride} />