From ad13ab83f2457f689f92cc260bf5aba60613089b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 12 Mar 2025 19:17:02 +0300 Subject: [PATCH 01/31] load panorama earlier --- src/panorama.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/panorama.ts b/src/panorama.ts index 65678a6a..a0d30fb4 100644 --- a/src/panorama.ts +++ b/src/panorama.ts @@ -102,12 +102,16 @@ export async function addPanoramaCubeMap () { panoramaCubeMap = group } -subscribeKey(miscUiState, 'fsReady', () => { - if (miscUiState.fsReady) { - // don't do it earlier to load fs and display menu faster - void addPanoramaCubeMap() - } -}) +if (process.env.SINGLE_FILE_BUILD_MODE) { + subscribeKey(miscUiState, 'fsReady', () => { + if (miscUiState.fsReady) { + // don't do it earlier to load fs and display menu faster + void addPanoramaCubeMap() + } + }) +} else { + void addPanoramaCubeMap() +} export function removePanorama () { for (const unloadPanoramaCallback of unloadPanoramaCallbacks) { @@ -124,6 +128,7 @@ export function removePanorama () { } const initDemoWorld = async () => { + if (!shouldDisplayPanorama) return const abortController = new AbortController() unloadPanoramaCallbacks.push(() => { abortController.abort() From 214828df0c0df7a6782b66feb023e2cc7aa3633e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 20:46:48 +0300 Subject: [PATCH 02/31] add option to enable/disable jei even depending on gamemode --- src/inventoryWindows.ts | 29 +++++++++++++++++++---------- src/optionsStorage.ts | 1 + 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 6a5ab0d6..f6737a12 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -458,16 +458,25 @@ const openWindow = (type: string | undefined) => { } } - // if (bot.game.gameMode !== 'spectator') { - lastWindow.pwindow.win.jeiSlotsPage = 0 - // todo workaround so inventory opens immediately (though it still lags) - setTimeout(() => { - upJei('') - }) - miscUiState.displaySearchInput = true - // } else { - // lastWindow.pwindow.win.jeiSlots = [] - // } + const isJeiEnabled = () => { + if (typeof options.jeiEnabled === 'boolean') return options.jeiEnabled + if (Array.isArray(options.jeiEnabled)) { + return options.jeiEnabled.includes(bot.game?.gameMode as any) + } + return false + } + + if (isJeiEnabled()) { + lastWindow.pwindow.win.jeiSlotsPage = 0 + // todo workaround so inventory opens immediately (though it still lags) + setTimeout(() => { + upJei('') + }) + miscUiState.displaySearchInput = true + } else { + lastWindow.pwindow.win.jeiSlots = [] + miscUiState.displaySearchInput = false + } if (type === undefined) { // player inventory diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 1c5c999e..ef397238 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -63,6 +63,7 @@ const defaultOptions = { preciseMouseInput: false, // todo ui setting, maybe enable by default? waitForChunksRender: 'sp-only' as 'sp-only' | boolean, + jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, // antiAliasing: false, From e1831eea38278244556f5c47c935e56adb93c535 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 20:59:07 +0300 Subject: [PATCH 03/31] fix placing panorama after login (again) --- src/panorama.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/panorama.ts b/src/panorama.ts index a0d30fb4..7fdd72b7 100644 --- a/src/panorama.ts +++ b/src/panorama.ts @@ -13,7 +13,7 @@ import { miscUiState } from './globalState' import { loadMinecraftData } from './connect' let panoramaCubeMap -let shouldDisplayPanorama = false +let shouldDisplayPanorama = true const panoramaFiles = [ 'panorama_3.png', // right (+x) @@ -34,13 +34,12 @@ export async function addPanoramaCubeMap () { setTimeout(resolve, 0) // wait for viewer to be initialized }) viewer.camera.fov = 85 + if (!shouldDisplayPanorama) return if (process.env.SINGLE_FILE_BUILD_MODE) { void initDemoWorld() return } - shouldDisplayPanorama = true - let time = 0 viewer.camera.near = 0.05 viewer.camera.updateProjectionMatrix() @@ -128,7 +127,6 @@ export function removePanorama () { } const initDemoWorld = async () => { - if (!shouldDisplayPanorama) return const abortController = new AbortController() unloadPanoramaCallbacks.push(() => { abortController.abort() From 09e61c9aa010a548b38c696eb3ff4380e24fb713 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 21:57:50 +0300 Subject: [PATCH 04/31] fix: cancel block placements in adventure and when interaction is expected eg crafting table! --- package.json | 2 +- pnpm-lock.yaml | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 36fe2218..a1fcff61 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.0.8", + "mineflayer-mouse": "^0.0.9", "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 c5a4459e..02a0a671 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -362,8 +362,8 @@ importers: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.0.8 - version: 0.0.8(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.0.9 + version: 0.0.9(@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 @@ -6916,8 +6916,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.0.8: - resolution: {integrity: sha512-Y6TfclMjx7ndV+ISznJsQ4SMwzjhYvwvkj8kKBsQXx9/bG6fDtkgfnAroO/f2ppV52WNrtERQlVXYs35gHtIFg==} + mineflayer-mouse@0.0.9: + resolution: {integrity: sha512-oViJrou2tziPuox/ZFJWZJMCnaF5+KPEsrbBgKmXVr3eK35iPohdhYwoKgqgBY8uXS/bNaFnkCR0K7ZDqyBF8g==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -17949,10 +17949,11 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.0.8(@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.0.9(@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) + prismarine-item: 1.16.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) transitivePeerDependencies: From c947b285ea01890ef228e67928f33e7c9b890600 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 23:06:41 +0300 Subject: [PATCH 05/31] fix: fix action bar text was not visible on ios (when landscape) --- src/react/HudBarsProvider.tsx | 2 +- src/react/Title.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/react/HudBarsProvider.tsx b/src/react/HudBarsProvider.tsx index ea78ae08..db061500 100644 --- a/src/react/HudBarsProvider.tsx +++ b/src/react/HudBarsProvider.tsx @@ -90,7 +90,7 @@ export default () => { upArmour() }, []) - return
+ return
Date: Thu, 13 Mar 2025 23:11:12 +0300 Subject: [PATCH 06/31] fix: reconnect button sometimes was not displayed --- src/builtinCommands.ts | 9 +++++---- src/index.ts | 6 ++++-- src/inventoryWindows.ts | 12 ++++++++++-- src/react/ChatProvider.tsx | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index 4bc21a73..278ff57f 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -79,8 +79,9 @@ const writeText = (text) => { displayClientChat(text) } -const commands: Array<{ +export const commands: Array<{ command: string[], + alwaysAvailable?: boolean, invoke (args: string[]): Promise | void //@ts-format-ignore-region }> = [ @@ -111,6 +112,7 @@ const commands: Array<{ }, { command: ['/pos'], + alwaysAvailable: true, async invoke ([type]) { let pos: { x: number, y: number, z: number } | undefined if (type === 'block') { @@ -131,13 +133,12 @@ const commands: Array<{ ] //@ts-format-ignore-endregion -export const getBuiltinCommandsList = () => commands.flatMap(command => command.command) +export const getBuiltinCommandsList = () => commands.filter(command => command.alwaysAvailable || localServer).flatMap(command => command.command) export const tryHandleBuiltinCommand = (message: string) => { - if (!localServer) return const [userCommand, ...args] = message.split(' ') - for (const command of commands) { + for (const command of commands.filter(command => command.alwaysAvailable || localServer)) { if (command.command.includes(userCommand)) { void command.invoke(args) // ignoring for now return true diff --git a/src/index.ts b/src/index.ts index 812b8c51..ab055ce6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -362,6 +362,7 @@ export async function connect (connectOptions: ConnectOptions) { miscUiState.hasErrors = true if (miscUiState.gameLoaded) return + appStatusState.showReconnect = true setLoadingScreenStatus(`Error encountered. ${err}`, true) onPossibleErrorDisconnect() destroyAll() @@ -711,6 +712,7 @@ export async function connect (connectOptions: ConnectOptions) { bot.on('kicked', (kickReason) => { console.log('You were kicked!', kickReason) const { formatted: kickReasonFormatted, plain: kickReasonString } = parseFormattedMessagePacket(kickReason) + appStatusState.showReconnect = true setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted) destroyAll() }) @@ -894,8 +896,8 @@ export async function connect (connectOptions: ConnectOptions) { const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined listenGlobalEvents() -const unsubscribe = watchValue(miscUiState, async s => { - if (s.fsReady && s.appConfig) { +const unsubscribe = subscribe(miscUiState, async () => { + if (miscUiState.fsReady && miscUiState.appConfig) { unsubscribe() if (reconnectOptions) { sessionStorage.removeItem('reconnectOptions') diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index f6737a12..6fd35611 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -37,6 +37,10 @@ export const allImagesLoadedState = proxy({ value: false }) +export const jeiCustomCategories = proxy({ + value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> +}) + export const onGameLoad = (onLoad) => { allImagesLoadedState.value = false version = bot.version @@ -324,9 +328,13 @@ const implementedContainersGuiMap = { const upJei = (search: string) => { search = search.toLowerCase() // todo fix pre flat - const matchedSlots = loadedData.itemsArray.map(x => { + const itemsArray = [ + ...jeiCustomCategories.value.flatMap(x => x.items).filter(x => x !== null), + ...loadedData.itemsArray.filter(x => x.displayName.toLowerCase().includes(search)).map(item => new PrismarineItem(item.id, 1)).filter(x => x !== null) + ] + const matchedSlots = itemsArray.map(x => { if (!x.displayName.toLowerCase().includes(search)) return null - return new PrismarineItem(x.id, 1) + return x }).filter(a => a !== null) lastWindow.pwindow.win.jeiSlotsPage = 0 lastWindow.pwindow.win.jeiSlots = mapSlots(matchedSlots, true) diff --git a/src/react/ChatProvider.tsx b/src/react/ChatProvider.tsx index e261c086..cf849df7 100644 --- a/src/react/ChatProvider.tsx +++ b/src/react/ChatProvider.tsx @@ -88,7 +88,7 @@ export default () => { // normalize items = items.map(item => `/${item}`) } - if (localServer) { + if (items.length) { items = [...items, ...getBuiltinCommandsList()] } } From d921977cafc28a46749c738692e43f45b8ecee90 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 23:21:50 +0300 Subject: [PATCH 07/31] on item giving preserve all metadata & nbt --- src/inventoryWindows.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 6fd35611..c2a1d353 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -456,7 +456,15 @@ const openWindow = (type: string | undefined) => { inGameError(`Item for block ${slotItem.name} not found`) return } - const item = new PrismarineItem(itemId, isRightclick ? 64 : 1, slotItem.metadata) + const item = PrismarineItem.fromNotch({ + ...slotItem, + itemId, + itemCount: isRightclick ? 64 : 1, + components: slotItem.components ?? [], + removeComponents: slotItem.removedComponents ?? [], + itemDamage: slotItem.metadata ?? 0, + nbt: slotItem.nbt, + }) if (bot.game.gameMode === 'creative') { const freeSlot = bot.inventory.firstEmptyInventorySlot() if (freeSlot === null) return From 91dc4d1007c92aabe4c0dd2083e326990994ed5a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 13 Mar 2025 23:27:44 +0300 Subject: [PATCH 08/31] fix build info alert --- src/react/MainMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index 09214af2..267e5c4b 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -62,8 +62,9 @@ export default ({ const versionLongPress = useLongPress( () => { - const buildDate = process.env.BUILD_VERSION ? new Date(process.env.BUILD_VERSION) : null - alert(`BUILD INFO:\n${buildDate?.toLocaleString() || 'Development build'}`) + const buildDate = process.env.BUILD_VERSION ? new Date(process.env.BUILD_VERSION + ':00:00.000Z') : null + const hoursAgo = buildDate ? Math.round((Date.now() - buildDate.getTime()) / (1000 * 60 * 60)) : null + alert(`BUILD DATE:\n${buildDate?.toLocaleString() || 'Development build'}${hoursAgo ? `\nBuilt ${hoursAgo} hours ago` : ''}`) }, () => onVersionTextClick?.(), ) From a8564232f7189a01b92e1a37d2b74b9849c8eed6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 01:36:14 +0300 Subject: [PATCH 09/31] fix: make chat arrows work on ios fix: disable annoying in many cases auto correct on ios (more annoying than useful especially in commands) fix: make stats dont overlap with chat fix: fix edgecases when focusing on chat was not possible on mobile --- renderer/viewer/lib/ui/newStats.ts | 2 +- src/index.ts | 1 + src/optionsStorage.ts | 2 +- src/react/Chat.css | 23 +++++++++---- src/react/Chat.tsx | 54 ++++++++++++++++++++---------- 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/renderer/viewer/lib/ui/newStats.ts b/renderer/viewer/lib/ui/newStats.ts index fff4d28c..d18ad16c 100644 --- a/renderer/viewer/lib/ui/newStats.ts +++ b/renderer/viewer/lib/ui/newStats.ts @@ -3,7 +3,7 @@ const rightOffset = 0 const stats = {} -let lastY = 20 +let lastY = 40 export const addNewStat = (id: string, width = 80, x = rightOffset, y = lastY) => { const pane = document.createElement('div') pane.style.position = 'fixed' diff --git a/src/index.ts b/src/index.ts index ab055ce6..67187c1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -829,6 +829,7 @@ export async function connect (connectOptions: ConnectOptions) { if (appStatusState.isError) return const waitForChunks = async () => { + if (appQueryParams.sp === '1') return //todo const waitForChunks = options.waitForChunksRender === 'sp-only' ? !!singleplayer : options.waitForChunksRender if (viewer.world.allChunksFinished || !waitForChunks) { return diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index ef397238..d8d72809 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -165,7 +165,7 @@ const migrateOptions = (options: Partial>) => { export type AppOptions = typeof defaultOptions // when opening html file locally in browser, localStorage is shared between all ever opened html files, so we try to avoid conflicts -const localStorageKey = process.env.SINGLE_FILE_BUILD ? 'minecraftWebClientOptions' : 'options' +const localStorageKey = process.env?.SINGLE_FILE_BUILD ? 'minecraftWebClientOptions' : 'options' export const options: AppOptions = proxy({ ...defaultOptions, ...initialAppConfig.defaultSettings, diff --git a/src/react/Chat.css b/src/react/Chat.css index 141524af..601e4ce3 100644 --- a/src/react/Chat.css +++ b/src/react/Chat.css @@ -29,6 +29,12 @@ div.chat-wrapper { gap: 1px; } +.chat-submit-button { + visibility: hidden; + position: absolute; + pointer-events: none !important; +} + .chat-input-wrapper form { display: flex; } @@ -157,17 +163,22 @@ input[type=text], height: 15px; } -.chat-mobile-hidden { - width: 8px; - height: 0; +.chat-mobile-input-hidden { position: absolute; + width: 8px; + height: 1px !important; display: block !important; opacity: 0; - pointer-events: none; + height: 1px !important; + /* ios: using z-index, pointer-events: none or top below -10px breaks arrows */ } -.chat-mobile-hidden:nth-last-child(1) { - height: 8px; +.chat-mobile-input-hidden-up { + top: -10px; +} + +.chat-mobile-input-hidden-down { + top: -5px; } #chatinput:focus { diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx index 129bfee1..78c69bec 100644 --- a/src/react/Chat.tsx +++ b/src/react/Chat.tsx @@ -71,6 +71,8 @@ export default ({ }: Props) => { const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]')) const [isInputFocused, setIsInputFocused] = useState(false) + // const [spellCheckEnabled, setSpellCheckEnabled] = useState(false) + const spellCheckEnabled = false const [completePadText, setCompletePadText] = useState('') const completeRequestValue = useRef('') @@ -107,9 +109,28 @@ export default ({ }, 0) } - const auxInputFocus = (fireKey: string) => { + const handleArrowUp = () => { + if (chatHistoryPos.current === 0) return + if (chatHistoryPos.current === sendHistoryRef.current.length) { // started navigating history + inputCurrentlyEnteredValue.current = chatInput.current.value + } + chatHistoryPos.current-- + updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || '') + } + + const handleArrowDown = () => { + if (chatHistoryPos.current === sendHistoryRef.current.length) return + chatHistoryPos.current++ + updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || inputCurrentlyEnteredValue.current || '') + } + + const auxInputFocus = (direction: 'up' | 'down') => { chatInput.current.focus() - chatInput.current.dispatchEvent(new KeyboardEvent('keydown', { code: fireKey, bubbles: true })) + if (direction === 'up') { + handleArrowUp() + } else { + handleArrowDown() + } } useEffect(() => { @@ -125,6 +146,7 @@ export default ({ if (opened) { updateInputValue(chatInputValueGlobal.value) chatInputValueGlobal.value = '' + chatHistoryPos.current = sendHistoryRef.current.length if (!usingTouch) { chatInput.current.focus() } @@ -167,6 +189,8 @@ export default ({ const onMainInputChange = () => { const completeValue = getCompleteValue() setCompletePadText(completeValue === '/' ? '' : completeValue) + // not sure if enabling would be useful at all (maybe make as a setting in the future?) + // setSpellCheckEnabled(!chatInput.current.value.startsWith('/')) if (completeRequestValue.current === completeValue) { updateFilteredCompleteItems(completionItemsSource) return @@ -271,20 +295,23 @@ export default ({ {isIos && auxInputFocus('ArrowUp')} + onFocus={() => auxInputFocus('up')} onChange={() => { }} />} setIsInputFocused(false)} onKeyDown={(e) => { if (e.code === 'ArrowUp') { - if (chatHistoryPos.current === 0) return - if (chatHistoryPos.current === sendHistoryRef.current.length) { // started navigating history - inputCurrentlyEnteredValue.current = e.currentTarget.value - } - chatHistoryPos.current-- - updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || '') + handleArrowUp() } else if (e.code === 'ArrowDown') { - if (chatHistoryPos.current === sendHistoryRef.current.length) return - chatHistoryPos.current++ - updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || inputCurrentlyEnteredValue.current || '') + handleArrowDown() } if (e.code === 'Tab') { if (completionItemsSource.length) { @@ -327,15 +347,15 @@ export default ({ {isIos && auxInputFocus('ArrowDown')} + onFocus={() => auxInputFocus('down')} onChange={() => { }} />} {/* for some reason this is needed to make Enter work on android chrome */} -
From 09cd2c3f644c0cd52d53778aaa167a3c050e06ad Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 01:50:54 +0300 Subject: [PATCH 10/31] fix(guiRenderer): dont break textures with custom namespaces rendering --- renderer/viewer/lib/guiRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/viewer/lib/guiRenderer.ts b/renderer/viewer/lib/guiRenderer.ts index f4af3d20..d2987ce6 100644 --- a/renderer/viewer/lib/guiRenderer.ts +++ b/renderer/viewer/lib/guiRenderer.ts @@ -155,7 +155,7 @@ const generateItemsGui = async (models: Record, isIt return null }, getTextureUV (texture) { - return textureAtlas.getTextureUV(texture.toString().slice(1).split('/').slice(1).join('/') as any) + return textureAtlas.getTextureUV(texture.toString().replace('minecraft:', '').replace('block/', '').replace('item/', '').replace('blocks/', '').replace('items/', '') as any) }, getTextureAtlas () { return textureAtlas.getTextureAtlas() From 518d6ad8661079eb84d948917a1fbb9a80a14130 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 02:13:04 +0300 Subject: [PATCH 11/31] fix always display reconnect and better last packets display (time) --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ src/index.ts | 4 ++-- src/mineflayer/plugins/packetsRecording.ts | 2 ++ src/packetsReplay/replayPackets.ts | 11 +++++++++++ src/react/ServersListProvider.tsx | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index a1fcff61..f6ae254e 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.8", + "mcraft-fun-mineflayer": "^0.1.10", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02a0a671..2b09f583 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.8 - version: 0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + specifier: ^0.1.10 + version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -6690,9 +6690,9 @@ packages: resolution: {integrity: sha512-j2D1RNYtB5Z9gFu9MVjyDBbiALI0mWZ3xW/A3PPefVAHm3HJ2T1vH+1XBov1spBGPl7u+Zo7mRXza3X0egbeOg==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.8: - resolution: {integrity: sha512-jyJTihNHfeToBPwVs3QMKBlVcaCABJ25YN2eoIBQEVTRVFzaXh13XRpElphLzTMj1Q5XFYqufHtMoR4tsb08qQ==} - version: 0.1.8 + mcraft-fun-mineflayer@0.1.10: + resolution: {integrity: sha512-KHzPts82I39nTDZlGwqJo1JXLwaIUHphBbmGWv7oYztUrq3iPiJDEIFgst0ROO/apjtHjzbCM9eb19qWw1JM3Q==} + version: 0.1.10 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -17579,7 +17579,7 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 diff --git a/src/index.ts b/src/index.ts index 67187c1c..83f9f289 100644 --- a/src/index.ts +++ b/src/index.ts @@ -362,8 +362,8 @@ export async function connect (connectOptions: ConnectOptions) { miscUiState.hasErrors = true if (miscUiState.gameLoaded) return - appStatusState.showReconnect = true setLoadingScreenStatus(`Error encountered. ${err}`, true) + appStatusState.showReconnect = true onPossibleErrorDisconnect() destroyAll() } @@ -712,8 +712,8 @@ export async function connect (connectOptions: ConnectOptions) { bot.on('kicked', (kickReason) => { console.log('You were kicked!', kickReason) const { formatted: kickReasonFormatted, plain: kickReasonString } = parseFormattedMessagePacket(kickReason) - appStatusState.showReconnect = true setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted) + appStatusState.showReconnect = true destroyAll() }) diff --git a/src/mineflayer/plugins/packetsRecording.ts b/src/mineflayer/plugins/packetsRecording.ts index f1ff18cf..a6b761e7 100644 --- a/src/mineflayer/plugins/packetsRecording.ts +++ b/src/mineflayer/plugins/packetsRecording.ts @@ -93,6 +93,8 @@ declare module 'mineflayer' { export const getLastAutoCapturedPackets = () => circularBuffer?.size export const downloadAutoCapturedPackets = () => { const logger = new PacketsLogger({ minecraftVersion: lastConnectVersion }) + logger.relativeTime = false + logger.formattedTime = true for (const packet of circularBuffer?.getLastElements() ?? []) { logger.log(packet.isFromServer, { name: packet.name, state: packet.state, time: packet.timestamp }, packet.params) } diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index 9e777b38..13891899 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -188,6 +188,10 @@ const mainPacketsReplayer = async (client: ServerClient, packets: ParsedReplayPa } if (packet.isFromServer) { + if (packet.params === null) { + console.warn('packet.params is null', packet) + continue + } playServerPacket(packet.name, packet.params) await new Promise(resolve => { setTimeout(resolve, packet.diff * packetsReplayState.speed + ADDITIONAL_DELAY * (packetsReplayState.customButtons.packetsSenderDelay.state ? 1 : 0)) @@ -216,6 +220,7 @@ const mainPacketsReplayer = async (client: ServerClient, packets: ParsedReplayPa setTimeout(resolve, 1000) })] : []) ]) + clientsPacketsWaiter.stopWaiting() clientPackets = [] } } @@ -236,6 +241,7 @@ interface PacketsWaiterOptions { interface PacketsWaiter { addPacket(name: string, params: any): void waitForPackets(packets: string[]): Promise + stopWaiting(): void } const createPacketsWaiter = (options: PacketsWaiterOptions = {}): PacketsWaiter => { @@ -296,6 +302,11 @@ const createPacketsWaiter = (options: PacketsWaiterOptions = {}): PacketsWaiter isWaiting = false packetHandler = null } + }, + stopWaiting () { + isWaiting = false + packetHandler = null + queuedPackets.length = 0 } } } diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index f0543e21..bf440c69 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -407,7 +407,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL worldNameRightGrayed: additional?.textNameRightGrayed ?? '', iconSrc: additional?.icon, offline: additional?.offline, - group: 'Custom Servers' + group: 'Your Servers' } })} initialProxies={{ From a67b9d7aa215bb8de6fd5c23b2cfd58e0ac14fea Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 19:11:14 +0300 Subject: [PATCH 12/31] active back all vanilla mechanics like hotbar wheel when replay window is minimized --- src/react/ReplayPanel.tsx | 8 +++++--- src/react/state/packetsReplayState.ts | 1 + src/utils.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/react/ReplayPanel.tsx b/src/react/ReplayPanel.tsx index fd4082af..3b709882 100644 --- a/src/react/ReplayPanel.tsx +++ b/src/react/ReplayPanel.tsx @@ -1,9 +1,11 @@ import { useState, useEffect } from 'react' +import { useSnapshot } from 'valtio' import { filterPackets } from './packetsFilter' import { DARK_COLORS } from './components/replay/constants' import FilterInput from './components/replay/FilterInput' import PacketList from './components/replay/PacketList' import ProgressBar from './components/replay/ProgressBar' +import { packetsReplayState } from './state/packetsReplayState' interface Props { replayName: string @@ -41,7 +43,7 @@ export default function ReplayPanel ({ style }: Props) { const [filter, setFilter] = useState(defaultFilter) - const [isMinimized, setIsMinimized] = useState(false) + const { isMinimized } = useSnapshot(packetsReplayState) const { filtered: filteredPackets, hiddenCount } = filterPackets(packets.slice(-500), filter) useEffect(() => { @@ -50,7 +52,7 @@ export default function ReplayPanel ({ const handlePlayPauseClick = () => { if (isMinimized) { - setIsMinimized(false) + packetsReplayState.isMinimized = false } else { onPlayPause?.(!isPlaying) } @@ -113,7 +115,7 @@ export default function ReplayPanel ({
{replayName || 'Unnamed Replay'}
- - GitHub - - {linksButton} +
diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index cdcfc096..c112cb0e 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -8,7 +8,6 @@ import { setLoadingScreenStatus } from '../appStatus' import { openFilePicker, copyFilesAsync, mkdirRecursive, openWorldDirectory, removeFileRecursiveAsync } from '../browserfs' import MainMenu from './MainMenu' -import { DiscordButton } from './DiscordButton' const isMainMenu = () => { return activeModalStack.length === 0 && !miscUiState.gameLoaded @@ -145,7 +144,6 @@ export default () => { }} githubAction={() => openGithub()} optionsAction={() => openOptionsMenu('main')} - linksButton={} bottomRightLinks={process.env.MAIN_MENU_LINKS} openFileAction={e => { if (!!window.showDirectoryPicker && !e.shiftKey) { diff --git a/src/react/PauseLinkButtons.tsx b/src/react/PauseLinkButtons.tsx new file mode 100644 index 00000000..18331f2d --- /dev/null +++ b/src/react/PauseLinkButtons.tsx @@ -0,0 +1,50 @@ +import { useSnapshot } from 'valtio' +import { openURL } from 'renderer/viewer/lib/simpleUtils' +import { ErrorBoundary } from '@zardoy/react-util' +import { miscUiState } from '../globalState' +import { openGithub } from '../utils' +import Button from './Button' +import { DiscordButton } from './DiscordButton' +import styles from './PauseScreen.module.css' + +function PauseLinkButtonsInner () { + const { appConfig } = useSnapshot(miscUiState) + const pauseLinksConfig = appConfig?.pauseLinks + + if (!pauseLinksConfig) return null + + const renderButton = (button: Record, style: React.CSSProperties, key: number) => { + if (button.type === 'discord') { + return + } + if (button.type === 'github') { + return + } + if (button.type === 'url' && button.text) { + return + } + return null + } + + return ( + <> + {pauseLinksConfig.map((row, i) => { + const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } + return ( +
+ {row.map((button, k) => renderButton(button, style, k))} +
+ ) + })} + + ) +} + +export default () => { + return { + console.error(error) + return null + }}> + + +} diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 35d873ea..55ffe47f 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -34,6 +34,7 @@ import { DiscordButton } from './DiscordButton' import { showNotification } from './NotificationProvider' import { appStatusState, reconnectReload } from './AppStatusProvider' import NetworkStatus from './NetworkStatus' +import PauseLinkButtons from './PauseLinkButtons' const waitForPotentialRender = async () => { return new Promise(resolve => { @@ -227,26 +228,6 @@ export default () => { if (!isModalActive) return null - const pauseLinks: React.ReactNode[] = [] - const pauseLinksConfig = miscUiState.appConfig?.pauseLinks - if (pauseLinksConfig) { - for (const [i, row] of pauseLinksConfig.entries()) { - const rowButtons: React.ReactNode[] = [] - for (const [k, button] of row.entries()) { - const key = `${i}-${k}` - const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } - if (button.type === 'discord') { - rowButtons.push() - } else if (button.type === 'github') { - rowButtons.push() - } else if (button.type === 'url' && button.text) { - rowButtons.push() - } - } - pauseLinks.push(
{rowButtons}
) - } - } - return
- {pauseLinks} + {singleplayer ? (
From 72028d925d4f01be0094ceb2badbe4a839006046 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 23:25:13 +0300 Subject: [PATCH 15/31] feat: revamp right click experience by reworking block placing prediction and extending activatble items list --- package.json | 2 +- pnpm-lock.yaml | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index f6ae254e..6485c25e 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.0.9", + "mineflayer-mouse": "^0.1.0", "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 2b09f583..dc4b1555 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.10 - version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + version: 0.1.10(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 @@ -360,10 +360,10 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.0.9 - version: 0.0.9(@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.0 + version: 0.1.0(@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 @@ -4129,6 +4129,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -6916,8 +6920,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.0.9: - resolution: {integrity: sha512-oViJrou2tziPuox/ZFJWZJMCnaF5+KPEsrbBgKmXVr3eK35iPohdhYwoKgqgBY8uXS/bNaFnkCR0K7ZDqyBF8g==} + mineflayer-mouse@0.1.0: + resolution: {integrity: sha512-NFfHASMo3iZOECoYOHVqGBFROVapzDJRlgANMiWnynO/oPUZkOJF6oRrcE33FCVHGlwMCT2S6N7TcqCYqF21Uw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -6927,8 +6931,8 @@ packages: resolution: {integrity: sha512-q7cmpZFaSI6sodcMJxc2GkV8IO84HbsUP+xNipGKfGg+FMISKabzdJ838Axb60qRtZrp6ny7LluQE7lesHvvxQ==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d} version: 4.25.0 engines: {node: '>=18'} @@ -14365,6 +14369,11 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camel-case@4.1.2: @@ -15424,7 +15433,7 @@ snapshots: es-iterator-helpers@1.2.1: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 @@ -17579,12 +17588,12 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.10(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) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(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 transitivePeerDependencies: @@ -17949,7 +17958,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.0.9(@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.0(@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) @@ -18010,7 +18019,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13): dependencies: 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) From 3e056946ec07c45e30182a7d012604564f5ab188 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 02:20:47 +0300 Subject: [PATCH 16/31] add world download button --- src/react/PauseScreen.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 55ffe47f..f051f9e1 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -35,6 +35,7 @@ import { showNotification } from './NotificationProvider' import { appStatusState, reconnectReload } from './AppStatusProvider' import NetworkStatus from './NetworkStatus' import PauseLinkButtons from './PauseLinkButtons' +import { pixelartIcons } from './PixelartIcon' const waitForPotentialRender = async () => { return new Promise(resolve => { @@ -161,7 +162,7 @@ export default () => { const { singleplayer, wanOpened, wanOpening } = useSnapshot(miscUiState) const { noConnection } = useSnapshot(gameAdditionalState) const { active: packetsReplaceActive, hasRecordedPackets: packetsReplaceHasRecordedPackets } = useSnapshot(packetsRecordingState) - const { displayRecordButton } = useSnapshot(options) + const { displayRecordButton: displayPacketsButtons } = useSnapshot(options) const handlePointerLockChange = () => { if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { @@ -234,7 +235,7 @@ export default () => { icon="pixelarticons:folder" onClick={async () => openWorldActions()} /> - {displayRecordButton && ( + {displayPacketsButtons && ( <>
From da35cfb8a23aa3978ad53bdca0e6bcf45e697a03 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 16:46:51 +0300 Subject: [PATCH 17/31] up mouse --- package.json | 4 ++-- pnpm-lock.yaml | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 6485c25e..9dd43eaf 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.10", + "mcraft-fun-mineflayer": "^0.1.12", "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.0", + "mineflayer-mouse": "^0.1.1", "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 dc4b1555..d8d28545 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.10 - version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) + 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)) 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.0 - version: 0.1.0(@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.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) 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.10: - resolution: {integrity: sha512-KHzPts82I39nTDZlGwqJo1JXLwaIUHphBbmGWv7oYztUrq3iPiJDEIFgst0ROO/apjtHjzbCM9eb19qWw1JM3Q==} - version: 0.1.10 + mcraft-fun-mineflayer@0.1.12: + resolution: {integrity: sha512-BhfkagVJX+QmD/dt3qNQS5f7g3/7NI//OfSW4VnRolCnZtrLU8ekr59bLRcNmUWsvtTjkg+wbMeXwclHshSWOA==} + version: 0.1.12 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -6920,8 +6920,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.0: - resolution: {integrity: sha512-NFfHASMo3iZOECoYOHVqGBFROVapzDJRlgANMiWnynO/oPUZkOJF6oRrcE33FCVHGlwMCT2S6N7TcqCYqF21Uw==} + mineflayer-mouse@0.1.1: + resolution: {integrity: sha512-7jKN+6pIGtQVfYxEIm4tA9CYwTS8Mgn/qJ2wyhrAoIEW8smCHUu0kj5Sdo0TwTCdlOQClKt8aEBZ13E7MGqOhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -17588,7 +17588,7 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.12(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 @@ -17958,7 +17958,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.0(@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.1(@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) From 36bf18b02f616b900852016a93ab22f79061f39b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 17 Mar 2025 16:05:04 +0300 Subject: [PATCH 18/31] feat: refactor all app storage managment (#310) --- src/appConfig.ts | 3 + src/browserfs.ts | 20 +--- src/controls.ts | 5 +- src/optionsGuiScheme.tsx | 112 ++++++++++++++++++- src/optionsStorage.ts | 33 ++++-- src/react/AddServerOrConnect.tsx | 18 +-- src/react/Chat.css | 5 +- src/react/Input.tsx | 18 ++- src/react/MainMenuRenderApp.tsx | 18 --- src/react/SelectOption.tsx | 131 +++++++++++++++++++--- src/react/ServersList.tsx | 79 +++++++------ src/react/ServersListProvider.tsx | 177 +++++++++--------------------- src/react/appStorageProvider.ts | 135 +++++++++++++++++++++++ src/react/serversStorage.ts | 62 +++-------- src/react/storageProvider.ts | 13 --- 15 files changed, 525 insertions(+), 304 deletions(-) create mode 100644 src/react/appStorageProvider.ts delete mode 100644 src/react/storageProvider.ts diff --git a/src/appConfig.ts b/src/appConfig.ts index 3d6d8f93..b8f83ad1 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -1,6 +1,7 @@ import { disabledSettings, options, qsOptions } from './optionsStorage' import { miscUiState } from './globalState' import { setLoadingScreenStatus } from './appStatus' +import { setStorageDataOnAppConfigLoad } from './react/appStorageProvider' export type AppConfig = { // defaultHost?: string @@ -42,6 +43,8 @@ export const loadAppConfig = (appConfig: AppConfig) => { } } } + + setStorageDataOnAppConfigLoad() } export const isBundledConfigUsed = !!process.env.INLINED_APP_CONFIG diff --git a/src/browserfs.ts b/src/browserfs.ts index 0f4579b8..9fb0771f 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -15,6 +15,7 @@ import { getFixedFilesize } from './downloadAndOpenFile' import { packetsReplayState } from './react/state/packetsReplayState' import { createFullScreenProgressReporter } from './core/progressReporter' import { showNotification } from './react/NotificationProvider' +import { resetAppStorage } from './react/appStorageProvider' const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive') browserfs.install(window) @@ -620,24 +621,13 @@ export const openWorldZip = async (...args: Parameters } } -export const resetLocalStorageWorld = () => { - for (const key of Object.keys(localStorage)) { - if (/^[\da-fA-F]{8}(?:\b-[\da-fA-F]{4}){3}\b-[\da-fA-F]{12}$/g.test(key) || key === '/') { - localStorage.removeItem(key) - } - } -} - -export const resetLocalStorageWithoutWorld = () => { - for (const key of Object.keys(localStorage)) { - if (!/^[\da-fA-F]{8}(?:\b-[\da-fA-F]{4}){3}\b-[\da-fA-F]{12}$/g.test(key) && key !== '/') { - localStorage.removeItem(key) - } - } +export const resetLocalStorage = () => { resetOptions() + resetAppStorage() } -window.resetLocalStorageWorld = resetLocalStorageWorld +window.resetLocalStorage = resetLocalStorage + export const openFilePicker = (specificCase?: 'resourcepack') => { // create and show input picker let picker: HTMLInputElement = document.body.querySelector('input#file-zip-picker')! diff --git a/src/controls.ts b/src/controls.ts index a3a54ffb..69b94636 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -25,11 +25,12 @@ import { showNotification } from './react/NotificationProvider' import { lastConnectOptions } from './react/AppStatusProvider' import { onCameraMove, onControInit } from './cameraRotationControls' import { createNotificationProgressReporter } from './core/progressReporter' +import { appStorage } from './react/appStorageProvider' -export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) as UserOverridesConfig +export const customKeymaps = proxy(appStorage.keybindings) subscribe(customKeymaps, () => { - localStorage.keymap = JSON.stringify(customKeymaps) + appStorage.keybindings = customKeymaps }) const controlOptions = { diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 7032e644..5851dd8d 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -3,19 +3,21 @@ import { useSnapshot } from 'valtio' import { openURL } from 'renderer/viewer/lib/simpleUtils' import { noCase } from 'change-case' import { gameAdditionalState, miscUiState, openOptionsMenu, showModal } from './globalState' -import { AppOptions, options } from './optionsStorage' +import { AppOptions, getChangedSettings, options, resetOptions } from './optionsStorage' import Button from './react/Button' import { OptionMeta, OptionSlider } from './react/OptionsItems' import Slider from './react/Slider' import { getScreenRefreshRate } from './utils' import { setLoadingScreenStatus } from './appStatus' -import { openFilePicker, resetLocalStorageWithoutWorld } from './browserfs' +import { openFilePicker, resetLocalStorage } from './browserfs' import { completeResourcepackPackInstall, getResourcePackNames, resourcePackState, uninstallResourcePack } from './resourcePack' import { downloadPacketsReplay, packetsRecordingState } from './packetsReplay/packetsReplayLegacy' -import { showOptionsModal } from './react/SelectOption' +import { showInputsModal, showOptionsModal } from './react/SelectOption' import supportedVersions from './supportedVersions.mjs' import { getVersionAutoSelect } from './connect' import { createNotificationProgressReporter } from './core/progressReporter' +import { customKeymaps } from './controls' +import { appStorage } from './react/appStorageProvider' export const guiOptionsScheme: { [t in OptionsGroupType]: Array<{ [K in keyof AppOptions]?: Partial> } & { custom? }> @@ -450,9 +452,19 @@ export const guiOptionsScheme: { return + >Reset settings + }, + }, + { + custom () { + return }, }, { @@ -460,6 +472,11 @@ export const guiOptionsScheme: { return Developer }, }, + { + custom () { + return + } + }, + { + custom () { + return + } + }, + { + custom () { + return + } + }, + { + custom () { + return + } + } + ], } -export type OptionsGroupType = 'main' | 'render' | 'interface' | 'controls' | 'sound' | 'advanced' | 'VR' +export type OptionsGroupType = 'main' | 'render' | 'interface' | 'controls' | 'sound' | 'advanced' | 'VR' | 'export-import' const Category = ({ children }) =>
>) => { export type AppOptions = typeof defaultOptions -// when opening html file locally in browser, localStorage is shared between all ever opened html files, so we try to avoid conflicts -const localStorageKey = process.env?.SINGLE_FILE_BUILD ? 'minecraftWebClientOptions' : 'options' +const isDeepEqual = (a: any, b: any): boolean => { + if (a === b) return true + if (typeof a !== typeof b) return false + if (typeof a !== 'object') return false + if (a === null || b === null) return a === b + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false + return a.every((item, index) => isDeepEqual(item, b[index])) + } + const keysA = Object.keys(a) + const keysB = Object.keys(b) + if (keysA.length !== keysB.length) return false + return keysA.every(key => isDeepEqual(a[key], b[key])) +} + +export const getChangedSettings = () => { + return Object.fromEntries( + Object.entries(options).filter(([key, value]) => !isDeepEqual(defaultOptions[key], value)) + ) +} + export const options: AppOptions = proxy({ ...defaultOptions, ...initialAppConfig.defaultSettings, - ...migrateOptions(JSON.parse(localStorage[localStorageKey] || '{}')), + ...migrateOptions(appStorage.options), ...qsOptions }) @@ -181,14 +198,14 @@ export const resetOptions = () => { Object.defineProperty(window, 'debugChangedOptions', { get () { - return Object.fromEntries(Object.entries(options).filter(([key, v]) => defaultOptions[key] !== v)) + return getChangedSettings() }, }) subscribe(options, () => { // Don't save disabled settings to localStorage const saveOptions = omitObj(options, [...disabledSettings.value] as any) - localStorage[localStorageKey] = JSON.stringify(saveOptions) + appStorage.options = saveOptions }) type WatchValue = >(proxy: T, callback: (p: T, isChanged: boolean) => void) => () => void diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 1186fd9a..c25db793 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -3,7 +3,7 @@ import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' import { parseServerAddress } from '../parseServerAddress' import Screen from './Screen' -import Input from './Input' +import Input, { INPUT_LABEL_WIDTH, InputWithLabel } from './Input' import Button from './Button' import SelectGameVersion from './SelectGameVersion' import { usePassesScaledDimensions } from './UIProvider' @@ -32,8 +32,6 @@ interface Props { allowAutoConnect?: boolean } -const ELEMENTS_WIDTH = 190 - export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions, allowAutoConnect }: Props) => { const isSmallHeight = !usePassesScaledDimensions(null, 350) const qsParamName = parseQs ? appQueryParams.name : undefined @@ -256,20 +254,8 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const ButtonWrapper = ({ ...props }: React.ComponentProps) => { props.style ??= {} - props.style.width = ELEMENTS_WIDTH + props.style.width = INPUT_LABEL_WIDTH return
} + +export default Input + +export const INPUT_LABEL_WIDTH = 190 + +export const InputWithLabel = ({ label, span, ...props }: React.ComponentProps & { label, span? }) => { + return
+ + +
+} diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index c112cb0e..f678e7f7 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -122,24 +122,6 @@ export default () => { singleplayerAvailable={singleplayerAvailable} connectToServerAction={() => showModal({ reactType: 'serversList' })} singleplayerAction={async () => { - const oldFormatSave = fs.existsSync('./world/level.dat') - if (oldFormatSave) { - setLoadingScreenStatus('Migrating old save, don\'t close the page') - try { - await mkdirRecursive('/data/worlds/local') - await copyFilesAsync('/world/', '/data/worlds/local') - try { - await removeFileRecursiveAsync('/world/') - } catch (err) { - console.error(err) - } - } catch (err) { - console.warn(err) - alert('Failed to migrate world from localStorage') - } finally { - setLoadingScreenStatus(undefined) - } - } showModal({ reactType: 'singleplayer' }) }} githubAction={() => openGithub()} diff --git a/src/react/SelectOption.tsx b/src/react/SelectOption.tsx index 9db20c86..4df471c0 100644 --- a/src/react/SelectOption.tsx +++ b/src/react/SelectOption.tsx @@ -1,10 +1,14 @@ import { proxy, useSnapshot } from 'valtio' +import { useEffect, useRef } from 'react' +import { noCase } from 'change-case' +import { titleCase } from 'title-case' import { hideCurrentModal, showModal } from '../globalState' import { parseFormattedMessagePacket } from '../botUtils' import Screen from './Screen' import { useIsModalActive } from './utilsApp' import Button from './Button' import MessageFormattedString from './MessageFormattedString' +import Input, { InputWithLabel } from './Input' const state = proxy({ title: '', @@ -12,6 +16,8 @@ const state = proxy({ showCancel: true, minecraftJsonMessage: null as null | Record, behavior: 'resolve-close' as 'resolve-close' | 'close-resolve', + inputs: {} as Record, + inputsConfirmButton: '' }) let resolve @@ -35,17 +41,63 @@ export const showOptionsModal = async ( title, options, showCancel: cancel, - minecraftJsonMessage: minecraftJsonMessageParsed + minecraftJsonMessage: minecraftJsonMessageParsed, + inputs: {}, + inputsConfirmButton: '' + }) + }) +} + +type InputOption = { + type: 'text' | 'checkbox' + defaultValue?: string | boolean + label?: string +} +export const showInputsModal = async >( + title: string, + inputs: T, + { cancel = true, minecraftJsonMessage }: { cancel?: boolean, minecraftJsonMessage? } = {} +): Promise<{ + [K in keyof T]: T[K] extends { type: 'text' } + ? string + : T[K] extends { type: 'checkbox' } + ? boolean + : never +}> => { + showModal({ reactType: 'general-select' }) + let minecraftJsonMessageParsed + if (minecraftJsonMessage) { + const parseResult = parseFormattedMessagePacket(minecraftJsonMessage) + minecraftJsonMessageParsed = parseResult.formatted + if (parseResult.plain) { + title += ` (${parseResult.plain})` + } + } + return new Promise((_resolve) => { + resolve = _resolve + Object.assign(state, { + title, + inputs, + showCancel: cancel, + minecraftJsonMessage: minecraftJsonMessageParsed, + options: [], + inputsConfirmButton: 'Confirm' }) }) } export default () => { - const { title, options, showCancel, minecraftJsonMessage } = useSnapshot(state) + const { title, options, showCancel, minecraftJsonMessage, inputs, inputsConfirmButton } = useSnapshot(state) const isModalActive = useIsModalActive('general-select') + const inputValues = useRef({}) + + useEffect(() => { + inputValues.current = Object.fromEntries(Object.entries(inputs).map(([key, input]) => [key, input.defaultValue ?? (input.type === 'checkbox' ? false : '')])) + }, [inputs]) + if (!isModalActive) return - const resolveClose = (value: string | undefined) => { + const resolveClose = (value: any) => { if (state.behavior === 'resolve-close') { resolve(value) hideCurrentModal() @@ -59,17 +111,66 @@ export default () => { {minecraftJsonMessage &&
} - {options.map(option => )} - {showCancel && } +
+ {options.length > 0 &&
+ {options.map(option => )} +
} +
+ {Object.entries(inputs).map(([key, input]) => { + const label = input.label ?? titleCase(noCase(key)) + return
+ {input.type === 'text' && ( + { + inputValues.current[key] = e.target.value + }} + /> + )} + {input.type === 'checkbox' && ( + + )} +
+ })} +
+ {inputs && inputsConfirmButton && ( + + )} + {showCancel && ( + + )} +
} diff --git a/src/react/ServersList.tsx b/src/react/ServersList.tsx index 46541af6..7b34f016 100644 --- a/src/react/ServersList.tsx +++ b/src/react/ServersList.tsx @@ -1,64 +1,61 @@ -import React from 'react' +import React, { useMemo } from 'react' +import { useSnapshot } from 'valtio' +import { miscUiState } from '../globalState' +import { appQueryParams } from '../appParams' import Singleplayer from './Singleplayer' import Input from './Input' import Button from './Button' import PixelartIcon, { pixelartIcons } from './PixelartIcon' - import Select from './Select' import { BaseServerInfo } from './AddServerOrConnect' import { useIsSmallWidth } from './simpleHooks' +import { appStorage, SavedProxiesData, ServerHistoryEntry } from './appStorageProvider' + +const getInitialProxies = () => { + const proxies = [] as string[] + if (miscUiState.appConfig?.defaultProxy) { + proxies.push(miscUiState.appConfig.defaultProxy) + } + return proxies +} + +export const getCurrentProxy = (): string | undefined => { + return appQueryParams.proxy ?? appStorage.proxiesData?.selected ?? getInitialProxies()[0] +} + +export const getCurrentUsername = () => { + return appQueryParams.username ?? appStorage.username +} interface Props extends React.ComponentProps { joinServer: (info: BaseServerInfo | string, additional: { shouldSave?: boolean index?: number }) => void - initialProxies: SavedProxiesLocalStorage - updateProxies: (proxies: SavedProxiesLocalStorage) => void - username: string - setUsername: (username: string) => void onProfileClick?: () => void setQuickConnectIp?: (ip: string) => void - serverHistory?: Array<{ - ip: string - versionOverride?: string - numConnects: number - }> -} - -export interface SavedProxiesLocalStorage { - proxies: readonly string[] - selected: string -} - -type ProxyStatusResult = { - time: number - ping: number - status: 'success' | 'error' | 'unknown' } export default ({ - initialProxies, - updateProxies: updateProxiesProp, joinServer, - username, - setUsername, onProfileClick, setQuickConnectIp, - serverHistory, ...props }: Props) => { - const [proxies, setProxies] = React.useState(initialProxies) - - const updateProxies = (newData: SavedProxiesLocalStorage) => { - setProxies(newData) - updateProxiesProp(newData) - } - + const snap = useSnapshot(appStorage) + const username = useMemo(() => getCurrentUsername(), [appQueryParams.username, appStorage.username]) const [serverIp, setServerIp] = React.useState('') const [save, setSave] = React.useState(true) const [activeHighlight, setActiveHighlight] = React.useState(undefined as 'quick-connect' | 'server-list' | undefined) + const updateProxies = (newData: SavedProxiesData) => { + appStorage.proxiesData = newData + } + + const setUsername = (username: string) => { + appStorage.username = username + } + const getActiveHighlightStyles = (type: typeof activeHighlight) => { const styles: React.CSSProperties = { transition: 'filter 0.2s', @@ -71,6 +68,8 @@ export default ({ const isSmallWidth = useIsSmallWidth() + const initialProxies = getInitialProxies() + const proxiesData = snap.proxiesData ?? { proxies: initialProxies, selected: initialProxies[0] } return setActiveHighlight('quick-connect')} onMouseLeave={() => setActiveHighlight(undefined)} > - {/* todo history */} - {serverHistory?.map((server) => ( -