From 28022a20546fb9b6185773780f6232cf6bb87e50 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 6 May 2025 16:36:31 +0300 Subject: [PATCH 01/62] feat: rework server data saving and auto login by using more aggressive strategy --- src/connect.ts | 1 - src/index.ts | 7 ++++--- src/react/ChatProvider.tsx | 6 +++--- src/react/ServersListProvider.tsx | 1 - src/react/serversStorage.ts | 30 ++++++++++++++++++++++++++++-- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/connect.ts b/src/connect.ts index b68e4325..914303c6 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -21,7 +21,6 @@ export type ConnectOptions = { peerId?: string ignoreQs?: boolean onSuccessfulPlay?: () => void - autoLoginPassword?: string serverIndex?: string authenticatedAccount?: AuthenticatedAccount | true peerOptions?: any diff --git a/src/index.ts b/src/index.ts index 26bba494..afa349fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,7 +77,7 @@ import './water' import { ConnectOptions, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect' import { ref, subscribe } from 'valtio' import { signInMessageState } from './react/SignInMessageProvider' -import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage' +import { findServerPassword, updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage' import { mainMenuState } from './react/MainMenuRenderApp' import './mobileShim' import { parseFormattedMessagePacket } from './botUtils' @@ -754,9 +754,10 @@ export async function connect (connectOptions: ConnectOptions) { } connectOptions.onSuccessfulPlay?.() updateDataAfterJoin() - if (connectOptions.autoLoginPassword) { + const password = findServerPassword() + if (password) { setTimeout(() => { - bot.chat(`/login ${connectOptions.autoLoginPassword}`) + bot.chat(`/login ${password}`) }, 500) } diff --git a/src/react/ChatProvider.tsx b/src/react/ChatProvider.tsx index 50912820..4acc9e79 100644 --- a/src/react/ChatProvider.tsx +++ b/src/react/ChatProvider.tsx @@ -8,7 +8,7 @@ import { viewerVersionState } from '../viewerConnector' import Chat, { Message, fadeMessage } from './Chat' import { useIsModalActive } from './utilsApp' import { hideNotification, notificationProxy, showNotification } from './NotificationProvider' -import { updateLoadedServerData } from './serversStorage' +import { getServerIndex, updateLoadedServerData } from './serversStorage' import { lastConnectOptions } from './AppStatusProvider' import { showOptionsModal } from './SelectOption' @@ -56,13 +56,13 @@ export default () => { placeholder={forwardChat || !viewerConnection ? undefined : 'Chat forwarding is not enabled in the plugin settings'} sendMessage={async (message) => { const builtinHandled = tryHandleBuiltinCommand(message) - if (miscUiState.loadedServerIndex && (message.startsWith('/login') || message.startsWith('/register'))) { + if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) { showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => { updateLoadedServerData((server) => { server.autoLogin ??= {} const password = message.split(' ')[1] server.autoLogin[bot.username] = password - return server + return { ...server } }) hideNotification() }) diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index cd44a27a..8692e0bd 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -287,7 +287,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL proxy: overrides.proxyOverride || getCurrentProxy(), botVersion: overrides.versionOverride ?? /* legacy */ overrides['version'], ignoreQs: true, - autoLoginPassword: server?.autoLogin?.[username], authenticatedAccount, saveServerToHistory: shouldSave, onSuccessfulPlay () { diff --git a/src/react/serversStorage.ts b/src/react/serversStorage.ts index b320e2f3..9b095454 100644 --- a/src/react/serversStorage.ts +++ b/src/react/serversStorage.ts @@ -1,6 +1,7 @@ import { appQueryParams } from '../appParams' import { miscUiState } from '../globalState' import { BaseServerInfo } from './AddServerOrConnect' +import { lastConnectOptions } from './AppStatusProvider' import { appStorage, StoreServerItem } from './appStorageProvider' const serversListQs = appQueryParams.serversList @@ -43,9 +44,34 @@ export function updateServerConnectionHistory (ip: string, version?: string) { } } -export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => { - if (index === undefined) index = miscUiState.loadedServerIndex +export const getServerIndex = () => { + const lastConnectedIp = lastConnectOptions.value?.server + const index = miscUiState.loadedServerIndex + if (index !== undefined) return index + if (lastConnectedIp) { + const idx = appStorage.serversList?.findIndex(s => s.ip === lastConnectedIp).toString() + if (idx === '-1') return undefined + return idx + } + return undefined +} + +export const findServerPassword = () => { + const { username } = bot + const index = getServerIndex() if (index === undefined) return + const pswd = appStorage.serversList?.[index]?.autoLogin?.[username] + if (pswd) return pswd + // try other servers with same host + return appStorage.serversList?.find(s => s.ip === lastConnectOptions.value?.server && s.autoLogin?.[username])?.autoLogin?.[username] +} + +export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => { + if (index === undefined) { + const idx = getServerIndex() + if (idx === undefined) return + index = idx + } const servers = [...(appStorage.serversList ?? [])] const server = servers[index] From bb9bb48efd60d3e617e48748988e86e1ce03d1a0 Mon Sep 17 00:00:00 2001 From: M G <118717627+mgDentist@users.noreply.github.com> Date: Tue, 6 May 2025 16:39:42 +0300 Subject: [PATCH 02/62] feat: Combine IP and port input fields (#345) --- src/react/AddServerOrConnect.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 08ef7f29..19332472 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -46,8 +46,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const parsedInitialIp = parseServerAddress(initialData?.ip) const [serverName, setServerName] = React.useState(initialData?.name ?? qsParamName ?? '') - const [serverIp, setServerIp] = React.useState(parsedQsIp.host || parsedInitialIp.host || '') - const [serverPort, setServerPort] = React.useState(parsedQsIp.port || parsedInitialIp.port || '') + const [serverIp, setServerIp] = React.useState(parsedQsIp.serverIpFull || parsedInitialIp.serverIpFull || '') 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 ?? '') @@ -61,7 +60,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const noAccountSelected = accountIndex === -1 const authenticatedAccountOverride = noAccountSelected ? undefined : freshAccount ? true : accounts?.[accountIndex] - let ipFinal = serverIp.includes(':') ? serverIp : `${serverIp}${serverPort ? `:${serverPort}` : ''}` + let ipFinal = serverIp ipFinal = ipFinal.replace(/:$/, '') const commonUseOptions: BaseServerInfo = { name: serverName, @@ -126,6 +125,9 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ }, []) const displayConnectButton = qsParamIp + const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg'] + // pick random example + const example = serverExamples[Math.floor(Math.random() * serverExamples.length)] return
- {!lockConnect && <> -
- setServerName(value)} placeholder='Defaults to IP' /> -
- } - setServerPort(value)} placeholder={serverIp.startsWith('ws://') || serverIp.startsWith('wss://') ? '' : '25565'} /> + {!lockConnect && <> +
+ setServerName(value)} placeholder='Defaults to IP' /> +
+ } {isSmallHeight ?
:
Overrides:
}
Date: Wed, 7 May 2025 15:03:48 +0300 Subject: [PATCH 03/62] Do not automatically enable renderer debug in dev --- src/optionsStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 65c0b4ba..bc58d21c 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -103,7 +103,7 @@ const defaultOptions = { autoJump: 'auto' as 'auto' | 'always' | 'never', autoParkour: false, vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users - renderDebug: (isDev ? 'advanced' : 'basic') as 'none' | 'advanced' | 'basic', + renderDebug: 'basic' as 'none' | 'advanced' | 'basic', // advanced bot options autoRespawn: false, From b3392bea6bdc0ec3776ababf0a7235cfaa852360 Mon Sep 17 00:00:00 2001 From: M G <118717627+mgDentist@users.noreply.github.com> Date: Wed, 7 May 2025 16:31:36 +0300 Subject: [PATCH 04/62] feat: Entity interaction button for mobile (#346) --- src/react/TouchInteractionHint.module.css | 7 ++++- src/react/TouchInteractionHint.tsx | 36 ++++++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/react/TouchInteractionHint.module.css b/src/react/TouchInteractionHint.module.css index 026b49c8..73fae1fc 100644 --- a/src/react/TouchInteractionHint.module.css +++ b/src/react/TouchInteractionHint.module.css @@ -1,8 +1,8 @@ .hint_container { position: fixed; - top: 20%; left: 0; right: 0; + bottom: calc(var(--safe-area-inset-bottom) + 55px); margin: 0 auto; width: fit-content; display: flex; @@ -13,6 +13,11 @@ text-shadow: 1px 1px 8px rgba(0, 0, 0, 1); } +.hint_container > button { + width: auto; + padding: 0 10px; +} + .hint_text { color: white; font-size: 10px; diff --git a/src/react/TouchInteractionHint.tsx b/src/react/TouchInteractionHint.tsx index e08e4634..bf2b42f0 100644 --- a/src/react/TouchInteractionHint.tsx +++ b/src/react/TouchInteractionHint.tsx @@ -3,28 +3,33 @@ import { useSnapshot } from 'valtio' import { options } from '../optionsStorage' import { activeModalStack } from '../globalState' import { videoCursorInteraction } from '../customChannels' -import PixelartIcon, { pixelartIcons } from './PixelartIcon' +// import PixelartIcon, { pixelartIcons } from './PixelartIcon' import styles from './TouchInteractionHint.module.css' import { useUsingTouch } from './utilsApp' +import Button from './Button' export default () => { const usingTouch = useUsingTouch() const modalStack = useSnapshot(activeModalStack) const { touchInteractionType } = useSnapshot(options) const [hintText, setHintText] = useState(null) + const [entityName, setEntityName] = useState(null) useEffect(() => { const update = () => { const videoInteraction = videoCursorInteraction() if (videoInteraction) { setHintText(`Interact with video`) + setEntityName(null) } else { const cursorState = bot.mouse.getCursorState() if (cursorState.entity) { - const entityName = cursorState.entity.displayName ?? cursorState.entity.name - setHintText(`Attack ${entityName}`) + const name = cursorState.entity.displayName ?? cursorState.entity.name ?? 'Entity' + setHintText(`Attack ${name}`) + setEntityName(name) } else { setHintText(null) + setEntityName(null) } } } @@ -40,13 +45,30 @@ export default () => { } }, []) + const handleUseButtonClick = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + + document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) + bot.mouse.update() + document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) + } + if (!usingTouch || touchInteractionType !== 'classic' || modalStack.length > 0) return null - if (!hintText) return null + if (!hintText && !entityName) return null return ( -
- - {hintText} +
+ {/* temporary hide hint indicator and text */} + {/* + {hintText || 'Attack entity'} */} +
) } From 58799d973caaa19b6214658a6f3aa86638c0868e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 00:07:33 +0300 Subject: [PATCH 05/62] fix transfer on 1.20 and above (should have added patch a long time ago) --- patches/minecraft-protocol.patch | 16 ++++++++++++++++ pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index 29111f69..efc79176 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -1,3 +1,6 @@ +diff --git a/README.md b/README.md +deleted file mode 100644 +index fbcaa43667323a58b8110a4495938c2c6d2d6f83..0000000000000000000000000000000000000000 diff --git a/src/client/chat.js b/src/client/chat.js index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aae0d1d337 100644 --- a/src/client/chat.js @@ -73,6 +76,19 @@ index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108 } function onJoinServerResponse (err) { +diff --git a/src/client/play.js b/src/client/play.js +index 6e06dc15291b38e1eeeec8d7102187b2a23d70a3..f67454942db9276cbb9eab99c281cfe182cb8a1f 100644 +--- a/src/client/play.js ++++ b/src/client/play.js +@@ -53,7 +53,7 @@ module.exports = function (client, options) { + client.write('configuration_acknowledged', {}) + } + client.state = states.CONFIGURATION +- client.on('select_known_packs', () => { ++ client.once('select_known_packs', () => { + client.write('select_known_packs', { packs: [] }) + }) + // Server should send finish_configuration on its own right after sending the client a dimension codec diff --git a/src/client.js b/src/client.js index 74749698f8cee05b5dc749c271544f78d06645b0..e77e0a3f41c1ee780c3abbd54b0801d248c2a07c 100644 --- a/src/client.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f099a85f..19248ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: 3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab + hash: 1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37 path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -142,7 +142,7 @@ importers: version: 3.83.1 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -13190,7 +13190,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13226,7 +13226,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -17023,7 +17023,7 @@ snapshots: 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/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.1 @@ -17342,7 +17342,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17441,7 +17441,7 @@ snapshots: mineflayer@4.27.0(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 @@ -17465,7 +17465,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-physics-util': 1.8.7 minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 From aa817139b73ee5ba23d442744a78bb85ec285f7c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 04:11:17 +0300 Subject: [PATCH 06/62] fix: fix auto jump on mobile! --- package.json | 2 +- pnpm-lock.yaml | 44 +++++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 16f009f0..e1f2b6fc 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "dependencies": { "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", - "@nxg-org/mineflayer-auto-jump": "^0.7.12", + "@nxg-org/mineflayer-auto-jump": "^0.7.17", "@nxg-org/mineflayer-tracker": "1.2.1", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19248ee1..f3c88f2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: ^0.26.1 version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nxg-org/mineflayer-auto-jump': - specifier: ^0.7.12 - version: 0.7.12 + specifier: ^0.7.17 + version: 0.7.17 '@nxg-org/mineflayer-tracker': specifier: 1.2.1 version: 1.2.1(encoding@0.1.13) @@ -347,7 +347,7 @@ importers: version: 0.1.10(@types/debug@4.1.12)(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) mineflayer-pathfinder: specifier: ^2.4.4 - version: 2.4.5 + version: 2.4.5(prismarine-registry@1.11.0) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -432,13 +432,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3 + version: 1.2.3(prismarine-registry@1.11.0) process: specifier: ^0.11.10 version: 0.11.10 @@ -2019,8 +2019,8 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@nxg-org/mineflayer-auto-jump@0.7.12': - resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==} + '@nxg-org/mineflayer-auto-jump@0.7.17': + resolution: {integrity: sha512-AgIiJcyh9y8bZjwNHZ2Wp/mbC6+XfE8cM/nOLcZaQYLdoUgncUeaNinFTYLvwi1RMQ42S5bxvwI9kKUwjRhlDA==} '@nxg-org/mineflayer-physics-util@1.8.7': resolution: {integrity: sha512-wtLYvHqoEFr/j0ny2lyogwjbMvwpFuG2aWI8sI14+EAiGFRpL5+cog2ujSDsnRTZruO7tUXMTiPc1kebjXwfJg==} @@ -11368,7 +11368,7 @@ snapshots: rimraf: 3.0.2 optional: true - '@nxg-org/mineflayer-auto-jump@0.7.12': + '@nxg-org/mineflayer-auto-jump@0.7.17': dependencies: '@nxg-org/mineflayer-physics-util': 1.8.7 strict-event-emitter-types: 2.0.0 @@ -11390,7 +11390,7 @@ snapshots: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.83.1 mineflayer: 4.27.0(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-item: 1.16.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -17428,22 +17428,24 @@ snapshots: - tsx - yaml - mineflayer-pathfinder@2.4.5: + mineflayer-pathfinder@2.4.5(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 + transitivePeerDependencies: + - prismarine-registry mineflayer@4.27.0(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 @@ -17467,7 +17469,7 @@ snapshots: minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 @@ -18260,7 +18262,7 @@ snapshots: minecraft-data: 3.83.1 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.83.1 prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) @@ -18268,6 +18270,8 @@ snapshots: prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 + transitivePeerDependencies: + - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18278,7 +18282,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18312,7 +18316,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18336,16 +18340,18 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3: + prismarine-schematic@1.2.3(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 + transitivePeerDependencies: + - prismarine-registry prismarine-windows@2.9.0: dependencies: From f76c7fb782416e768f2a1610628c750c23ef4bb5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 19:51:42 +0300 Subject: [PATCH 07/62] fix: water now supports lighting when its enabled --- renderer/viewer/lib/mesher/models.ts | 38 ++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 46258134..802ecaf0 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -125,6 +125,13 @@ const isCube = (block: Block) => { })) } +const getVec = (v: Vec3, dir: Vec3) => { + for (const coord of ['x', 'y', 'z']) { + if (Math.abs(dir[coord]) > 0) v[coord] = 0 + } + return v.plus(dir) +} + function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record, isRealWater: boolean) { const heights: number[] = [] for (let z = -1; z <= 1; z++) { @@ -142,7 +149,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ // eslint-disable-next-line guard-for-in for (const face in elemFaces) { - const { dir, corners } = elemFaces[face] + const { dir, corners, mask1, mask2 } = elemFaces[face] const isUp = dir[1] === 1 const neighborPos = cursor.offset(...dir as [number, number, number]) @@ -180,6 +187,9 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ const { su } = texture const { sv } = texture + // Get base light value for the face + const baseLight = world.getLight(neighborPos, undefined, undefined, water ? 'water' : 'lava') / 15 + for (const pos of corners) { const height = cornerHeights[pos[2] * 2 + pos[0]] attr.t_positions.push( @@ -189,7 +199,31 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ ) attr.t_normals.push(...dir) attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v) - attr.t_colors.push(tint[0], tint[1], tint[2]) + + let cornerLightResult = baseLight + if (world.config.smoothLighting) { + const dx = pos[0] * 2 - 1 + const dy = pos[1] * 2 - 1 + const dz = pos[2] * 2 - 1 + const cornerDir: [number, number, number] = [dx, dy, dz] + const side1Dir: [number, number, number] = [dx * mask1[0], dy * mask1[1], dz * mask1[2]] + const side2Dir: [number, number, number] = [dx * mask2[0], dy * mask2[1], dz * mask2[2]] + + const dirVec = new Vec3(...dir as [number, number, number]) + + const side1LightDir = getVec(new Vec3(...side1Dir), dirVec) + const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15 + const side2DirLight = getVec(new Vec3(...side2Dir), dirVec) + const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15 + const cornerLightDir = getVec(new Vec3(...cornerDir), dirVec) + const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15 + // interpolate + const lights = [side1Light, side2Light, cornerLight, baseLight] + cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length + } + + // Apply light value to tint + attr.t_colors.push(tint[0] * cornerLightResult, tint[1] * cornerLightResult, tint[2] * cornerLightResult) } } } From 25f2fdef4e766a2d53bcbe24e95ce796f95d2e90 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 19:56:13 +0300 Subject: [PATCH 08/62] fix: dont display hand & cursor block outline in spectator. --- renderer/viewer/lib/basePlayerState.ts | 2 ++ renderer/viewer/three/worldrendererThree.ts | 2 +- src/mineflayer/playerState.ts | 22 ++++++++++++++++++++- src/mineflayer/plugins/mouse.ts | 6 +++++- src/react/ChunksDebugScreen.tsx | 2 +- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index df8cbc61..856772b1 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -31,6 +31,8 @@ export interface IPlayerState { getHeldItem?(isLeftHand: boolean): HandItemBlock | undefined username?: string onlineMode?: boolean + lightingDisabled?: boolean + shouldHideHand?: boolean events: TypedEmitter diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 088f0ee7..5c16aa9a 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -470,7 +470,7 @@ export class WorldRendererThree extends WorldRendererCommon { const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera this.renderer.render(this.scene, cam) - if (this.displayOptions.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) { + if (this.displayOptions.inWorldRenderingConfig.showHand && !this.playerState.shouldHideHand /* && !this.freeFlyMode */) { this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 85f0b00c..16739c86 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -24,6 +24,7 @@ export class PlayerStateManager implements IPlayerState { private itemUsageTicks = 0 private isUsingItem = false private ready = false + public lightingDisabled = false onlineMode = false get username () { return bot.username ?? '' @@ -51,6 +52,21 @@ export class PlayerStateManager implements IPlayerState { } private botCreated () { + const handleDimensionData = (data) => { + let hasSkyLight = 1 + try { + hasSkyLight = data.dimension.value.has_skylight.value + } catch {} + this.lightingDisabled = bot.game.dimension === 'the_nether' || bot.game.dimension === 'the_end' || !hasSkyLight + } + + bot._client.on('login', (packet) => { + handleDimensionData(packet) + }) + bot._client.on('respawn', (packet) => { + handleDimensionData(packet) + }) + // Movement tracking bot.on('move', this.updateState) @@ -75,6 +91,10 @@ export class PlayerStateManager implements IPlayerState { this.reactive.gameMode = bot.game?.gameMode } + get shouldHideHand () { + return this.reactive.gameMode === 'spectator' + } + // #region Movement and Physics State private updateState () { if (!bot?.entity || this.disableStateUpdates) return @@ -118,7 +138,7 @@ export class PlayerStateManager implements IPlayerState { } getEyeHeight (): number { - return bot.controlState.sneak ? 1.27 : 1.62 + return bot.controlState.sneak && !this.isFlying() ? 1.27 : 1.62 } isOnGround (): boolean { diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 4e82b770..fc1ce0fd 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -10,7 +10,7 @@ import { sendVideoInteraction, videoCursorInteraction } from '../../customChanne function cursorBlockDisplay (bot: Bot) { const updateCursorBlock = (data?: { block: Block }) => { - if (!data?.block) { + if (!data?.block || bot.game.gameMode === 'spectator') { playerState.reactive.lookingAtBlock = undefined return } @@ -27,6 +27,10 @@ function cursorBlockDisplay (bot: Bot) { } bot.on('highlightCursorBlock', updateCursorBlock) + bot.on('game', () => { + const block = bot.mouse.getCursorState().cursorBlock + updateCursorBlock(block ? { block } : undefined) + }) bot.on('blockBreakProgressStage', (block, stage) => { const mergedShape = bot.mouse.getMergedCursorShape(block) diff --git a/src/react/ChunksDebugScreen.tsx b/src/react/ChunksDebugScreen.tsx index 80e926d8..9f47d023 100644 --- a/src/react/ChunksDebugScreen.tsx +++ b/src/react/ChunksDebugScreen.tsx @@ -30,7 +30,7 @@ const Inner = () => { state, lines: [String(chunk?.loads.length ?? 0)], sidebarLines: [ - `loads: ${chunk.loads.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`, + `loads: ${chunk.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`, // `blockUpdates: ${chunk.blockUpdates}`, ], } From 674b6ab00daadcb1caefcffe93b30e17d0f83e81 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 21:23:04 +0300 Subject: [PATCH 09/62] feat: add chat scroll debug and use alternative ways of forcing scroll in DOM! --- src/optionsGuiScheme.tsx | 4 ++ src/optionsStorage.ts | 1 + src/react/Chat.tsx | 64 +++++++++++++++++++++++++++- src/react/ChatProvider.tsx | 3 +- src/react/hooks/useScrollBehavior.ts | 59 ++++++++++++++++++------- 5 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index b9c44b29..d4be46f6 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -605,6 +605,10 @@ export const guiOptionsScheme: { debugResponseTimeIndicator: { text: 'Debug Input Lag', }, + }, + { + debugChatScroll: { + }, } ], 'export-import': [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index bc58d21c..ab164454 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -72,6 +72,7 @@ const defaultOptions = { preventBackgroundTimeoutKick: false, preventSleep: false, debugContro: false, + debugChatScroll: false, chatVanillaRestrictions: true, debugResponseTimeIndicator: false, // antiAliasing: false, diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx index 130f25a0..c2e59b07 100644 --- a/src/react/Chat.tsx +++ b/src/react/Chat.tsx @@ -41,6 +41,7 @@ type Props = { inputDisabled?: string placeholder?: string chatVanillaRestrictions?: boolean + debugChatScroll?: boolean } export const chatInputValueGlobal = proxy({ @@ -69,7 +70,8 @@ export default ({ allowSelection, inputDisabled, placeholder, - chatVanillaRestrictions + chatVanillaRestrictions, + debugChatScroll }: Props) => { const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]')) const [isInputFocused, setIsInputFocused] = useState(false) @@ -86,7 +88,16 @@ export default ({ const chatHistoryPos = useRef(sendHistoryRef.current.length) const inputCurrentlyEnteredValue = useRef('') - const { scrollToBottom } = useScrollBehavior(chatMessages, { messages, opened }) + const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened }) + const [rightNowAtBottom, setRightNowAtBottom] = useState(false) + + useEffect(() => { + if (!debugChatScroll) return + const interval = setInterval(() => { + setRightNowAtBottom(isAtBottom()) + }, 50) + return () => clearInterval(interval) + }, [debugChatScroll]) const setSendHistory = (newHistory: string[]) => { sendHistoryRef.current = newHistory @@ -252,6 +263,55 @@ export default ({ }} > {opacity &&
+ {debugChatScroll && ( +
+
+
+
+
+
+ )} {messages.map((m) => ( ))} diff --git a/src/react/ChatProvider.tsx b/src/react/ChatProvider.tsx index 4acc9e79..5c499029 100644 --- a/src/react/ChatProvider.tsx +++ b/src/react/ChatProvider.tsx @@ -17,7 +17,7 @@ export default () => { const isChatActive = useIsModalActive('chat') const lastMessageId = useRef(0) const usingTouch = useSnapshot(miscUiState).currentTouch - const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions } = useSnapshot(options) + const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll } = useSnapshot(options) const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, []) const { forwardChat } = useSnapshot(viewerVersionState) const { viewerConnection } = useSnapshot(gameAdditionalState) @@ -48,6 +48,7 @@ export default () => { return { const openedWasAtBottom = useRef(true) // before new messages + const [currentlyAtBottom, setCurrentlyAtBottom] = useState(true) + const scrollTimeoutRef = useRef(null) const isAtBottom = () => { if (!elementRef.current) return true @@ -20,17 +22,30 @@ export const useScrollBehavior = ( return distanceFromBottom < 1 } - const scrollToBottom = () => { - if (elementRef.current) { - elementRef.current.scrollTop = elementRef.current.scrollHeight - setTimeout(() => { - if (!elementRef.current) return - elementRef.current.scrollTo({ - top: elementRef.current.scrollHeight, - behavior: 'instant' - }) - }, 0) + const scrollToBottom = (behavior: ScrollBehavior = 'instant') => { + if (!elementRef.current) return + + // Clear any existing scroll timeout + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) } + + const el = elementRef.current + + // Immediate scroll + el.scrollTop = el.scrollHeight + + // Double-check after a short delay to ensure we're really at the bottom + scrollTimeoutRef.current = setTimeout(() => { + if (!elementRef.current) return + const el = elementRef.current + el.scrollTo({ + top: el.scrollHeight, + behavior + }) + setCurrentlyAtBottom(true) + openedWasAtBottom.current = true + }, 5) } // Handle scroll position tracking @@ -39,18 +54,28 @@ export const useScrollBehavior = ( if (!element) return const handleScroll = () => { - openedWasAtBottom.current = isAtBottom() + const atBottom = isAtBottom() + openedWasAtBottom.current = atBottom + setCurrentlyAtBottom(atBottom) } element.addEventListener('scroll', handleScroll) - return () => element.removeEventListener('scroll', handleScroll) + return () => { + element.removeEventListener('scroll', handleScroll) + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + } + } }, []) // Handle opened state changes useLayoutEffect(() => { if (opened) { - openedWasAtBottom.current = true - } else { + // Wait a frame before scrolling to ensure DOM has updated + requestAnimationFrame(() => { + scrollToBottom() + }) + } else if (elementRef.current) { scrollToBottom() } }, [opened]) @@ -64,6 +89,8 @@ export const useScrollBehavior = ( return { scrollToBottom, - isAtBottom + isAtBottom, + wasAtBottom: () => openedWasAtBottom.current, + currentlyAtBottom } } From a58ff0776e8a3d63029c30f46669261be58274ca Mon Sep 17 00:00:00 2001 From: M G <118717627+mgDentist@users.noreply.github.com> Date: Mon, 12 May 2025 18:09:10 +0300 Subject: [PATCH 10/62] server name field "flex-start" now (#351) --- src/react/AddServerOrConnect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 19332472..08e4d69e 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -165,7 +165,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ placeholder={example} /> {!lockConnect && <> -
+
setServerName(value)} placeholder='Defaults to IP' />
} From e2400ee667a1bedd8796653838d9a56feae499d4 Mon Sep 17 00:00:00 2001 From: M G <118717627+mgDentist@users.noreply.github.com> Date: Mon, 12 May 2025 18:15:45 +0300 Subject: [PATCH 11/62] Entity Interaction Button works now (#352) --- src/react/TouchInteractionHint.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/TouchInteractionHint.module.css b/src/react/TouchInteractionHint.module.css index 73fae1fc..da11a65c 100644 --- a/src/react/TouchInteractionHint.module.css +++ b/src/react/TouchInteractionHint.module.css @@ -8,7 +8,7 @@ display: flex; align-items: center; gap: 8px; - pointer-events: none; + pointer-events: auto; z-index: 1000; text-shadow: 1px 1px 8px rgba(0, 0, 0, 1); } From 48cdd9484fe024ed224349d40fac909c06a6a606 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 12 May 2025 18:56:20 +0300 Subject: [PATCH 12/62] annoying contextmenu on windows on right click sometimes was appearing --- src/globalDomListeners.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/globalDomListeners.ts b/src/globalDomListeners.ts index 5055c600..bfce0d42 100644 --- a/src/globalDomListeners.ts +++ b/src/globalDomListeners.ts @@ -35,3 +35,12 @@ window.addEventListener('beforeunload', (event) => { event.returnValue = '' // Required for some browsers return 'The game is running. Are you sure you want to close this page?' }) + +window.addEventListener('contextmenu', (e) => { + const ALLOW_TAGS = ['INPUT', 'TEXTAREA', 'A'] + // allow if target is in ALLOW_TAGS or has selection text + if (ALLOW_TAGS.includes((e.target as HTMLElement)?.tagName) || window.getSelection()?.toString()) { + return + } + e.preventDefault() +}) From 489c16793be6619812cbe8b41779b4d3794a8591 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 14 May 2025 01:24:53 +0300 Subject: [PATCH 13/62] fix original name logging on error --- src/inventoryWindows.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 2906177b..21e3061f 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -189,7 +189,6 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal image?: HTMLImageElement } | undefined => { let itemModelName = model.modelName - const originalItemName = itemModelName const isItem = loadedData.itemsByName[itemModelName] // #region normalize item name @@ -225,7 +224,7 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal ?? (model.originalItemName ? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture(model.originalItemName, {}, false, fullBlockModelSupport) : undefined) ?? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture('item/missing_texture')! } catch (err) { - inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`) + inGameError(`Failed to render item ${itemModelName} (original: ${model.originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`) itemTexture = blockToTopTexture(appViewer.resourcesManager.currentResources!.itemsRenderer.getItemTexture('errored')!) } From 75adc29bf080f87236b21ac8bcf452c2f7b76f8f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 14 May 2025 03:07:02 +0300 Subject: [PATCH 14/62] up mc assets --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e1f2b6fc..2388d508 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "http-browserify": "^1.7.0", "http-server": "^14.1.1", "https-browserify": "^1.0.0", - "mc-assets": "^0.2.53", + "mc-assets": "^0.2.54", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", "mineflayer-mouse": "^0.1.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3c88f2a..7da635c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -334,8 +334,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 mc-assets: - specifier: ^0.2.53 - version: 0.2.53 + specifier: ^0.2.54 + version: 0.2.54 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1) @@ -6466,8 +6466,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mc-assets@0.2.53: - resolution: {integrity: sha512-Ucsu2pDLr/cs8bxbxU9KTszdf/vPTLphYgEHUEWxuYlMkPQUCpsQwkn3YgyykJ7RXaca7zZGlZXaTPXBAqJT6A==} + mc-assets@0.2.54: + resolution: {integrity: sha512-ZEaa9IcqfOt4cFGLVJVkZMemKPfbjQskvIxuDepkXWkJb9T+xQ+Hj86zDMh1Ah8WZWNeGx2x26CuXt8QNr6pcw==} engines: {node: '>=18.0.0'} mcraft-fun-mineflayer@0.1.23: @@ -17014,7 +17014,7 @@ snapshots: math-intrinsics@1.1.0: {} - mc-assets@0.2.53: + mc-assets@0.2.54: dependencies: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 From 7ed3413b28ac7915b17400cd5b7834ae1ef541dd Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 14 May 2025 03:12:55 +0300 Subject: [PATCH 15/62] fix save is undefined in rare cases --- src/flyingSquidUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/flyingSquidUtils.ts b/src/flyingSquidUtils.ts index 012830d9..2ae0be7c 100644 --- a/src/flyingSquidUtils.ts +++ b/src/flyingSquidUtils.ts @@ -18,9 +18,10 @@ export function nameToMcOfflineUUID (name) { } export async function savePlayers (autoSave: boolean) { + if (!localServer?.players[0]) return if (autoSave && new URL(location.href).searchParams.get('noSave') === 'true') return //@ts-expect-error TODO - await localServer!.savePlayersSingleplayer() + await localServer.savePlayersSingleplayer() } // todo flying squid should expose save function instead From 0a61bbba757c47c62f2b4d82ef54aaa213cdfc13 Mon Sep 17 00:00:00 2001 From: M G <118717627+mgDentist@users.noreply.github.com> Date: Wed, 14 May 2025 18:15:21 +0300 Subject: [PATCH 16/62] Fixed button shown even when cursor is not over the entity (e.g., a video) (#356) fix: button shown even when cursor is not over the entity (e.g., a video) --- src/react/TouchInteractionHint.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/react/TouchInteractionHint.tsx b/src/react/TouchInteractionHint.tsx index bf2b42f0..b3cbe268 100644 --- a/src/react/TouchInteractionHint.tsx +++ b/src/react/TouchInteractionHint.tsx @@ -57,6 +57,9 @@ export default () => { if (!usingTouch || touchInteractionType !== 'classic' || modalStack.length > 0) return null if (!hintText && !entityName) return null + // need to hide "Use" button if there isn't an entity name, but there is a hint text + if (!entityName) return null + return (
Date: Fri, 16 May 2025 20:19:23 +0700 Subject: [PATCH 17/62] feat: Add remote splash text loading via URLs --- config.json | 4 ++-- src/react/MainMenu.tsx | 20 ++++++++++++++++--- src/utils/splashText.ts | 43 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/utils/splashText.ts diff --git a/config.json b/config.json index ea27ca5a..0515aa40 100644 --- a/config.json +++ b/config.json @@ -26,7 +26,7 @@ } ], "rightSideText": "A Minecraft client clone in the browser!", - "splashText": "Gen is cooking!", + "splashText": "https://jsonplaceholder.typicode.com/posts/1", "pauseLinks": [ [ { @@ -37,4 +37,4 @@ } ] ] -} +} \ No newline at end of file diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index 7ed257c0..e706a3a8 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -1,9 +1,10 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { openURL } from 'renderer/viewer/lib/simpleUtils' import { useSnapshot } from 'valtio' import { haveDirectoryPicker } from '../utils' import { ConnectOptions } from '../connect' import { miscUiState } from '../globalState' +import { isRemoteSplashText, loadRemoteSplashText } from '../utils/splashText' import styles from './mainMenu.module.css' import Button from './Button' import ButtonWithTooltip from './ButtonWithTooltip' @@ -47,6 +48,19 @@ export default ({ singleplayerAvailable = true }: Props) => { const { appConfig } = useSnapshot(miscUiState) + const [splashText, setSplashText] = useState(appConfig?.splashText || '') + + useEffect(() => { + const loadSplashText = async () => { + if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { + const text = await loadRemoteSplashText(appConfig.splashText) + setSplashText(text) + } else { + setSplashText(appConfig?.splashText || '') + } + } + void loadSplashText() + }, [appConfig?.splashText]) if (!bottomRightLinks?.trim()) bottomRightLinks = undefined // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -75,7 +89,7 @@ export default ({ const connectToServerLongPress = useLongPress( () => { if (process.env.NODE_ENV === 'development') { - // Connect to :25565 + // Connect to :25565 const origin = window.location.hostname const connectOptions: ConnectOptions = { server: `${origin}:25565`, @@ -93,7 +107,7 @@ export default ({
- {appConfig?.splashText} + {splashText}
diff --git a/src/utils/splashText.ts b/src/utils/splashText.ts new file mode 100644 index 00000000..a46da218 --- /dev/null +++ b/src/utils/splashText.ts @@ -0,0 +1,43 @@ +const MAX_WORDS = 5 +const HTTPS_REGEX = /^https?:\/\// + +const limitWords = (text: string): string => { + const words = text.split(/\s+/) + if (words.length <= MAX_WORDS) { + return text + } + return words.slice(0, MAX_WORDS).join(' ') + '...' +} + +export const isRemoteSplashText = (text: string): boolean => { + return HTTPS_REGEX.test(text) +} + +export const loadRemoteSplashText = async (url: string): Promise => { + try { + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Failed to fetch splash text: ${response.statusText}`) + } + try { + const json = await response.json() + + if (typeof json === 'object' && json !== null) { + if (json.title) return limitWords(json.title) + if (json.text) return limitWords(json.text) + if (json.message) return limitWords(json.message) + + return limitWords(JSON.stringify(json)) + } + + return limitWords(String(json)) + } catch (jsonError) { + + const text = await response.text() + return limitWords(text.trim()) + } + } catch (error) { + console.error('Error loading remote splash text:', error) + return 'Failed to load splash text!' + } +} From 2b0f178fe080d9ee315fef7087a8e92e2c4adafd Mon Sep 17 00:00:00 2001 From: Maxim Grigorev Date: Fri, 16 May 2025 20:35:04 +0700 Subject: [PATCH 18/62] feat: improoved the code --- src/react/MainMenu.tsx | 15 ++++++++++----- src/utils/splashText.ts | 7 ++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index e706a3a8..86e2b1ea 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -52,11 +52,16 @@ export default ({ useEffect(() => { const loadSplashText = async () => { - if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { - const text = await loadRemoteSplashText(appConfig.splashText) - setSplashText(text) - } else { - setSplashText(appConfig?.splashText || '') + try { + if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { + const text = await loadRemoteSplashText(appConfig.splashText) + setSplashText(text) + } else { + setSplashText(appConfig?.splashText || '') + } + } catch (error) { + console.error('Failed to load splash text:', error) + setSplashText('Error loading splash text') } } void loadSplashText() diff --git a/src/utils/splashText.ts b/src/utils/splashText.ts index a46da218..f60b5296 100644 --- a/src/utils/splashText.ts +++ b/src/utils/splashText.ts @@ -1,5 +1,5 @@ const MAX_WORDS = 5 -const HTTPS_REGEX = /^https?:\/\// +const HTTPS_REGEX = /^https?:\/\/[-\w@:%.+~#=]{1,256}\.[a-zA-Z\d()]{1,6}\b([-\w()@:%+.~#?&/=]*)$/ const limitWords = (text: string): string => { const words = text.split(/\s+/) @@ -19,6 +19,8 @@ export const loadRemoteSplashText = async (url: string): Promise => { if (!response.ok) { throw new Error(`Failed to fetch splash text: ${response.statusText}`) } + + const clonedResponse = response.clone() try { const json = await response.json() @@ -32,8 +34,7 @@ export const loadRemoteSplashText = async (url: string): Promise => { return limitWords(String(json)) } catch (jsonError) { - - const text = await response.text() + const text = await clonedResponse.text() return limitWords(text.trim()) } } catch (error) { From f921275c876699500b8a73301623d3eec31a8f15 Mon Sep 17 00:00:00 2001 From: Maxim Grigorev Date: Fri, 16 May 2025 20:44:32 +0700 Subject: [PATCH 19/62] feat: improoved code safety --- src/utils/splashText.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/utils/splashText.ts b/src/utils/splashText.ts index f60b5296..9ebe157d 100644 --- a/src/utils/splashText.ts +++ b/src/utils/splashText.ts @@ -1,12 +1,17 @@ const MAX_WORDS = 5 const HTTPS_REGEX = /^https?:\/\/[-\w@:%.+~#=]{1,256}\.[a-zA-Z\d()]{1,6}\b([-\w()@:%+.~#?&/=]*)$/ +const TIMEOUT_MS = 5000 + +const sanitizeText = (text: string): string => { + return text.replaceAll(/<[^>]*>/g, '').replaceAll(/[^\w\s.,!?-]/g, '') +} const limitWords = (text: string): string => { const words = text.split(/\s+/) if (words.length <= MAX_WORDS) { - return text + return sanitizeText(text) } - return words.slice(0, MAX_WORDS).join(' ') + '...' + return sanitizeText(words.slice(0, MAX_WORDS).join(' ') + '...') } export const isRemoteSplashText = (text: string): boolean => { @@ -15,7 +20,10 @@ export const isRemoteSplashText = (text: string): boolean => { export const loadRemoteSplashText = async (url: string): Promise => { try { - const response = await fetch(url) + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS) + const response = await fetch(url, { signal: controller.signal }) + clearTimeout(timeoutId) if (!response.ok) { throw new Error(`Failed to fetch splash text: ${response.statusText}`) } From 051cc5b35c8687dc2f0a75f29ef7a4a1a93ffb88 Mon Sep 17 00:00:00 2001 From: Maxim Grigorev Date: Fri, 16 May 2025 22:47:59 +0700 Subject: [PATCH 20/62] fix: optimized splashText working process --- config.json | 1 + src/appConfig.ts | 1 + src/react/MainMenu.tsx | 46 +++++++++++++++++++++++++++------ src/utils/splashText.ts | 57 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/config.json b/config.json index 0515aa40..e45b4052 100644 --- a/config.json +++ b/config.json @@ -26,6 +26,7 @@ } ], "rightSideText": "A Minecraft client clone in the browser!", + "splashTextFallback": "GEN is cooking", "splashText": "https://jsonplaceholder.typicode.com/posts/1", "pauseLinks": [ [ diff --git a/src/appConfig.ts b/src/appConfig.ts index 156c5974..769ef14d 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -24,6 +24,7 @@ export type AppConfig = { // hideSettings?: Record allowAutoConnect?: boolean splashText?: string + splashTextFallback?: string pauseLinks?: Array>> keybindings?: Record defaultLanguage?: string diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index 86e2b1ea..9f5d5357 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -4,7 +4,15 @@ import { useSnapshot } from 'valtio' import { haveDirectoryPicker } from '../utils' import { ConnectOptions } from '../connect' import { miscUiState } from '../globalState' -import { isRemoteSplashText, loadRemoteSplashText } from '../utils/splashText' +import { + isRemoteSplashText, + loadRemoteSplashText, + getDisplayText, + cacheSplashText, + hasSourceUrlChanged, + cacheSourceUrl, + clearSplashCache +} from '../utils/splashText' import styles from './mainMenu.module.css' import Button from './Button' import ButtonWithTooltip from './ButtonWithTooltip' @@ -48,24 +56,46 @@ export default ({ singleplayerAvailable = true }: Props) => { const { appConfig } = useSnapshot(miscUiState) - const [splashText, setSplashText] = useState(appConfig?.splashText || '') + + useEffect(() => { + if (hasSourceUrlChanged(appConfig?.splashText)) { + clearSplashCache() + if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { + cacheSourceUrl(appConfig.splashText) + } + } + }, [appConfig?.splashText]) + + const initialSplashText = getDisplayText(appConfig?.splashText, appConfig?.splashTextFallback) + const [splashText, setSplashText] = useState(initialSplashText) + const [showSplash, setShowSplash] = useState(false) + + useEffect(() => { + const timer = setTimeout(() => { + setShowSplash(true) + }, 100) + return () => clearTimeout(timer) + }, []) useEffect(() => { const loadSplashText = async () => { try { if (appConfig?.splashText && isRemoteSplashText(appConfig.splashText)) { const text = await loadRemoteSplashText(appConfig.splashText) - setSplashText(text) - } else { - setSplashText(appConfig?.splashText || '') + + if (text && text.trim() !== '') { + setSplashText(text) + cacheSplashText(text) + } + } else if (appConfig?.splashText && !isRemoteSplashText(appConfig.splashText)) { + setSplashText(appConfig.splashText) } } catch (error) { console.error('Failed to load splash text:', error) - setSplashText('Error loading splash text') } } void loadSplashText() - }, [appConfig?.splashText]) + }, [appConfig?.splashText, appConfig?.splashTextFallback]) if (!bottomRightLinks?.trim()) bottomRightLinks = undefined // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -112,7 +142,7 @@ export default ({
- {splashText} + {showSplash && {splashText}}
diff --git a/src/utils/splashText.ts b/src/utils/splashText.ts index 9ebe157d..2332c22a 100644 --- a/src/utils/splashText.ts +++ b/src/utils/splashText.ts @@ -1,17 +1,15 @@ const MAX_WORDS = 5 const HTTPS_REGEX = /^https?:\/\/[-\w@:%.+~#=]{1,256}\.[a-zA-Z\d()]{1,6}\b([-\w()@:%+.~#?&/=]*)$/ const TIMEOUT_MS = 5000 - -const sanitizeText = (text: string): string => { - return text.replaceAll(/<[^>]*>/g, '').replaceAll(/[^\w\s.,!?-]/g, '') -} +const SPLASH_CACHE_KEY = 'minecraft_splash_text_cache' +const SPLASH_URL_KEY = 'minecraft_splash_url' const limitWords = (text: string): string => { const words = text.split(/\s+/) if (words.length <= MAX_WORDS) { - return sanitizeText(text) + return text } - return sanitizeText(words.slice(0, MAX_WORDS).join(' ') + '...') + return words.slice(0, MAX_WORDS).join(' ') + '...' } export const isRemoteSplashText = (text: string): boolean => { @@ -50,3 +48,50 @@ export const loadRemoteSplashText = async (url: string): Promise => { return 'Failed to load splash text!' } } + +export const cacheSourceUrl = (url: string): void => { + localStorage.setItem(SPLASH_URL_KEY, url) +} + +export const hasSourceUrlChanged = (newUrl?: string): boolean => { + const cachedUrl = localStorage.getItem(SPLASH_URL_KEY) + + if ((!cachedUrl && newUrl) || (cachedUrl && !newUrl)) { + return true + } + + return cachedUrl !== newUrl +} + +export const clearSplashCache = (): void => { + localStorage.removeItem(SPLASH_CACHE_KEY) +} + +export const getCachedSplashText = (): string | null => { + return localStorage.getItem(SPLASH_CACHE_KEY) +} + +export const cacheSplashText = (text: string): void => { + localStorage.setItem(SPLASH_CACHE_KEY, text) +} + +export const getDisplayText = (splashText?: string, fallbackText?: string): string => { + const cachedText = getCachedSplashText() + + if (cachedText) return cachedText + + if (fallbackText) return fallbackText + + if (splashText && isRemoteSplashText(splashText)) return '' + + return splashText || '' +} + +export const getDirectDisplayText = (splashText?: string, fallbackText?: string): string => { + + if (splashText && !isRemoteSplashText(splashText)) return splashText + + if (fallbackText) return fallbackText + + return '' +} From ff29fc1fc53acfce5c42ac9b25b1297c5bbabf36 Mon Sep 17 00:00:00 2001 From: Maksim Grigorev Date: Fri, 16 May 2025 19:50:38 +0300 Subject: [PATCH 21/62] feat: Credits modal (#354) --- src/react/CreditsAboutModal.module.css | 84 +++++++++++++++++++++ src/react/CreditsAboutModal.module.css.d.ts | 18 +++++ src/react/CreditsAboutModal.tsx | 57 ++++++++++++++ src/react/CreditsBookButton.module.css | 27 +++++++ src/react/CreditsBookButton.module.css.d.ts | 7 ++ src/react/CreditsBookButton.tsx | 22 ++++++ src/react/MainMenu.tsx | 6 +- src/reactUi.tsx | 3 +- 8 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 src/react/CreditsAboutModal.module.css create mode 100644 src/react/CreditsAboutModal.module.css.d.ts create mode 100644 src/react/CreditsAboutModal.tsx create mode 100644 src/react/CreditsBookButton.module.css create mode 100644 src/react/CreditsBookButton.module.css.d.ts create mode 100644 src/react/CreditsBookButton.tsx diff --git a/src/react/CreditsAboutModal.module.css b/src/react/CreditsAboutModal.module.css new file mode 100644 index 00000000..753d0448 --- /dev/null +++ b/src/react/CreditsAboutModal.module.css @@ -0,0 +1,84 @@ +.modalScreen { + margin-top: -15px; +} + +.container { + position: relative; + background-color: #eee0c3; + border: 5px solid #7A5C3E; + padding: 15px; + width: 80%; + margin: 0 auto; + color: #3F2A14; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + max-height: 70vh; + overflow-y: auto; +} + +.title { + text-align: center; + margin-top: 0; + margin-bottom: 0; + font-size: 10px; +} + +.contentWrapper { + margin-bottom: 5px; +} + +.subtitle { + font-size: 6px; + margin-bottom: 4px; + font-style: italic; +} + +.paragraph { + font-size: 6px; + margin-bottom: 4px; +} + +.list { + list-style-type: none; + padding: 0; + font-size: 6px; +} + +.listItem { + margin-bottom: 2px; +} + +.link { + color: #0000AA; + text-decoration: none; +} + +.sectionTitle { + margin-top: 7px; + margin-bottom: 5px; + font-size: 8px; +} + +.closeButton { + position: absolute; + top: 1px; + right: 1px; + display: flex; + justify-content: center; + cursor: pointer; + padding: 5px; + background: none; + border: none; + outline: none; +} + +.closeButton:hover { + opacity: 0.8; +} + +.closeButton:focus-visible { + outline: 1px dashed #7A5C3E; +} + +.closeIcon { + color: #3F2A14; +} \ No newline at end of file diff --git a/src/react/CreditsAboutModal.module.css.d.ts b/src/react/CreditsAboutModal.module.css.d.ts new file mode 100644 index 00000000..30ac3261 --- /dev/null +++ b/src/react/CreditsAboutModal.module.css.d.ts @@ -0,0 +1,18 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + closeButton: string; + closeIcon: string; + container: string; + contentWrapper: string; + link: string; + list: string; + listItem: string; + modalScreen: string; + paragraph: string; + sectionTitle: string; + subtitle: string; + title: string; +} +declare const cssExports: CssExports; +export default cssExports; diff --git a/src/react/CreditsAboutModal.tsx b/src/react/CreditsAboutModal.tsx new file mode 100644 index 00000000..24826cd3 --- /dev/null +++ b/src/react/CreditsAboutModal.tsx @@ -0,0 +1,57 @@ +import { hideCurrentModal } from '../globalState' +import { useIsModalActive } from './utilsApp' +import Screen from './Screen' +import PixelartIcon, { pixelartIcons } from './PixelartIcon' +import styles from './CreditsAboutModal.module.css' + +export default () => { + const isModalActive = useIsModalActive('credits-about') + + if (!isModalActive) return null + + return ( + +
+

Minecraft Open Source JS Edition

+ +
+ What if Minecraft was an online game? +

+ Hey! You are on the safest modern Minecraft clone rewritten in JavaScript. A huge amount of work has gone into this project to make it fast and complete, and many features would not be possible without these awesome people and projects: +

+
    +
  • - Everyone who provided awesome mods for the game
  • +
  • - [Gen] for rewriting the physics engine to be Grim-compliant
  • +
  • - [ViaVersion] for providing reliable sound id mappings
  • +
  • - [Bluemap] for providing block entity models like chest
  • +
  • - [Deepslate] for rendering 3d blocks in GUI (inventory)
  • +
  • - [skinview3d] for rendering skins & player geometry
  • +
  • - [Polymer] (c++ project) for providing fast & accurate server light implementation
  • +
+ +

Current contributors:

+
    +
  • - Maxim Grigorev - React UI & Core Developer Maintainer
  • +
  • - And many more community contributors!
  • +
+ + +
+
+
+ ) +} diff --git a/src/react/CreditsBookButton.module.css b/src/react/CreditsBookButton.module.css new file mode 100644 index 00000000..83af5d80 --- /dev/null +++ b/src/react/CreditsBookButton.module.css @@ -0,0 +1,27 @@ +.creditsButton { + position: absolute; + top: 1px; + right: -30px; + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + cursor: pointer; + color: white; + opacity: 1; + transition: opacity 0.2s ease; +} + +.creditsButton:hover { + opacity: 0.9; +} + +.creditsButton:focus:not(:hover) { + outline: 1px solid #fff; +} + +.creditsButton svg { + width: 15px; + height: 15px; +} diff --git a/src/react/CreditsBookButton.module.css.d.ts b/src/react/CreditsBookButton.module.css.d.ts new file mode 100644 index 00000000..72f94435 --- /dev/null +++ b/src/react/CreditsBookButton.module.css.d.ts @@ -0,0 +1,7 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + creditsButton: string; +} +declare const cssExports: CssExports; +export default cssExports; diff --git a/src/react/CreditsBookButton.tsx b/src/react/CreditsBookButton.tsx new file mode 100644 index 00000000..a3be740f --- /dev/null +++ b/src/react/CreditsBookButton.tsx @@ -0,0 +1,22 @@ +import { showModal } from '../globalState' +import styles from './CreditsBookButton.module.css' + +export default () => { + const handleClick = () => { + showModal({ reactType: 'credits-about' }) + } + + return ( + + ) +} diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index 7ed257c0..40751b72 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -10,6 +10,7 @@ import ButtonWithTooltip from './ButtonWithTooltip' import { pixelartIcons } from './PixelartIcon' import useLongPress from './useLongPress' import PauseLinkButtons from './PauseLinkButtons' +import CreditsBookButton from './CreditsBookButton' type Action = (e: React.MouseEvent) => void @@ -44,7 +45,7 @@ export default ({ versionTitle, onVersionStatusClick, bottomRightLinks, - singleplayerAvailable = true + singleplayerAvailable = true, }: Props) => { const { appConfig } = useSnapshot(miscUiState) @@ -75,7 +76,7 @@ export default ({ const connectToServerLongPress = useLongPress( () => { if (process.env.NODE_ENV === 'development') { - // Connect to :25565 + // Connect to :25565 const origin = window.location.hostname const connectOptions: ConnectOptions = { server: `${origin}:25565`, @@ -149,6 +150,7 @@ export default ({
+
diff --git a/src/reactUi.tsx b/src/reactUi.tsx index c91c37d6..4409c10a 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -61,6 +61,7 @@ import ControDebug from './react/ControDebug' import ChunksDebug from './react/ChunksDebug' import ChunksDebugScreen from './react/ChunksDebugScreen' import DebugResponseTimeIndicator from './react/debugs/DebugResponseTimeIndicator' +import CreditsAboutModal from './react/CreditsAboutModal' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -239,7 +240,7 @@ const App = () => { - + From 42f973e05728b028946dc2d8e1ceab97147ee049 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 17 May 2025 05:33:31 +0300 Subject: [PATCH 22/62] Update config.json --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index e45b4052..c1589ef9 100644 --- a/config.json +++ b/config.json @@ -26,7 +26,7 @@ } ], "rightSideText": "A Minecraft client clone in the browser!", - "splashTextFallback": "GEN is cooking", + "splashTextFallback": "Welcome!", "splashText": "https://jsonplaceholder.typicode.com/posts/1", "pauseLinks": [ [ From a9b94ec897607b66491a8c2a40be41c761340209 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 17 May 2025 06:58:33 +0300 Subject: [PATCH 23/62] fix: fix sneaking didnt work with water --- package.json | 2 +- pnpm-lock.yaml | 46 ++++++++++++++++++++-------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 2388d508..4d07ffe7 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ }, "pnpm": { "overrides": { - "@nxg-org/mineflayer-physics-util": "1.8.7", + "@nxg-org/mineflayer-physics-util": "1.8.8", "buffer": "^6.0.3", "vec3": "0.1.10", "three": "0.154.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7da635c6..150468f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@nxg-org/mineflayer-physics-util': 1.8.7 + '@nxg-org/mineflayer-physics-util': 1.8.8 buffer: ^6.0.3 vec3: 0.1.10 three: 0.154.0 @@ -347,7 +347,7 @@ importers: version: 0.1.10(@types/debug@4.1.12)(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) mineflayer-pathfinder: specifier: ^2.4.4 - version: 2.4.5(prismarine-registry@1.11.0) + version: 2.4.5 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -432,13 +432,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3(prismarine-registry@1.11.0) + version: 1.2.3 process: specifier: ^0.11.10 version: 0.11.10 @@ -2022,8 +2022,8 @@ packages: '@nxg-org/mineflayer-auto-jump@0.7.17': resolution: {integrity: sha512-AgIiJcyh9y8bZjwNHZ2Wp/mbC6+XfE8cM/nOLcZaQYLdoUgncUeaNinFTYLvwi1RMQ42S5bxvwI9kKUwjRhlDA==} - '@nxg-org/mineflayer-physics-util@1.8.7': - resolution: {integrity: sha512-wtLYvHqoEFr/j0ny2lyogwjbMvwpFuG2aWI8sI14+EAiGFRpL5+cog2ujSDsnRTZruO7tUXMTiPc1kebjXwfJg==} + '@nxg-org/mineflayer-physics-util@1.8.8': + resolution: {integrity: sha512-u7CiKCroGrxtqGpIVWKo40GsE7aLc2cH1lvCDBW+Y/V4h1SaCDXzYBOADUKMeOemFss90au/AOGtkahezaoF8A==} '@nxg-org/mineflayer-tracker@1.2.1': resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} @@ -11370,10 +11370,10 @@ snapshots: '@nxg-org/mineflayer-auto-jump@0.7.17': dependencies: - '@nxg-org/mineflayer-physics-util': 1.8.7 + '@nxg-org/mineflayer-physics-util': 1.8.8 strict-event-emitter-types: 2.0.0 - '@nxg-org/mineflayer-physics-util@1.8.7': + '@nxg-org/mineflayer-physics-util@1.8.8': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 @@ -11390,7 +11390,7 @@ snapshots: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.83.1 mineflayer: 4.27.0(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.16.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -17428,24 +17428,22 @@ snapshots: - tsx - yaml - mineflayer-pathfinder@2.4.5(prismarine-registry@1.11.0): + mineflayer-pathfinder@2.4.5: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 - transitivePeerDependencies: - - prismarine-registry mineflayer@4.27.0(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 @@ -17465,11 +17463,11 @@ snapshots: mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13): dependencies: - '@nxg-org/mineflayer-physics-util': 1.8.7 + '@nxg-org/mineflayer-physics-util': 1.8.8 minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 @@ -18262,7 +18260,7 @@ snapshots: minecraft-data: 3.83.1 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: minecraft-data: 3.83.1 prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) @@ -18270,8 +18268,6 @@ snapshots: prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - transitivePeerDependencies: - - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18282,7 +18278,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18316,7 +18312,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18340,18 +18336,16 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3(prismarine-registry@1.11.0): + prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 - transitivePeerDependencies: - - prismarine-registry prismarine-windows@2.9.0: dependencies: From 785ab490f2fe76bcdd760dac046baa014c09b529 Mon Sep 17 00:00:00 2001 From: gguio <109200692+gguio@users.noreply.github.com> Date: Sun, 18 May 2025 10:58:52 +0400 Subject: [PATCH 24/62] fix: restore minimal and full map (#348) Co-authored-by: gguio --- renderer/viewer/baseGraphicsBackend.ts | 3 +- renderer/viewer/lib/mesher/mesher.ts | 23 +++++++++++++ renderer/viewer/lib/mesher/models.ts | 6 ++-- renderer/viewer/lib/mesher/shared.ts | 11 +++++- renderer/viewer/lib/worldrendererCommon.ts | 33 ++++++++++++------ src/appViewer.ts | 5 +-- src/index.ts | 2 +- src/react/Minimap.tsx | 3 +- src/react/MinimapProvider.tsx | 39 +++++++++++----------- 9 files changed, 85 insertions(+), 40 deletions(-) diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts index 79607695..4724076a 100644 --- a/renderer/viewer/baseGraphicsBackend.ts +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -3,7 +3,8 @@ import { RendererReactiveState } from '../../src/appViewer' export const getDefaultRendererState = (): RendererReactiveState => { return { world: { - chunksLoaded: [], + chunksLoaded: new Set(), + heightmaps: new Map(), chunksTotalNumber: 0, allChunksLoaded: true, mesherWork: false, diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 21e2d8ef..66621f22 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -148,6 +148,29 @@ const handleMessage = data => { global.postMessage({ type: 'customBlockModel', chunkKey, customBlockModel }) break } + case 'getHeightmap': { + const heightmap = new Uint8Array(256) + + const blockPos = new Vec3(0, 0, 0) + for (let z = 0; z < 16; z++) { + for (let x = 0; x < 16; x++) { + const blockX = x + data.x + const blockZ = z + data.z + blockPos.x = blockX; blockPos.z = blockZ + blockPos.y = 256 + let block = world.getBlock(blockPos) + while (block?.name.includes('air')) { + blockPos.y -= 1 + block = world.getBlock(blockPos) + } + const index = z * 16 + x + heightmap[index] = block ? blockPos.y : 0 + } + } + postMessage({ type: 'heightmap', key: `${Math.floor(data.x / 16)},${Math.floor(data.z / 16)}`, heightmap }) + + break + } // No default } } diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 802ecaf0..3658d120 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -510,7 +510,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { heads: {}, signs: {}, // isFull: true, - highestBlocks: {}, + highestBlocks: new Map(), hadErrors: false, blocksCount: 0 } @@ -521,9 +521,9 @@ export function getSectionGeometry (sx, sy, sz, world: World) { for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) { let block = world.getBlock(cursor, blockProvider, attr)! if (!INVISIBLE_BLOCKS.has(block.name)) { - const highest = attr.highestBlocks[`${cursor.x},${cursor.z}`] + const highest = attr.highestBlocks.get(`${cursor.x},${cursor.z}`) if (!highest || highest.y < cursor.y) { - attr.highestBlocks[`${cursor.x},${cursor.z}`] = { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id } + attr.highestBlocks.set(`${cursor.x},${cursor.z}`, { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id }) } } if (INVISIBLE_BLOCKS.has(block.name)) continue diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index eb1346f4..16b89a71 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -40,12 +40,21 @@ export type MesherGeometryOutput = { heads: Record, signs: Record, // isFull: boolean - highestBlocks: Record + highestBlocks: Map hadErrors: boolean blocksCount: number customBlockModels?: CustomBlockModels } +export interface MesherMainEvents { + geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number }; + sectionFinished: { type: 'sectionFinished'; key: string; workerIndex: number; processTime?: number }; + blockStateModelInfo: { type: 'blockStateModelInfo'; info: Record }; + heightmap: { type: 'heightmap'; key: string; heightmap: Uint8Array }; +} + +export type MesherMainEvent = MesherMainEvents[keyof MesherMainEvents] + export type HighestBlockInfo = { y: number, stateId: number | undefined, biomeId: number | undefined } export type BlockStateModelInfo = { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 516e4264..ef82f7af 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -14,7 +14,7 @@ import { ResourcesManager } from '../../../src/resourcesManager' import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer' import { SoundSystem } from '../three/threeJsSound' import { buildCleanupDecorator } from './cleanupDecorator' -import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared' +import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared' import { chunkPos } from './simpleUtils' import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats' import { WorldDataEmitter } from './worldDataEmitter' @@ -85,6 +85,7 @@ export abstract class WorldRendererCommon dirty (pos: Vec3, value: boolean): void update (/* pos: Vec3, value: boolean */): void chunkFinished (key: string): void + heightmap (key: string, heightmap: Uint8Array): void }> customTexturesDataUrl = undefined as string | undefined workers: any[] = [] @@ -103,8 +104,8 @@ export abstract class WorldRendererCommon ONMESSAGE_TIME_LIMIT = 30 // ms handleResize = () => { } - highestBlocksByChunks = {} as Record - highestBlocksBySections = {} as Record + highestBlocksByChunks = new Map() + highestBlocksBySections = new Map() blockEntities = {} workersProcessAverageTime = 0 @@ -247,7 +248,7 @@ export abstract class WorldRendererCommon } async getHighestBlocks (chunkKey: string) { - return this.highestBlocksByChunks[chunkKey] + return this.highestBlocksByChunks.get(chunkKey) } updateCustomBlock (chunkKey: string, blockPos: string, model: string) { @@ -365,19 +366,20 @@ export abstract class WorldRendererCommon this.isProcessingQueue = false } - handleMessage (data) { + handleMessage (rawData: any) { + const data = rawData as MesherMainEvent if (!this.active) return this.mesherLogReader?.workerMessageReceived(data.type, data) if (data.type !== 'geometry' || !this.debugStopGeometryUpdate) { const start = performance.now() - this.handleWorkerMessage(data) + this.handleWorkerMessage(data as WorkerReceive) this.workerCustomHandleTime += performance.now() - start } if (data.type === 'geometry') { this.logWorkerWork(() => `-> ${data.workerIndex} geometry ${data.key} ${JSON.stringify({ dataSize: JSON.stringify(data).length })}`) this.geometryReceiveCount[data.workerIndex] ??= 0 this.geometryReceiveCount[data.workerIndex]++ - const geometry = data.geometry as MesherGeometryOutput + const { geometry } = data this.highestBlocksBySections[data.key] = geometry.highestBlocks const chunkCoords = data.key.split(',').map(Number) this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2]))) @@ -404,6 +406,7 @@ export abstract class WorldRendererCommon if (loaded) { // CHUNK FINISHED this.finishedChunks[chunkKey] = true + this.reactiveState.world.chunksLoaded.add(`${Math.floor(chunkCoords[0] / 16)},${Math.floor(chunkCoords[2] / 16)}`) this.renderUpdateEmitter.emit(`chunkFinished`, `${chunkCoords[0]},${chunkCoords[2]}`) this.checkAllFinished() // merge highest blocks by sections into highest blocks by chunks @@ -442,6 +445,10 @@ export abstract class WorldRendererCommon this.blockStateModelInfo.set(cacheKey, info) } } + + if (data.type === 'heightmap') { + appViewer.rendererState.world.heightmaps.set(data.key, new Uint8Array(data.heightmap)) + } } downloadMesherLog () { @@ -599,7 +606,7 @@ export abstract class WorldRendererCommon updateChunksStats () { const loadedChunks = Object.keys(this.finishedChunks) - this.displayOptions.nonReactiveState.world.chunksLoaded = loadedChunks + this.displayOptions.nonReactiveState.world.chunksLoaded = new Set(loadedChunks) this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength this.reactiveState.world.allChunksLoaded = this.allChunksFinished @@ -628,6 +635,11 @@ export abstract class WorldRendererCommon customBlockModels: customBlockModels || undefined }) } + this.workers[0].postMessage({ + type: 'getHeightmap', + x, + z, + }) this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`) this.mesherLogReader?.chunkReceived(x, z, chunk.length) for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) { @@ -664,9 +676,9 @@ export abstract class WorldRendererCommon for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) { this.setSectionDirty(new Vec3(x, y, z), false) delete this.finishedSections[`${x},${y},${z}`] - delete this.highestBlocksBySections[`${x},${y},${z}`] + this.highestBlocksBySections.delete(`${x},${y},${z}`) } - delete this.highestBlocksByChunks[`${x},${z}`] + this.highestBlocksByChunks.delete(`${x},${z}`) this.updateChunksStats() @@ -992,7 +1004,6 @@ export abstract class WorldRendererCommon this.active = false this.renderUpdateEmitter.removeAllListeners() - this.displayOptions.worldView.removeAllListeners() // todo this.abortController.abort() removeAllStats() } diff --git a/src/appViewer.ts b/src/appViewer.ts index ca62bd1b..90ca847f 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -17,7 +17,8 @@ import { watchOptionsAfterWorldViewInit } from './watchOptions' export interface RendererReactiveState { world: { - chunksLoaded: string[] + chunksLoaded: Set + heightmaps: Map chunksTotalNumber: number allChunksLoaded: boolean mesherWork: boolean @@ -28,7 +29,7 @@ export interface RendererReactiveState { } export interface NonReactiveState { world: { - chunksLoaded: string[] + chunksLoaded: Set chunksTotalNumber: number allChunksLoaded: boolean mesherWork: boolean diff --git a/src/index.ts b/src/index.ts index afa349fa..bea10726 100644 --- a/src/index.ts +++ b/src/index.ts @@ -708,7 +708,7 @@ export async function connect (connectOptions: ConnectOptions) { resolve() unsub() } else { - const perc = Math.round(appViewer.rendererState.world.chunksLoaded.length / appViewer.rendererState.world.chunksTotalNumber * 100) + const perc = Math.round(appViewer.rendererState.world.chunksLoaded.size / appViewer.rendererState.world.chunksTotalNumber * 100) progress?.reportProgress('chunks', perc / 100) } }) diff --git a/src/react/Minimap.tsx b/src/react/Minimap.tsx index 10c2b666..25ee59ed 100644 --- a/src/react/Minimap.tsx +++ b/src/react/Minimap.tsx @@ -85,6 +85,7 @@ export default ( top: '0px', padding: '5px 5px 0px 0px', textAlign: 'center', + zIndex: 7, }} onClick={() => { toggleFullMap?.() @@ -106,7 +107,7 @@ export default ( textShadow: '0.1em 0 black, 0 0.1em black, -0.1em 0 black, 0 -0.1em black, -0.1em -0.1em black, -0.1em 0.1em black, 0.1em -0.1em black, 0.1em 0.1em black' }} > - {position.x.toFixed(2)} {position.y.toFixed(2)} {position.z.toFixed(2)} + {Math.round(position.x)} {Math.round(position.y)} {Math.round(position.z)}
: null } diff --git a/src/react/MinimapProvider.tsx b/src/react/MinimapProvider.tsx index 6e7e01ff..afb6a198 100644 --- a/src/react/MinimapProvider.tsx +++ b/src/react/MinimapProvider.tsx @@ -10,7 +10,7 @@ import { Chunk } from 'prismarine-world/types/world' import { Block } from 'prismarine-block' import { INVISIBLE_BLOCKS } from 'renderer/viewer/lib/mesher/worldConstants' import { getRenamedData } from 'flying-squid/dist/blockRenames' -import { useSnapshot } from 'valtio' +import { useSnapshot, subscribe } from 'valtio' import { subscribeKey } from 'valtio/utils' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import BlockData from '../../renderer/viewer/lib/moreBlockDataGenerated.json' @@ -42,7 +42,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements yaw: number world: string warps: WorldWarp[] = gameAdditionalState.warps - chunksStore = new Map() + chunksStore = new Map() loadingChunksQueue = new Set() loadChunk: (key: string) => Promise = this.loadChunkMinimap mapDrawer = new MinimapDrawer(this.loadChunk, this.warps, this.loadingChunksQueue, this.chunksStore) @@ -119,9 +119,9 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements this.blockData.set(renamedKey, BlockData.colors[blockKey]) } - subscribeKey(appViewer.rendererState, 'world', () => { + subscribe(appViewer.rendererState.world, () => { for (const key of this.loadingChunksQueue) { - if (appViewer.rendererState.world.chunksLoaded.includes(key)) { + if (appViewer.rendererState.world.chunksLoaded.has(key)) { this.loadingChunksQueue.delete(key) void this.loadChunk(key) } @@ -205,33 +205,31 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements const [chunkX, chunkZ] = key.split(',').map(Number) const chunkWorldX = chunkX * 16 const chunkWorldZ = chunkZ * 16 - if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) { - const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`) - if (!highestBlocks) return undefined - const heightmap = new Uint8Array(256) + if (appViewer.rendererState.world.chunksLoaded.has(key)) { + // console.log('[MinimapProvider] loading chunk for minimap', key) + const heightmap = appViewer.rendererState.world.heightmaps.get(key) + if (heightmap) { + // console.log('[MinimapProvider] did get highest blocks') + } else { + console.warn('[MinimapProvider] no highestBlocks from renderMethods') + return undefined + } const colors = Array.from({ length: 256 }).fill('') as string[] // avoid creating new object every time const blockPos = new Vec3(0, 0, 0) - // filling up colors and heightmap + // filling up colors for (let z = 0; z < 16; z += 1) { for (let x = 0; x < 16; x += 1) { const blockX = chunkWorldX + x const blockZ = chunkWorldZ + z - const hBlock = highestBlocks[`${blockX},${blockZ}`] - blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = hBlock?.y ?? 0 - let block = bot.world.getBlock(blockPos) - while (block?.name.includes('air')) { - blockPos.y -= 1 - block = bot.world.getBlock(blockPos) - } const index = z * 16 + x + blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = heightmap[index] + const block = bot.world.getBlock(blockPos) // blocks which are not set are shown as half transparent - if (!block || !hBlock) { - heightmap[index] = 0 + if (!block) { colors[index] = 'rgba(0, 0, 0, 0.5)' continue } - heightmap[index] = block.position.y colors[index] = this.setColor(block) } } @@ -242,6 +240,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements } else { this.loadingChunksQueue.add(`${chunkX},${chunkZ}`) this.chunksStore.set(key, 'requested') + // console.log('[MinimapProvider] requested new chunk', key) } } @@ -339,7 +338,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements const chunkWorldX = chunkX * 16 const chunkWorldZ = chunkZ * 16 const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`) - if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) { + if (appViewer.rendererState.world.chunksLoaded.has(`${chunkWorldX},${chunkWorldZ}`)) { const heightmap = new Uint8Array(256) const colors = Array.from({ length: 256 }).fill('') as string[] if (!highestBlocks) return null From a5d16a75ef1727164568627e5632b9593d2c714e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 19 May 2025 00:16:18 +0300 Subject: [PATCH 25/62] fix(regression): hotbar on mobile was broken --- src/react/HotbarRenderApp.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 039f283f..6e91180d 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -80,6 +80,9 @@ const HotbarInner = () => { const controller = new AbortController() const inv = openItemsCanvas('HotbarWin', { + _client: { + write () {} + }, clickWindow (slot, mouseButton, mode) { if (mouseButton === 1) { console.log('right click') From 2dc811b2a14961b029872e5f56e56af0ca4442da Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 19 May 2025 00:23:20 +0300 Subject: [PATCH 26/62] adjust settings visually --- README.MD | 1 + src/optionsGuiScheme.tsx | 31 ++++++++++++++++--------------- src/react/DeathScreen.tsx | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/README.MD b/README.MD index aa36f7e8..4769192a 100644 --- a/README.MD +++ b/README.MD @@ -34,6 +34,7 @@ All components that are in [Storybook](https://mcraft.fun/storybook) are publish - Controls -> **Touch Controls Type** -> **Joystick** - Controls -> **Auto Full Screen** -> **On** - To avoid ctrl+w issue +- Interface -> **Enable Minimap** -> **Always** - To enable useful minimap (why not?) - Controls -> **Raw Input** -> **On** - This will make the controls more precise (UPD: already enabled by default) - Interface -> **Chat Select** -> **On** - To select chat messages (UPD: already enabled by default) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index d4be46f6..ede367f5 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -90,8 +90,7 @@ export const guiOptionsScheme: { }, lowMemoryMode: { text: 'Low Memory Mode', - enableWarning: 'Enabling it will make chunks load ~4x slower', - disabledDuringGame: true + enableWarning: 'Enabling it will make chunks load ~4x slower. When in the game, app needs to be reloaded to apply this setting.', }, starfieldRendering: {}, renderEntities: {}, @@ -286,6 +285,20 @@ export const guiOptionsScheme: { chatSelect: { }, }, + { + custom () { + return Map + }, + showMinimap: { + text: 'Enable Minimap', + enableWarning: 'App reload is required to apply this setting', + values: [ + 'always', + 'singleplayer', + 'never' + ], + }, + }, { custom () { return World @@ -321,19 +334,6 @@ export const guiOptionsScheme: { ], }, }, - { - custom () { - return Map - }, - showMinimap: { - text: 'Enable Minimap', - values: [ - 'always', - 'singleplayer', - 'never' - ], - }, - }, { custom () { return Experimental @@ -555,6 +555,7 @@ export const guiOptionsScheme: { { preventBackgroundTimeoutKick: {}, preventSleep: { + text: 'Prevent Device Sleep', disabledReason: navigator.wakeLock ? undefined : 'Your browser does not support wake lock API', enableWarning: 'When connected to a server, prevent PC from sleeping or screen dimming. Useful for purpusely staying AFK for long time. Some events might still prevent this like loosing tab focus or going low power mode.', }, diff --git a/src/react/DeathScreen.tsx b/src/react/DeathScreen.tsx index 8f4c3f00..3501368f 100644 --- a/src/react/DeathScreen.tsx +++ b/src/react/DeathScreen.tsx @@ -24,7 +24,7 @@ export default ({ dieReasonMessage, respawnCallback, disconnectCallback }: Props }} />