From daf47097e2cf7c7fc28a1a86bf5465b05fac1297 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 16 Mar 2026 00:51:46 +0300 Subject: [PATCH 01/42] inv start! --- package.json | 1 + pnpm-lock.yaml | 41 ++++++++++++++++++++++++------- src/react/inventory/Inventory.tsx | 37 ++++++++++++++++++++++++++++ src/reactUi.tsx | 2 ++ 4 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 src/react/inventory/Inventory.tsx diff --git a/package.json b/package.json index 395f6961b..38d7887b4 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.103.0", + "minecraft-inventory": "^0.1.2", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e6d8a794..484007205 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,10 +140,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)) minecraft-data: specifier: 3.103.0 version: 3.103.0 + minecraft-inventory: + specifier: ^0.1.2 + version: 0.1.2(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -345,7 +348,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.25 version: 0.1.25 @@ -6686,6 +6689,18 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db} version: 1.0.1 + minecraft-inventory@0.1.2: + resolution: {integrity: sha512-MybWXwm6iEWDbemfPvoQjUEz3v3ALUIAGLTjw6JoKVTCYhr24nsalN/aaZJSkceYW4hCiwSbiIb1fgONWC4T6w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + '@xmcl/text-component': '*' + mc-assets: '*' + react: ^18.2.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + mc-assets: + optional: true + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd} version: 1.64.0 @@ -6706,8 +6721,8 @@ packages: resolution: {integrity: sha512-6QyPT7b7ETNmCy5A34eQ5nSLkBWMNX7c4UWFo7VOU8igCzGag9oPKRlY7GGjZzmTdhe+bXGfA1qb63CaZnML1w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67} version: 8.0.0 engines: {node: '>=22'} @@ -11364,7 +11379,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.103.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -17026,12 +17041,12 @@ snapshots: dependencies: minecraft-data: 3.103.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17349,6 +17364,14 @@ snapshots: - '@types/react' - react + minecraft-inventory@0.1.2(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@xmcl/text-component': 2.1.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + mc-assets: 0.2.72 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 @@ -17401,7 +17424,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17416,7 +17439,7 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/125bfddc24a73c9846b85e26f5f756045a8874f6(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 minecraft-data: 3.103.0 diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx new file mode 100644 index 000000000..a8c66321f --- /dev/null +++ b/src/react/inventory/Inventory.tsx @@ -0,0 +1,37 @@ +import { TextureProvider, ScaleProvider, InventoryProvider, InventoryOverlay, createMineflayerConnector, MineflayerBot, InventoryOverlayProps } from 'minecraft-inventory/src' +import { useMemo } from 'react' +import { proxy, useSnapshot } from 'valtio' +import { useAppScale } from '../../scaleInterface' + +export const openInventoryProxy = proxy({ + inventory: undefined as InventoryOverlayProps | undefined, +}) + +export const Inventory = () => { + const windowType = 'player' + const connector = useMemo(() => createMineflayerConnector(bot as MineflayerBot), [bot]) + const appScale = useAppScale() + const { inventory } = useSnapshot(openInventoryProxy) as typeof openInventoryProxy + + if (!inventory) return null + + return
+ + + + + + + +
+} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 42514b493..e1f7606f4 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -75,6 +75,7 @@ import AllSettingsEditor from './react/AllSettingsEditor' import { isPlayground, urlParams } from './playgroundIntegration' import { withInjectableUi } from './react/extendableSystem' import { hadReactUiRegistered } from './clientMods' +import { Inventory } from './react/inventory/Inventory' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -204,6 +205,7 @@ const InGameUi = () => { + {displayFullmap && } From c105fd3b3007eedee86a87ddb7ea4fe99f448b26 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 18 Mar 2026 07:45:16 +0300 Subject: [PATCH 02/42] local config, make test --- package.json | 2 +- pnpm-lock.yaml | 10 ++--- src/inventoryWindows.ts | 74 +++++++++++++++++-------------- src/react/inventory/Inventory.tsx | 5 ++- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 38d7887b4..4a9e12f15 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.103.0", - "minecraft-inventory": "^0.1.2", + "minecraft-inventory": "^0.1.4", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 484007205..b983fde19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.103.0 version: 3.103.0 minecraft-inventory: - specifier: ^0.1.2 - version: 0.1.2(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.4 + version: 0.1.4(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -6689,8 +6689,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db} version: 1.0.1 - minecraft-inventory@0.1.2: - resolution: {integrity: sha512-MybWXwm6iEWDbemfPvoQjUEz3v3ALUIAGLTjw6JoKVTCYhr24nsalN/aaZJSkceYW4hCiwSbiIb1fgONWC4T6w==} + minecraft-inventory@0.1.4: + resolution: {integrity: sha512-XjkXo0wSxVVdX4gDJ+gNshJoxy8650gJRu4kEmdb22kVBOwSi7eEfmP18dpM0ZBtn6yRZyHs/0Ic2JE8cZC4mg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -17364,7 +17364,7 @@ snapshots: - '@types/react' - react - minecraft-inventory@0.1.2(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.4(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 51db10ec2..0dc2da887 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -25,6 +25,7 @@ import { MessageFormatPart } from './chatUtils' import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' import { playerState } from './mineflayer/playerState' import { modelViewerState } from './react/OverlayModelViewer' +import { openInventoryProxy } from './react/inventory/Inventory' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -45,38 +46,42 @@ export const jeiCustomCategories = proxy({ let remotePlayerSkin: string | undefined | Promise export const showInventoryPlayer = () => { - modelViewerState.model = { - positioning: { - windowWidth: 176, - windowHeight: 166, - x: 25, - y: 8, - width: 50, - height: 70, - scaled: true, - onlyInitialScale: true, - }, - followCursor: true, - followCursorCenter: { - x: 51, - y: 27, - }, - // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], - // debug: true, - steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), - } - if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { - remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { - setTimeout(() => { - // Check if player inventory is still open before updating - if (lastWindowType === null) { - showInventoryPlayer() - } - }, 0) // todo patch instead and make reactive - remotePlayerSkin = a ?? '' - return remotePlayerSkin - }) - } + // openInventoryProxy.inventory = { + // type: 'player', + // } + + // modelViewerState.model = { + // positioning: { + // windowWidth: 176, + // windowHeight: 166, + // x: 25, + // y: 8, + // width: 50, + // height: 70, + // scaled: true, + // onlyInitialScale: true, + // }, + // followCursor: true, + // followCursorCenter: { + // x: 51, + // y: 27, + // }, + // // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], + // // debug: true, + // steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), + // } + // if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { + // remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { + // setTimeout(() => { + // // Check if player inventory is still open before updating + // if (lastWindowType === null) { + // showInventoryPlayer() + // } + // }, 0) // todo patch instead and make reactive + // remotePlayerSkin = a ?? '' + // return remotePlayerSkin + // }) + // } } export const onGameLoad = () => { @@ -577,7 +582,10 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = let destroyFn = () => { } export const openPlayerInventory = () => { - openWindow(undefined) + openInventoryProxy.inventory = { + type: 'player', + } + // openWindow(undefined) } const getResultingRecipe = (slots: Array, gridRows: number) => { diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index a8c66321f..75edd1011 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -1,6 +1,7 @@ import { TextureProvider, ScaleProvider, InventoryProvider, InventoryOverlay, createMineflayerConnector, MineflayerBot, InventoryOverlayProps } from 'minecraft-inventory/src' import { useMemo } from 'react' import { proxy, useSnapshot } from 'valtio' +import { localTexturesConfig } from 'minecraft-inventory/src/generated/localTextures' import { useAppScale } from '../../scaleInterface' export const openInventoryProxy = proxy({ @@ -21,13 +22,13 @@ export const Inventory = () => { width: '100%', height: '100dvh', zIndex: 1000, - // backgroundColor: 'rgba(0, 0, 0, 0.5)', }}> - + From c8da2a1c16b2261346dbc7b4dcd9f470548fe3fb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 30 Mar 2026 03:34:55 +0300 Subject: [PATCH 03/42] final changes by me for now --- assets/generic_91.png | Bin 2468 -> 0 bytes assets/generic_92.png | Bin 2504 -> 0 bytes assets/generic_93.png | Bin 2524 -> 0 bytes assets/generic_94.png | Bin 2536 -> 0 bytes assets/generic_95.png | Bin 2564 -> 0 bytes package.json | 3 +- pnpm-lock.yaml | 24 +- src/inventoryWindows.ts | 772 +++++------------- src/react/HotbarRenderApp.tsx | 4 +- src/react/inventory/Inventory.tsx | 261 +++++- .../inventory/inventoryTexturesConfig.ts | 8 + src/resourcePack.ts | 15 + src/resourcesSource.ts | 44 - 13 files changed, 468 insertions(+), 663 deletions(-) delete mode 100644 assets/generic_91.png delete mode 100644 assets/generic_92.png delete mode 100644 assets/generic_93.png delete mode 100644 assets/generic_94.png delete mode 100644 assets/generic_95.png create mode 100644 src/react/inventory/inventoryTexturesConfig.ts diff --git a/assets/generic_91.png b/assets/generic_91.png deleted file mode 100644 index 99e4d04ae8581563228e4cd505d390adcc9fe152..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2468 zcmbVO4Qvx-82&moG7yM_8K;nWIiiD%>;1Id?Jg?}+KtUn7^C4QXyAJHwLR9}9d|d{ zZ6XDXM590=(E!W*K_jAI5F%5Afkvib3MTwaG8_p8Fk#suNf)Ate%E#tq#I;Sn)~j% z=X>7geV_N<_O<%O3nx!}dLjT|a$T*r0RS6X+CbrWba_GFA4Hc4TJ0(WfXAj;UmMud zRt!MFkFtNIxze|Y7uBd;kkkOQ$DL@QD_QSJQ`69KJLa-ygXW4+a!*qAZDW*pKk@k zR{H8OkE%nAwKIfBQ542S zCew{0sp%Y|T9#HZGIc@~BjbWb(ss(qDFu|oEKY0EBdOq$NWut=Ld7%?md;|euxhGC zSRH{n`}n>9sJ1>|Hbzb@(P%b=VODQIX=DnL6K(jLHAprBL-Q2P({vf-;3+B% z^{J8^Y|cWR1jP_^xu16OE}D0`?t`M9kpxqCC|DBtpsGg&l&~BXLXgyy5RPTb$a~a? zs-wV2og<~f=i}=X!xR(|)_L7H%GoZ<5)Y+7Am~E!%PSl#!E#QP5LkymI0cD?oP!aW zU|{&XR~4HqXRY(;E|*jhar_sR3kWPtSI`8*1!*Gaas~;3VnqT%j-zP`u?f;J+Y((y zFPad!m(@}wA;utN0~HR@ML3}(5{$!16N1QbM8H)pxq_@1U}(nb=2V05wX%V%YtFnM z%i#LVDk5X4p5O)1>UB3RS|&rJAiXW;sK+Bk&YX{hAtD{c{B#&Y4Vp1Qhx0?Ib`NbX zawPGFuwiuRk7i|BQ&##&%{Uh6nX-ssK?y-LIZ50~hO|aZ+7$Btex;t>GmeJgXvSH~ z>~uvp+4&3=M6ILgo2;qn2VmUhI`4dc{7V1TrR%m<6;&<$>A-^CS6BUzI9u4UQ<}c- z>=!<)Cwb%?HKl#rz|h7?e^ig#-M;5yV9~QjUJk$Weg2K3?fbr?n!5W62RD5@^vt{q zfj!Sn?V&(t_nk=hq0o%38!abZKXu6#FR^RaNQN{!6eV^w#Py$(J=3muPM0Q54tN}swz`wA3|)I7N3pZz zPdl-Epif+Qrlj?Q)>%b~uIsG6<0oaQ@*K6L}+L_n!$(a&1@9vb*R9Fm&jRmKCu4Kp5Tc>QdzCN`3CRd4)dFq?I zl<$Lg!H2uPearZ=tNnb*#JRSPgO^H<<+YA#ua(={H8ySaLAO#cR|CDv8+WYUd?HO) z5lg1uXhG_mnM?>R~3^Ftnchw^X}@>!&l3y{;Kqg>xwu1J-uM< ws<&@+9X;b`K0i0-WPbjGEj-3 z!W@c^NpvI{2?Pz8Xhaeo>LL+wixY;A_=q7aOhHA4CPTLn1__DyzqYFzwv2d5bKl+n z_x--#_qeZJHP!3#au?b3&C*r~u5(*MA1EcV7Vj z*PUzenxTeyV3QPd;D4Ry#W6{A;B^)k2@wL*_zS%EPW!;qAPq*#KoaSX#TES9`*6iyMnT&;6$G8lSt z;66ZIhmB#AQC6Brnq^s%Vn~J|5Q0$KgBl+pf@<*ugA=NPB84Es=GY|$y%t&t%?9E+K|j*J)00$u0URs z6}K$=CpuL#AsJ&#Ml-gwP6~>0SY0;B0XlgNI&joA6G2%CnsqZYN6{QjFQ-f#MJ1pv zS(Lo(DX5vC7=pICX)|Y`Inz`qni-MT_-VnSzhYK<-b^qih9(5Y3<*}WPy|i$EGv4ff)&~)*fuB< z`p|g)WL903h!{nyjiK!nBm^rX5+>eE6RekJ36qDidaZU3Z?g0HG{*;otCUpKy7uJv zu@SZ;Ex&}tXM*Deebybgpf?$cc=ENBrXKecIq4tvK}33&<|m@7vR4c93M^|vwVQUh z$V}o@zU}VP@2!;yO)2S_hVlPMPnJdS@xdmDHYbVe$&mVrNi>DLvtRMEN$Y3=j&_`G zrnW13NbP4BMAQn}zG1cabpW#7u5y;SBj>OF_bXTn@B8JU`9t<& zE5i9Xf1Z?22sLB38aH1ldw(^3R6Y2}2lVVcm&;dvyM)?(nT-|<7hHRK=FJmd-RfTa z%CBRQ7yDi^+#E2wPLD*DZ_ZwA%irFUHM;e9UstaI%m6t61D)Vbqpu}vS8eWwWv7Z_ zYhr_=g{9|O5BzcKLQ(9q*l2DBpEyf`aOaKN!n)H%vA1G_OTi`kp;VM#|A3)=g;0B7}~P;olz8COioANm8tX7+1xXJ zJa^>bG@VKj>PF|b?7p5SV-49QEui60&%ma}BZ)C7{F063ET6sq@A2rlXi0-GzG~}m zVd=J$n;m)gU+U=XE&1rp&!b1c^=k(fsyoI8dTYUO`{9oDfv?MkukSh4-8pr#^r6Q< kks3trv#!oj|wGXPyx71hqeYZ{;V2cbLEbpQYW diff --git a/assets/generic_93.png b/assets/generic_93.png deleted file mode 100644 index 1b35f671d1dc6e621e204386105e02b00d467f95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2524 zcmbVNYfKbZ6uy91r6r+N`%sG0u|GsRJCEJj$AqmQOM%8k3MiV=l-ZfPFuFT4&J66r zrgoJ^Q(7O1O-z+)AF-uAP=B;d(~4T6*wm&!j7q2pM6D(j>$4DBAHB1?Y|#K3C)v!I zx#xV}cOG;0{qnNn%#7(70D#QW5>EvHDacF#lP02Djn?i*x5;YBQXPONr&Mg*gNega(g=g8^CR!frf<%OTr*P2yP0#aQmf3rs?6 ziMJdpR5XY=EDRw~6ooll7S?WuK7oA&V|PQMp15 z@`j>SDN10tQ{}^wF~(}KVY90xSyV!L?uY>B;SK1lWhZD?6+?3r&C&E+%F0nx z+|{dylD{eGY9lCypzT$(jbmw!q3&}kgtQJD%asyvW#4z z5>PZmjMQ0UD!g8x$n9@fs|sL$!P8 zaFJt)*ZIcrr9WCLDv#~Q}Rk)9}vP|wSC5N%EpH<`9%NU>Z;a*DIgV00vL#b2aQ8bX&Wmt7Umww ziOi4mT$_>KH}u&HYYyi`eu`Yp7$YWz5(nJ%+dZN9XintwNKY;}VPk{8ukmLWM+PQe z=DVxG=4Asbo<63GLVLfE?76^lvgaILek4Wx`e@E`tLD6$9ye}J98ZIp(xLsS{T1un zmefVralZDm;Cx%xiH1+ygYOOYJ3zkqCp!UTjbdbh4P%^)6ZEj7vB*ZmMJv;>9aFbe zw3-t4opVGxuM}FJYC%s!{nJKOnh1`kv3UEv?)4wK2R%dkD#4f1vfghqe|4@O?3vQr zwSI5Bk{kQR&N_kRTnuV~w;+PO~@i z)^_Ja@RsU$jXP&XKdK17o*P%x2WobG)4iy6@1u-C$I0Gvb5t}2drsw4uR4$#8`pu^ z7YAMnWJiB_`$}lf=`Qxdv93^ON{#&8)~^mrc{v+~E}q)d^VhWg>&@*)+xXkpl&Mj; xbjwVgzVPymyT{KK=Z`3TViq2=^U=Mx(w^yDz2kh=^xh*$)V&wLKQM=3nVA>4l=K|Yy z%mQH2X*JSfw}hXSk)HBEMUP@nCS?#b02P%P1EN-JQ&AjOwIF@vSU*jvN|0_A!(7;? z#tF5y)5J}k^%2zBiX?@ue3Gij$Rt1t+mOnnlA0xFf^;^nOl;>dOH)~h-5R8;973uk z+(1?9CZ+-&fk7O{Q31&#`u#YH#6^_A2?EQBEGIEsKo$g9kf{7c6FO6g$&I0>^JL@{ zq!YGn$Sj*qr#)$}M>pdvFG&*139KM61i@Gxnhi6IX3Zao5W*HRRl`BD|!X$eizFW60GehPG}- zbUiuJsfH2Bl;HLFsK=XCP0`cVf?FJL2--MEldgFg&d=~tMBrtPmwA36=ao4w2Mz0r z8tW)PeGDfsyg$PGWRa6aX%v*qi~?T_{MCoIR?l$WZhm=qrbeQ0^^fqJAIGKtYfgpM(N_@=E{$Y)Y<-CZ>G2?@=7!yD~iGdJ_jNi|pfD+{-ACg9*Ej3m0p~2*> zQ5{u^qskAVC`J7YRuE>qqA%vC3NQgLEEkm+6az?cra3zxa;<8S)^+5+k0!h-ZzWYK zI}9Iq#oNU=nnK=I7WgJ!Yq&iL2tIc6S^uc0BPGY`=r_ zyJ}@lQ$hN8!}wpM=gWc;P>W--Ia%6ChILj8I^Q>%8x6 zJ+ZlJZ-2?k7p9&5bK~0P^RdITtT&cb9y$|sTQBb3vjLvvyK1`sO5Ys5`suQY-Akum z;6T^bOW$p+UV8S(ksq(n2im|(B{O@jKME+&4S*X=xy{%S01NgGZEn1L@UcT1=Jvb( zcxB!;IE$S6?%zM2j}s*q@W74X%65gIk8arM=E6;1zn@B+Ty^Nwt}n~lU!G*VbFysa z+TqD#(2f%F$wBu(<7+{W`?)z_%gW2EruY2ToHIVxC(@8jY0g66`R2kP-EhK{<9nj( zUWi|}w4=S#zNk2N?ZS+U>nEh6K$&Zl?r}ylJKD)~sCJ3DPd zH^JMRk2gJYFgp>$lLwyP7wmPfo&<_ODOl|M{~6mDXmJ_)zAbBByYQjz*2C96oO=1; ztiy^LDP95x!SXd7{jDFB_3|}kAOBuFU>^ISqPTwWYNoI9gI%As?+07XSBpPCIJl{6 z4_I{Ot6~34R4-gPw`fgK(d~S$p(zt68TZxoZSz`=@7%j!^>!B^SKX5OQ14SK*8L3y C^X*0e diff --git a/assets/generic_95.png b/assets/generic_95.png deleted file mode 100644 index 2286dbfc87153e08e8b1f8b04156931cf8e6f16a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2564 zcmbtUeQXnD7=Ozg8yEzF3kxEg#~(1@dLO-h+(B9A3T$e}80E`M$MyPlJ!pI7?nb*2 zLj8zD(aZ!taG8t7Fb9diM8k*3P!fg#qLBd0hGBD)5!^DOW?>P;_u8&*vJaw{G=1*w z_x_&W^Lw6qUN0_MIc4&!$pC;Ug$2$M01PNJfXs>LuT*L9p}$Gtf{iKwkIvA422fL* z4M6&Ll6!-;!Sx(3%0Z*xl|9fH4TcdKfTg+7upn-M8s>rJQpk??ebt0xlGl!}H@hfT zI3N0@f+_{BsVZ`dRa->Pi{~!GmPUC*5QLh5MT3Em%17;ZoR>$j{+PtEI7HiG$Mf`r z*alZImM<$1vlqbbOPni` z$81270fnu@S=3LEyz znymWeQK(a|hb2JXx?HIoBWei-Qz=w!`8HHW(vT6+s=G1_$r7l_6^aO#Z$mQY>D7e! zd<6=cthi-4FxaW$LCF|nGP2l$^-{Z+W=CLs}VWUkP!B|)i!FgyOL9-sl zCkhs`z&KQWjWDiwwP>)gEA2`#o7p)&u1YZWnrzf#p1EC zDK)DU34LfnU?{7u%8M9T#sUS}<|7yiX@M-u5uDe=5N4J$c_^QarZ}rU&G7-@3nUe_ zt}^+3tbtpTQ9#1tGrwc=E9{f*%i+Ie9+fhlq5H<|neNvQLW$3d}1< z-iE0cfaEAhODDO zINEV~nA)!BCbgeo2vI9&``%jcp>P0dK(RM%Y}Z2RHOPJgxi zWBcqoliN$)vKuE1OdKKYFd>eOhVcHgb7sD@@Uhsl=Kf=u?tw*#pzDbT1P?ojih}8< z_s!Z~`p5wrFkWuy#EXA9lnCCI$I>6S#|7<)4~y>VMSv z*MDiO<7&ml&lh(zHrJhN&jQnc1H}3=uzio$)gICP3b&Ks>gL|9SuJB07Iy`8{LAOJ zm!==IfrFP@I~N{akJx}Amgc(D^;FO7*!i-)$Y)p2nCrVbmNWt5(YA}1+SxhP&42b? z+0)XO)&5&m!`WKlZs+wJ@aqXn?hVg{!W;LjyQ^alej*r{V;Iu&#L$m7&WNV$_YCDo VQAf`y@(@5*;qoHq!KXL9{5Q$K2EzaV diff --git a/package.json b/package.json index 4a9e12f15..e0a452af1 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.103.0", - "minecraft-inventory": "^0.1.4", + "minecraft-inventory": "^0.1.6", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -156,7 +156,6 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", - "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", "mineflayer-mouse": "^0.1.25", "npm-run-all": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b983fde19..fa08b0ab8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.103.0 version: 3.103.0 minecraft-inventory: - specifier: ^0.1.4 - version: 0.1.4(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.6 + version: 0.1.6(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -343,9 +343,6 @@ importers: mc-assets: specifier: ^0.2.72 version: 0.2.72 - minecraft-inventory-gui: - specifier: github:zardoy/minecraft-inventory-gui#next - version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) @@ -6685,12 +6682,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db} - version: 1.0.1 - - minecraft-inventory@0.1.4: - resolution: {integrity: sha512-XjkXo0wSxVVdX4gDJ+gNshJoxy8650gJRu4kEmdb22kVBOwSi7eEfmP18dpM0ZBtn6yRZyHs/0Ic2JE8cZC4mg==} + minecraft-inventory@0.1.6: + resolution: {integrity: sha512-n/8jwIZNEZW2Iqb6yWQd+lLXwzEWt3OP2S7oS8cjYbp6w8GYiLLxku42dtcrxPXopgFeodmTt718RWHykhDPZw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -17357,14 +17350,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db(@types/react@18.3.18)(react@18.3.1): - dependencies: - valtio: 1.13.2(@types/react@18.3.18)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - react - - minecraft-inventory@0.1.4(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.6(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 0dc2da887..146048936 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -1,131 +1,73 @@ -import { proxy, subscribe } from 'valtio' -import { showInventory } from 'minecraft-inventory-gui/web/ext.mjs' +import { proxy } from 'valtio' -// import Dirt from 'mc-assets/dist/other-textures/latest/blocks/dirt.png' import { RecipeItem } from 'minecraft-data' -import { flat, fromFormattedString } from '@xmcl/text-component' +import { flat } from '@xmcl/text-component' import { splitEvery, equals } from 'rambda' import PItem, { Item } from 'prismarine-item' import { versionToNumber } from 'renderer/viewer/common/utils' -import { getRenamedData } from 'flying-squid/dist/blockRenames' -import PrismarineChatLoader from 'prismarine-chat' -import * as nbt from 'prismarine-nbt' -import { BlockModel } from 'mc-assets' -import { renderSlot } from 'renderer/viewer/three/renderSlot' -import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins' -import Generic95 from '../assets/generic_95.png' -import { appReplacableResources } from './generated/resources' +import { getInventoryType } from 'minecraft-inventory/src/registry' +import type { RecipeGuide, ItemStack as InventoryItemStack } from 'minecraft-inventory/src/types' +import type { JEIItem } from 'minecraft-inventory/src/components/JEI/JEI' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' import { options } from './optionsStorage' -import { assertDefined, inGameError } from './utils' import { displayClientChat } from './botUtils' -import { currentScaling } from './scaleInterface' import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' -import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' -import { playerState } from './mineflayer/playerState' -import { modelViewerState } from './react/OverlayModelViewer' -import { openInventoryProxy } from './react/inventory/Inventory' - -const loadedImagesCache = new Map() -const cleanLoadedImagesCache = () => { - loadedImagesCache.delete('blocks') - loadedImagesCache.delete('items') -} +import { getItemNameRaw, RenderItem } from './mineflayer/items' +import { clearInventoryCaches } from './react/inventory/Inventory' -let lastWindow: ReturnType -let lastWindowType: string | null | undefined // null is inventory -/** bot version */ -let version: string let PrismarineItem: typeof Item export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) -let remotePlayerSkin: string | undefined | Promise - -export const showInventoryPlayer = () => { - // openInventoryProxy.inventory = { - // type: 'player', - // } - - // modelViewerState.model = { - // positioning: { - // windowWidth: 176, - // windowHeight: 166, - // x: 25, - // y: 8, - // width: 50, - // height: 70, - // scaled: true, - // onlyInitialScale: true, - // }, - // followCursor: true, - // followCursorCenter: { - // x: 51, - // y: 27, - // }, - // // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], - // // debug: true, - // steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), - // } - // if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { - // remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { - // setTimeout(() => { - // // Check if player inventory is still open before updating - // if (lastWindowType === null) { - // showInventoryPlayer() - // } - // }, 0) // todo patch instead and make reactive - // remotePlayerSkin = a ?? '' - // return remotePlayerSkin - // }) - // } -} - export const onGameLoad = () => { - version = bot.version - - PrismarineItem = PItem(version) + PrismarineItem = PItem(bot.version) - const mapWindowType = (type: string, inventoryStart: number) => { + /** Maps mineflayer window type + inventoryStart to an exact key in the new library's registry */ + const mapWindowType = (type: string, inventoryStart: number): string => { + // minecraft:container size is determined by inventoryStart if (type === 'minecraft:container') { - if (inventoryStart === 45 - 9 * 4) return 'minecraft:generic_9x1' - if (inventoryStart === 45 - 9 * 3) return 'minecraft:generic_9x2' - if (inventoryStart === 45 - 9 * 2) return 'minecraft:generic_9x3' - if (inventoryStart === 45 - 9) return 'minecraft:generic_9x4' - if (inventoryStart === 45) return 'minecraft:generic_9x5' - if (inventoryStart === 45 + 9) return 'minecraft:generic_9x6' + if (inventoryStart === 45 - 9 * 4) return 'generic_9x1' + if (inventoryStart === 45 - 9 * 3) return 'generic_9x2' + if (inventoryStart === 45 - 9 * 2) return 'generic_9x3' + if (inventoryStart === 45 - 9) return 'generic_9x4' + if (inventoryStart === 45) return 'generic_9x5' + if (inventoryStart === 45 + 9) return 'generic_9x6' } - return type - } - - const maybeParseNbtJson = (data: any) => { - if (typeof data === 'string') { - try { - data = JSON.parse(data) - } catch (err) { - // ignore - } + // Version-specific smithing table layout + if (type === 'minecraft:smithing') { + return versionToNumber(bot.version) < versionToNumber('1.20') ? 'smithing_table_legacy' : 'smithing_table' + } + // Strip minecraft: prefix then handle remaining aliases + const stripped = type.startsWith('minecraft:') ? type.slice(10) : type + const remap: Record = { + generic_5x1: 'hopper', + generic_3x3: 'dispenser', + crafting: 'crafting_table', + crafting3x3: 'crafting_table', } - return nbt.simplify(data) ?? data + return remap[stripped] ?? stripped } bot.on('windowOpen', (win) => { - const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] - if (implementedWindow) { - openWindow(implementedWindow, maybeParseNbtJson(win.title)) - } else if (options.unimplementedContainers) { - openWindow('ChestWin', maybeParseNbtJson(win.title)) + const mappedType = mapWindowType(win.type as string, win.inventoryStart) + const isImplemented = !!getInventoryType(mappedType) + + if (isImplemented || options.unimplementedContainers) { + if (activeModalStack.length && !miscUiState.disconnectedCleanup) { + hideCurrentModal() + } + showModal({ reactType: `player_win:${isImplemented ? mappedType : 'chest'}` }) } else { - // todo format - displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) + displayClientChat(`[client error] cannot open unimplemented window ${(win as any).id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item as any)).filter(Boolean).join(', ')}`) + displayClientChat('You can help us fix it! Open a pull request adding support for it on https://github.com/zardoy/minecraft-inventory') bot.currentWindow?.['close']() } }) - // workaround: singleplayer player inventory crafting + // Workaround: singleplayer player-inventory crafting let skipUpdate = false bot.inventory.on('updateSlot', ((_oldSlot, oldItem, newItem) => { const currentSlot = _oldSlot as number @@ -154,444 +96,43 @@ export const onGameLoad = () => { }) } catch (err) { console.error(err) - // todo resolve the error! and why would we ever get here on every update? } }) as any) bot.on('windowClose', () => { - // todo hide up to the window itself! - if (lastWindow) { - hideCurrentModal() + const modal = activeModalStack.at(-1) + if (modal?.reactType?.startsWith('player_win:')) { + hideModal(undefined, undefined, { force: true }) } }) - bot.on('respawn', () => { // todo validate logic against native client (maybe login) - if (lastWindow) { - hideCurrentModal() + bot.on('respawn', () => { + const modal = activeModalStack.at(-1) + if (modal?.reactType?.startsWith('player_win:')) { + hideModal(undefined, undefined, { force: true }) } }) - customEvents.on('search', (q) => { - if (!lastWindow) return - upJei(q) - }) - if (!appViewer.resourcesManager['_inventoryChangeTracked']) { appViewer.resourcesManager['_inventoryChangeTracked'] = true - const texturesChanged = () => { - cleanLoadedImagesCache() - if (!lastWindow) return - upWindowItemsLocal() - upJei(lastJeiSearch) - } - appViewer.resourcesManager.on('assetsInventoryReady', () => texturesChanged()) - appViewer.resourcesManager.on('assetsTexturesUpdated', () => texturesChanged()) + appViewer.resourcesManager.on('assetsInventoryReady', () => clearInventoryCaches()) + appViewer.resourcesManager.on('assetsTexturesUpdated', () => clearInventoryCaches()) } } -const getImageSrc = (path): string | HTMLImageElement | ImageBitmap => { - switch (path) { - case 'gui/container/inventory': return appReplacableResources.latest_gui_container_inventory.content - case 'blocks': return appViewer.resourcesManager.blocksAtlasParser.latestImage - case 'items': return appViewer.resourcesManager.itemsAtlasParser.latestImage - case 'gui': return appViewer.resourcesManager.currentResources!.guiAtlas!.image - case 'gui/container/dispenser': return appReplacableResources.latest_gui_container_dispenser.content - case 'gui/container/furnace': return appReplacableResources.furnace_gui_texture.content - case 'gui/container/crafting_table': return appReplacableResources.latest_gui_container_crafting_table.content - case 'gui/container/shulker_box': return appReplacableResources.latest_gui_container_shulker_box.content - case 'gui/container/generic_54': return appReplacableResources.latest_gui_container_generic_54.content - case 'gui/container/generic_95': return Generic95 - case 'gui/container/hopper': return appReplacableResources.latest_gui_container_hopper.content - case 'gui/container/horse': return appReplacableResources.latest_gui_container_horse.content - case 'gui/container/villager2': return appReplacableResources.latest_gui_container_villager2.content - case 'gui/container/enchanting_table': return appReplacableResources.latest_gui_container_enchanting_table.content - case 'gui/container/anvil': return appReplacableResources.latest_gui_container_anvil.content - case 'gui/container/beacon': return appReplacableResources.latest_gui_container_beacon.content - case 'gui/container/smithing': - return versionToNumber(bot.version) < versionToNumber('1.20') - ? appReplacableResources._1_19_4_gui_container_smithing.content - : appReplacableResources.latest_gui_container_smithing.content - case 'gui/widgets': return appReplacableResources.other_textures_latest_gui_widgets.content - } - // empty texture - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' -} - -const getImage = ({ path = undefined as string | undefined, texture = undefined as string | undefined, blockData = undefined as any, image = undefined as HTMLImageElement | undefined }, onLoad = () => { }) => { - if (image) { - return image - } - if (!path && !texture) { - throw new Error('Either pass path or texture') - } - const loadPath = (blockData ? 'blocks' : path ?? texture)! - if (loadedImagesCache.has(loadPath)) { - onLoad() - } else { - const imageSrc = getImageSrc(loadPath) - if (imageSrc instanceof ImageBitmap) { - onLoad() - loadedImagesCache.set(loadPath, imageSrc) - return imageSrc - } - - let image: HTMLImageElement - if (imageSrc instanceof Image) { - image = imageSrc - } else { - image = new Image() - image.src = imageSrc - } - image.onload = onLoad - loadedImagesCache.set(loadPath, image) - } - return loadedImagesCache.get(loadPath) -} - const getItemName = (slot: Item | RenderItem | null) => { const parsed = getItemNameRaw(slot, appViewer.resourcesManager) if (!parsed) return - // todo display full text renderer from sign renderer const text = flat(parsed as MessageFormatPart).map(x => (typeof x === 'string' ? x : x.text)) return text.join('') } -let lastMappedSlots = [] as any[] -const itemToVisualKey = (slot: RenderItem | Item | null) => { - if (!slot) return '' - const keys = [ - slot.name, - slot.durabilityUsed, - slot.maxDurability, - slot['count'], - slot['metadata'], - slot.nbt ? JSON.stringify(slot.nbt) : '', - slot['components'] ? JSON.stringify(slot['components']) : '', - appViewer.resourcesManager.currentResources!.guiAtlasVersion, - ].join('|') - return keys -} -const validateSlot = (slot: any, index: number) => { - if (!slot.texture) { - throw new Error(`Slot has no texture: ${index} ${slot.name}`) - } -} -const mapSlots = (slots: Array, isJei = false) => { - const newSlots = slots.map((slot, i) => { - if (!slot) return null - - if (!isJei) { - const oldKey = lastMappedSlots[i]?.cacheKey - const newKey = itemToVisualKey(slot) - slot['cacheKey'] = i + '|' + newKey - if (oldKey && oldKey === newKey) { - validateSlot(lastMappedSlots[i], i) - return lastMappedSlots[i] - } - } - - try { - if (slot.durabilityUsed && slot.maxDurability) slot.durabilityUsed = Math.min(slot.durabilityUsed, slot.maxDurability) - const debugIsQuickbar = !isJei && i === bot.inventory.hotbarStart + bot.quickBarSlot - const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }, appViewer.resourcesManager, appViewer.playerState.reactive) - const slotCustomProps = renderSlot({ modelName, originalItemName: slot.name }, appViewer.resourcesManager, debugIsQuickbar) - const itemCustomName = getItemName(slot) - Object.assign(slot, { ...slotCustomProps, displayName: itemCustomName ?? slot.displayName }) - //@ts-expect-error - slot.toJSON = () => { - // Allow to serialize slot to JSON as minecraft-inventory-gui creates icon property as cache (recursively) - //@ts-expect-error - const { icon, ...rest } = slot - return rest - } - validateSlot(slot, i) - } catch (err) { - inGameError(err) - } - return slot - }) - lastMappedSlots = JSON.parse(JSON.stringify(newSlots)) - return newSlots -} - -export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) => { - // inv.pwindow.inv.slots[2].displayName = 'test' - // inv.pwindow.inv.slots[2].blockData = getBlockData('dirt') - const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots) - invWindow.pwindow.setSlots(customSlots) - return customSlots -} - -export const onModalClose = (callback: () => any) => { - const modal = activeModalStack.at(-1) - const unsubscribe = subscribe(activeModalStack, () => { - const newModal = activeModalStack.at(-1) - if (modal?.reactType !== newModal?.reactType) { - callback() - unsubscribe() - } - }, true) -} - -const implementedContainersGuiMap = { - // todo allow arbitrary size instead! - 'minecraft:generic_9x1': 'ChestWin', - 'minecraft:generic_9x2': 'ChestWin', - 'minecraft:generic_9x3': 'ChestWin', - 'minecraft:generic_9x4': 'Generic95Win', - 'minecraft:generic_9x5': 'Generic95Win', - // hopper - 'minecraft:hopper': 'HopperWin', - 'minecraft:generic_5x1': 'HopperWin', - 'minecraft:generic_9x6': 'LargeChestWin', - 'minecraft:generic_3x3': 'DropDispenseWin', - 'minecraft:furnace': 'FurnaceWin', - 'minecraft:smoker': 'FurnaceWin', - 'minecraft:blast_furnace': 'FurnaceWin', - 'minecraft:shulker_box': 'ChestWin', - 'minecraft:crafting': 'CraftingWin', - 'minecraft:smithing': 'will_be_patched_in_openWindow', - 'minecraft:crafting3x3': 'CraftingWin', // todo different result slot - 'minecraft:anvil': 'AnvilWin', - // enchant - 'minecraft:enchanting_table': 'EnchantingWin', - // horse - 'minecraft:horse': 'HorseWin', - // villager - 'minecraft:villager': 'VillagerWin', -} - -let lastJeiSearch = '' -const upJei = (search: string) => { - lastJeiSearch = search - search = search.toLowerCase() - // todo fix pre flat - const itemsArray = [ - ...jeiCustomCategories.value.flatMap(x => x.items).filter(x => x !== null), - ...loadedData.itemsArray.filter(x => x.displayName.toLowerCase().includes(search)).map(item => new PrismarineItem(item.id, 1)).filter(x => x !== null) - ] - const matchedSlots = itemsArray.map(x => { - x.displayName = getItemName(x) ?? x.displayName - if (!x.displayName.toLowerCase().includes(search)) return null - return x - }).filter(a => a !== null) - lastWindow.pwindow.win.jeiSlotsPage = 0 - lastWindow.pwindow.win.jeiSlots = mapSlots(matchedSlots, true) -} - -export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { - const inv = showInventory(type, getImage, {}, _bot); - (inv.canvasManager.children[0].callbacks as any).getItemRecipes = (item) => { - const allRecipes = getAllItemRecipes(item.name) - inv.canvasManager.children[0].messageDisplay = '' - const itemDescription = getItemDescription(item) - if (!allRecipes?.length && !itemDescription) { - inv.canvasManager.children[0].messageDisplay = `No recipes found for ${item.displayName}` - } - return [...allRecipes ?? [], ...itemDescription ? [ - [ - 'GenericDescription', - mapSlots([item], true)[0], - [], - itemDescription - ] - ] : []] - } - (inv.canvasManager.children[0].callbacks as any).getItemUsages = (item) => { - const allItemUsages = getAllItemUsages(item.name) - inv.canvasManager.children[0].messageDisplay = '' - if (!allItemUsages?.length) { - inv.canvasManager.children[0].messageDisplay = `No usages found for ${item.displayName}` - } - return allItemUsages - } - return inv -} - -const upWindowItemsLocal = () => { - void Promise.resolve().then(() => { - if (!lastWindow && bot.currentWindow) { - // edge case: might happen due to high ping, inventory should be closed soon! - // openWindow(implementedContainersGuiMap[bot.currentWindow.type]) - return - } - upInventoryItems(lastWindowType === null) - }) -} - -let skipClosePacketSending = false -const openWindow = (type: string | undefined, title: string | any = undefined) => { - // patch implementedContainersGuiMap - implementedContainersGuiMap['minecraft:smithing'] = versionToNumber(bot.version) < versionToNumber('1.20') ? 'SmithingTableLegacyWin' : 'SmithingTableWin' - - // if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) { - if (activeModalStack.length && !miscUiState.disconnectedCleanup) { // game is not in foreground, don't close current modal - if (type) { - skipClosePacketSending = true - hideCurrentModal() - } else { - bot.currentWindow?.['close']() - return - } - } - lastWindowType = type ?? null - showModal({ - reactType: `player_win:${type}`, - }) - if (type === undefined) { - showInventoryPlayer() - } - cleanLoadedImagesCache() - const inv = openItemsCanvas(type) - inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch - window.inventory = inv - const PrismarineChat = PrismarineChatLoader(bot.version) - try { - inv.canvasManager.children[0].customTitleText = title ? - typeof title === 'string' ? - fromFormattedString(title).text : - new PrismarineChat(title).toString() : - undefined - } catch (err) { - reportError?.(err) - inv.canvasManager.children[0].customTitleText = undefined - } - // todo - inv.canvasManager.setScale(currentScaling.scale === 1 ? 1.5 : currentScaling.scale) - inv.canvas.style.zIndex = '10' - inv.canvas.style.position = 'fixed' - inv.canvas.style.inset = '0' - - inv.canvasManager.onClose = async () => { - await new Promise(resolve => { - setTimeout(resolve, 0) - }) - if (activeModalStack.at(-1)?.reactType?.includes('player_win:')) { - hideModal(undefined, undefined, { force: true }) - } - inv.canvasManager.destroy() - } - - lastWindow = inv - - onModalClose(() => { - // might be already closed (event fired) - if (type !== undefined && bot.currentWindow && !skipClosePacketSending) bot.currentWindow['close']() - lastWindow.destroy() - lastWindow = null as any - lastWindowType = undefined - window.inventory = null - miscUiState.displaySearchInput = false - destroyFn() - skipClosePacketSending = false - - modelViewerState.model = undefined - }) - - upWindowItemsLocal() - - lastWindow.pwindow.touch = miscUiState.currentTouch ?? false - const oldOnInventoryEvent = lastWindow.pwindow.onInventoryEvent.bind(lastWindow.pwindow) - lastWindow.pwindow.onInventoryEvent = (type, containing, windowIndex, inventoryIndex, item) => { - if (inv.canvasManager.children[0].currentGuide) { - const isRightClick = type === 'rightclick' - const isLeftClick = type === 'leftclick' - if (isLeftClick || isRightClick) { - modelViewerState.model = undefined - inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) - } - } else { - oldOnInventoryEvent(type, containing, windowIndex, inventoryIndex, item) - } - } - lastWindow.pwindow.onJeiClick = (slotItem, _index, isRightclick) => { - if (!slotItem) { - console.warn('onJeiClick called with undefined slotItem') - return - } - if (versionToNumber(bot.version) < versionToNumber('1.13')) { - alert('Item give is broken on 1.12.2 and below, we are working on it!') - return - } - // slotItem is the slot from mapSlots - const itemId = loadedData.itemsByName[slotItem.name]?.id - if (!itemId) { - inGameError(`Item for block ${slotItem.name} not found`) - return - } - const item = PrismarineItem.fromNotch({ - ...slotItem, - itemId, - itemCount: isRightclick ? 64 : 1, - components: slotItem.components ?? [], - removeComponents: slotItem.removedComponents ?? [], - itemDamage: slotItem.metadata ?? 0, - nbt: slotItem.nbt, - }) - if (bot.game.gameMode === 'creative') { - const freeSlot = bot.inventory.firstEmptyInventorySlot() - if (freeSlot === null) { - console.warn('[JEI] no free inventory slot available') - return - } - bot._client.write('set_creative_slot', { - slot: freeSlot, - item: PrismarineItem.toNotch(item) - }) - //@ts-expect-error - bot._setSlot(freeSlot, item) - } else { - modelViewerState.model = undefined - inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) - } - } - - const isJeiEnabled = () => { - if (typeof options.jeiEnabled === 'boolean') return options.jeiEnabled - if (Array.isArray(options.jeiEnabled)) { - return options.jeiEnabled.includes(bot.game?.gameMode as any) - } - return false - } - - if (isJeiEnabled()) { - lastWindow.pwindow.win.jeiSlotsPage = 0 - // todo workaround so inventory opens immediately (though it still lags) - setTimeout(() => { - upJei('') - }) - miscUiState.displaySearchInput = true - } else { - lastWindow.pwindow.win.jeiSlots = [] - miscUiState.displaySearchInput = false - } - - if (type === undefined) { - // player inventory - bot.inventory.on('updateSlot', upWindowItemsLocal) - destroyFn = () => { - bot.inventory.off('updateSlot', upWindowItemsLocal) - } - } else { - //@ts-expect-error - bot.currentWindow.on('updateSlot', () => { - upWindowItemsLocal() - }) - } -} - -let destroyFn = () => { } - export const openPlayerInventory = () => { - openInventoryProxy.inventory = { - type: 'player', - } - // openWindow(undefined) + showModal({ reactType: 'player_win:player' }) } const getResultingRecipe = (slots: Array, gridRows: number) => { const inputSlotsItems = slots.map(blockSlot => blockSlot?.type) let currentShape = splitEvery(gridRows, inputSlotsItems as Array) - // todo rewrite with candidates search if (currentShape.length > 1) { // eslint-disable-next-line @typescript-eslint/no-for-in-array for (const slotX in currentShape[0]) { @@ -606,7 +147,6 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { } currentShape = currentShape.map(arr => arr.filter(x => x !== undefined)).filter(x => x.length !== 0) - // todo rewrite // eslint-disable-next-line @typescript-eslint/require-array-sort-compare const slotsIngredients = [...inputSlotsItems].sort().filter(item => item !== undefined) type Result = RecipeItem | undefined @@ -626,82 +166,186 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { } const result = shapeResult ?? shapelessResult if (!result) return - const id = typeof result === 'number' ? result : Array.isArray(result) ? result[0] : result.id + const id = typeof result === 'number' ? result : Array.isArray(result) ? result[0] : (result as any).id if (!id) return - const count = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : result.count) ?? 1 - const metadata = typeof result === 'object' && !Array.isArray(result) ? result.metadata : undefined - const item = new PrismarineItem(id, count, metadata) + const count = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 + const metadata = typeof result === 'object' && !Array.isArray(result) ? (result as any).metadata : undefined + const item = new PrismarineItem(id as number, count, metadata) return item } const ingredientToItem = (recipeItem) => (recipeItem === null ? null : new PrismarineItem(recipeItem, 1)) -const getAllItemRecipes = (itemName: string) => { +// Legacy format used by openItemsCanvas / HotbarRenderApp +// const getAllItemRecipesLegacy = (itemName: string) => { +// const item = loadedData.itemsByName[itemName] +// if (!item) return +// const itemId = item.id +// const recipes = loadedData.recipes?.[itemId] +// if (!recipes) return +// const results = [] as Array<{ +// result: Item, +// ingredients: Array, +// description?: string +// }> + +// for (const recipe of recipes) { +// const { result } = recipe +// if (!result) continue +// const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : (result as any).id +// const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 +// const resultMetadata = typeof result === 'object' && !Array.isArray(result) ? (result as any).metadata : undefined +// const resultItem = new PrismarineItem(resultId as number, resultCount, resultMetadata) +// if ('inShape' in recipe) { +// const ingredients = recipe.inShape +// if (!ingredients) continue +// const ingredientsItems = ingredients.flatMap(items => items.map(item => ingredientToItem(item))) +// results.push({ result: resultItem, ingredients: ingredientsItems }) +// } +// if ('ingredients' in recipe) { +// const { ingredients } = recipe +// if (!ingredients) continue +// const ingredientsItems = ingredients.map(item => ingredientToItem(item)) +// results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) +// } +// } +// return results.map(({ result, ingredients, description }) => { +// return [ +// 'CraftingTableGuide', +// mapSlots([result], true)[0], +// mapSlots(ingredients, true), +// description +// ] +// }) +// } + +// const getAllItemUsagesLegacy = (itemName: string) => { +// const item = loadedData.itemsByName[itemName] +// if (!item) return +// const foundRecipeIds = [] as string[] + +// for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { +// for (const recipe of recipes) { +// if ('inShape' in recipe) { +// if (recipe.inShape.some(row => row.includes(item.id))) { +// foundRecipeIds.push(id) +// } +// } +// if ('ingredients' in recipe) { +// if (recipe.ingredients.includes(item.id)) { +// foundRecipeIds.push(id) +// } +// } +// } +// } + +// return foundRecipeIds.flatMap(id => { +// return getAllItemRecipesLegacy(loadedData.items[id].name) +// }) +// } + +// ----- New React inventory exports ----- + +/** Helper: convert a minecraft-data item ID to a minimal ItemStack for recipe guides */ +const idToItemStack = (id: number | null | undefined): InventoryItemStack | null => { + if (!id) return null + const data = loadedData.items[id] + if (!data) return null + return { type: id, count: 1, name: data.name, displayName: data.displayName } +} + +/** + * Returns recipes for the given item name in the new library's RecipeGuide format. + * Used by Inventory.tsx for JEI recipe lookup. + */ +export const getItemRecipes = (itemName: string): RecipeGuide[] => { + if (!PrismarineItem) return [] const item = loadedData.itemsByName[itemName] - if (!item) return - const itemId = item.id - const recipes = loadedData.recipes?.[itemId] - if (!recipes) return - const results = [] as Array<{ - result: Item, - ingredients: Array, - description?: string - }> - - // get recipes here + if (!item) return [] + const recipes = loadedData.recipes?.[item.id] + if (!recipes) { + const description = getItemDescription(item as any) + if (description) return [{ type: 'custom', title: item.displayName, description }] + return [] + } + + const guides: RecipeGuide[] = [] + for (const recipe of recipes) { const { result } = recipe if (!result) continue - const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : result.id - const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : result.count) ?? 1 - const resultMetadata = typeof result === 'object' && !Array.isArray(result) ? result.metadata : undefined - const resultItem = new PrismarineItem(resultId!, resultCount, resultMetadata) - if ('inShape' in recipe) { - const ingredients = recipe.inShape - if (!ingredients) continue - - const ingredientsItems = ingredients.flatMap(items => items.map(item => ingredientToItem(item))) - results.push({ result: resultItem, ingredients: ingredientsItems }) + const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : (result as any).id + const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 + const resultData = resultId ? loadedData.items[resultId as number] : undefined + if (!resultData) continue + const resultStack: InventoryItemStack = { type: resultId, count: resultCount, name: resultData.name, displayName: resultData.displayName } + + if ('inShape' in recipe && recipe.inShape) { + // Expand shaped recipe into a 9-element 3x3 grid (top-left aligned) + const grid: Array = Array.from({ length: 9 }, () => null) + for (const [rowIdx, row] of recipe.inShape.entries()) { + if (rowIdx >= 3) continue + for (const [colIdx, id] of row.entries()) { + if (colIdx >= 3) continue + grid[rowIdx * 3 + colIdx] = idToItemStack(id as number | null | undefined) + } + } + guides.push({ type: 'crafting', title: resultData.displayName, ingredients: grid, result: resultStack }) } - if ('ingredients' in recipe) { - const { ingredients } = recipe - if (!ingredients) continue - const ingredientsItems = ingredients.map(item => ingredientToItem(item)) - results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) + + if ('ingredients' in recipe && recipe.ingredients) { + const grid: Array = Array.from({ length: 9 }, () => null) + for (const [i, id] of recipe.ingredients.slice(0, 9).entries()) { grid[i] = idToItemStack(id as number | null | undefined) } + guides.push({ type: 'crafting', title: resultData.displayName, description: 'Shapeless', ingredients: grid, result: resultStack }) } } - return results.map(({ result, ingredients, description }) => { - return [ - 'CraftingTableGuide', - mapSlots([result], true)[0], - mapSlots(ingredients, true), - description - ] - }) + + const description = getItemDescription(item as any) + if (description) guides.push({ type: 'custom', title: item.displayName, description }) + + return guides } -const getAllItemUsages = (itemName: string) => { +/** + * Returns usages (recipes where this item is an ingredient) in RecipeGuide format. + * Used by Inventory.tsx for JEI usage lookup. + */ +export const getItemUsages = (itemName: string): RecipeGuide[] => { const item = loadedData.itemsByName[itemName] - if (!item) return - const foundRecipeIds = [] as string[] + if (!item) return [] + const foundItemIds = new Set() for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { for (const recipe of recipes) { - if ('inShape' in recipe) { - if (recipe.inShape.some(row => row.includes(item.id))) { - foundRecipeIds.push(id) - } + if ('inShape' in recipe && recipe.inShape.some(row => row.includes(item.id))) { + foundItemIds.add(id) } - if ('ingredients' in recipe) { - if (recipe.ingredients.includes(item.id)) { - foundRecipeIds.push(id) - } + if ('ingredients' in recipe && recipe.ingredients.includes(item.id)) { + foundItemIds.add(id) } } } - return foundRecipeIds.flatMap(id => { - // todo should use exact match, not include all recipes! - return getAllItemRecipes(loadedData.items[id].name) - }) + return [...foundItemIds].flatMap(id => getItemRecipes(loadedData.items[id].name)) +} + +/** + * Returns all JEI items (custom categories + vanilla items) for the new inventory UI. + */ +export const getJeiItems = (): JEIItem[] => { + if (!PrismarineItem) return [] + + const customItems: JEIItem[] = jeiCustomCategories.value.flatMap(cat => (cat.items).filter(Boolean).map(item => ({ + type: item.type ?? item.id ?? 0, + name: item.name ?? '', + displayName: getItemName(item) ?? item.displayName ?? item.name ?? '', + }))) + + const vanillaItems: JEIItem[] = loadedData.itemsArray.map(item => ({ + type: item.id, + name: item.name, + displayName: item.displayName, + })) + + return [...customItems, ...vanillaItems] } diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 911d9836b..f36aa5faa 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { createPortal } from 'react-dom' import { subscribe, useSnapshot } from 'valtio' -import { openItemsCanvas, upInventoryItems } from '../inventoryWindows' import { activeModalStack, isGameActive, miscUiState } from '../globalState' import { currentScaling } from '../scaleInterface' import { watchUnloadForCleanup } from '../gameUnload' @@ -236,7 +235,8 @@ export default () => { }) }, []) - return gameMode === 'spectator' ? null : + // return gameMode === 'spectator' ? null : + return null // TODO! } const Portal = ({ children, to = document.body }) => { diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 75edd1011..ef4079b79 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -1,38 +1,235 @@ -import { TextureProvider, ScaleProvider, InventoryProvider, InventoryOverlay, createMineflayerConnector, MineflayerBot, InventoryOverlayProps } from 'minecraft-inventory/src' -import { useMemo } from 'react' -import { proxy, useSnapshot } from 'valtio' -import { localTexturesConfig } from 'minecraft-inventory/src/generated/localTextures' +import { createPortal } from 'react-dom' +import { useEffect, useMemo, useCallback, useState } from 'react' +import { useSnapshot } from 'valtio' +import { + TextureProvider, + ScaleProvider, + InventoryProvider, + InventoryOverlay, + createMineflayerConnector, + type MineflayerBot, + type JEIItem, + type RecipeGuide, +} from 'minecraft-inventory/src' +import type { ItemStack, BlockTextureRender } from 'minecraft-inventory/src/types' +import { flat } from '@xmcl/text-component' +import PItem from 'prismarine-item' +import type { Item } from 'prismarine-item' +import { renderSlot } from 'renderer/viewer/three/renderSlot' +import { getItemModelName, getItemNameRaw, RenderItem } from '../../mineflayer/items' import { useAppScale } from '../../scaleInterface' +import { activeModalStack, hideCurrentModal } from '../../globalState' +import { options } from '../../optionsStorage' +import { getJeiItems, getItemRecipes, getItemUsages } from '../../inventoryWindows' +import { inventoryBundledConfig } from './inventoryTexturesConfig' -export const openInventoryProxy = proxy({ - inventory: undefined as InventoryOverlayProps | undefined, -}) +// ----- Atlas sprite extraction (for item textures with resource pack support) ----- + +const spriteCache = new Map() + +/** Clear sprite extraction cache (call when atlases are updated). */ +export function clearInventoryCaches (): void { + spriteCache.clear() + inventoryBundledConfig.resetRenderedSlots() +} + +function getAtlas (texture: string): CanvasImageSource | null { + if (!appViewer?.resourcesManager) return null + const r = appViewer.resourcesManager + if (texture === 'gui') return (r.currentResources?.guiAtlas?.image ?? null) as unknown as CanvasImageSource | null + if (texture === 'items') return (r.itemsAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null + if (texture === 'blocks') return (r.blocksAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null + return null +} + +/** Extract a single-face sprite from the GUI or items atlas as a data URL. */ +function extractSpriteDataUrl (texture: string, slice: number[]): string | undefined { + const atlas = getAtlas(texture) + if (!atlas || !slice) return undefined + const [x, y, w, h] = slice + const cacheKey = `${texture}:${x}:${y}:${w}:${h}` + if (spriteCache.has(cacheKey)) return spriteCache.get(cacheKey) + + try { + const canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h + const ctx = canvas.getContext('2d')! + ctx.drawImage(atlas, x, y, w, h, 0, 0, w, h) + const url = canvas.toDataURL() + spriteCache.set(cacheKey, url) + return url + } catch { + return undefined + } +} + +/** Build an isometric BlockTextureRender from blockData returned by renderSlot. */ +function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { + const source = getAtlas('blocks') + if (!source) return undefined + + const getFace = (...names: string[]): { slice: [number, number, number, number] } | undefined => { + for (const n of names) { + const face = (blockData as any)[n] + if (face?.slice) return { slice: face.slice as [number, number, number, number] } + } + return undefined + } + + const top = getFace('top', 'up', 'all', 'south', 'east', 'west', 'north') + if (!top) return undefined + const left = getFace('west', 'north', 'all', 'south', 'east') ?? top + const right = getFace('south', 'east', 'all', 'north', 'west') ?? top + + return { + source: source as unknown as HTMLImageElement, + top, + left, + right, + } +} + +// ----- Item mapper – enriches raw bot slots with textures and display info ----- + +function buildItemMapper (version: string) { + const PrismarineItem = PItem(version) + + return (raw: { type: number; count: number; metadata?: number; nbt?: unknown }, + mapped: ItemStack): ItemStack => { + try { + const slot = new PrismarineItem(raw.type, raw.count, raw.metadata ?? 0) as Item & RenderItem + if (raw.nbt) (slot as any).nbt = raw.nbt + + const modelName = getItemModelName( + slot, + { 'minecraft:display_context': 'gui' }, + appViewer.resourcesManager, + appViewer.playerState.reactive + ) + const slotProps = renderSlot({ modelName, originalItemName: slot.name }, appViewer.resourcesManager) + + let texture: string | undefined + let blockTexture: BlockTextureRender | undefined + + if (slotProps.blockData) { + blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + + const nameRaw = getItemNameRaw(slot, appViewer.resourcesManager) + const displayName = nameRaw + ? flat(nameRaw).map((p: any) => (typeof p === 'string' ? p : p.text)).join('') + : slot.displayName + + return { + ...mapped, + name: slot.name, + displayName, + texture, + blockTexture, + durability: (slot.durabilityUsed ?? undefined) as number | undefined, + maxDurability: (slot.maxDurability ?? undefined) as number | undefined, + enchantments: slot.enchants?.map((e: any) => ({ name: e.name, level: e.lvl })), + } + } catch { + return mapped + } + } +} + +// ----- Texture config – delegates GUI lookups to inventoryBundledConfig ----- + +const REMOTE_ASSETS = 'https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages/1.21.11/textures' + +const textureConfig = { + getGuiTextureUrl: (path: string) => inventoryBundledConfig.getGuiTextureUrl(path), + getItemTextureUrl (item: ItemStack) { + return `${REMOTE_ASSETS}/item/${item.name ?? item.type}.png` + }, + getBlockTextureUrl (item: ItemStack) { + return `${REMOTE_ASSETS}/block/${item.name ?? item.type}.png` + }, +} + +// ----- Inventory component ----- export const Inventory = () => { - const windowType = 'player' - const connector = useMemo(() => createMineflayerConnector(bot as MineflayerBot), [bot]) const appScale = useAppScale() - const { inventory } = useSnapshot(openInventoryProxy) as typeof openInventoryProxy - - if (!inventory) return null - - return
- - - - - - - -
+ const [textureVersion, setTextureVersion] = useState(0) + + const modalStack = useSnapshot(activeModalStack) as Array<{ reactType: string }> + const activeInvModal = useMemo( + () => modalStack.findLast(m => m.reactType.startsWith('player_win:')), + [modalStack], + ) + const inventoryType = activeInvModal?.reactType.replace('player_win:', '') ?? null + + // Recreate connector when textures refresh so itemMapper re-extracts sprites + const connector = useMemo(() => { + if (!inventoryType) return null + return createMineflayerConnector(bot as MineflayerBot, { + itemMapper: buildItemMapper(bot.version), + }) + }, [textureVersion, !!inventoryType]) + + // Clear caches and force connector refresh on resource-pack changes + useEffect(() => { + const refresh = () => { + clearInventoryCaches() + setTextureVersion(v => v + 1) + } + appViewer.resourcesManager.on('assetsTexturesUpdated', refresh) + appViewer.resourcesManager.on('assetsInventoryReady', refresh) + return () => { + appViewer.resourcesManager.off('assetsTexturesUpdated', refresh) + appViewer.resourcesManager.off('assetsInventoryReady', refresh) + } + }, []) + + const jeiEnabled = options.jeiEnabled === true + || (Array.isArray(options.jeiEnabled) && options.jeiEnabled.includes(bot.game?.gameMode as any)) + + const jeiItems = useMemo( + (): JEIItem[] => (inventoryType && jeiEnabled ? getJeiItems() : []), + [!!inventoryType, jeiEnabled], + ) + + const handleGetRecipes = useCallback( + (item: JEIItem): RecipeGuide[] => getItemRecipes(item.name), + [], + ) + const handleGetUsages = useCallback( + (item: JEIItem): RecipeGuide[] => getItemUsages(item.name), + [], + ) + + const handleClose = useCallback(() => { + if (bot.currentWindow) (bot.currentWindow as any).close?.() + hideCurrentModal() + }, []) + + if (!inventoryType || !connector) return null + + return createPortal( +
+ + + + + + + +
, + document.body, + ) } diff --git a/src/react/inventory/inventoryTexturesConfig.ts b/src/react/inventory/inventoryTexturesConfig.ts new file mode 100644 index 000000000..94a07086a --- /dev/null +++ b/src/react/inventory/inventoryTexturesConfig.ts @@ -0,0 +1,8 @@ +import { createBundledTexturesConfig } from 'minecraft-inventory/src/bundledTexturesConfig' + +/** + * Singleton bundled-textures config for the React inventory. + * Used by Inventory.tsx for getGuiTextureUrl and by resourcePack.ts + * to push resource-pack overrides via setOverride(). + */ +export const inventoryBundledConfig = createBundledTexturesConfig() diff --git a/src/resourcePack.ts b/src/resourcePack.ts index 348c30294..955f56770 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -4,6 +4,7 @@ import fs from 'fs' import JSZip from 'jszip' import { proxy, subscribe } from 'valtio' import { armorTextures } from 'renderer/viewer/three/entity/armorModels' +import { allTexturePaths } from 'minecraft-inventory/src/bundledTexturesConfig' import { copyFilesAsyncWithProgress, mkdirRecursive, removeFileRecursiveAsync } from './browserfs' import { showNotification } from './react/NotificationProvider' import { options } from './optionsStorage' @@ -12,6 +13,7 @@ import { appReplacableResources, resourcesContentOriginal } from './generated/re import { gameAdditionalState, miscUiState } from './globalState' import { watchUnloadForCleanup } from './gameUnload' import { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter' +import { inventoryBundledConfig } from './react/inventory/inventoryTexturesConfig' export const resourcePackState = proxy({ resourcePackInstalled: false, @@ -563,6 +565,19 @@ const updateAllReplacableTextures = async () => { } await setCustomPicture(key, resPath) } + + // Apply resource-pack overrides for inventory GUI textures via the bundled config + inventoryBundledConfig.clearOverrides() + if (basePath) { + for (const texPath of allTexturePaths) { + const fsPath = `${basePath}/assets/minecraft/textures/${texPath}` + if (await existsAsync(fsPath)) { + const file = await fs.promises.readFile(fsPath, 'base64') + inventoryBundledConfig.setOverride(texPath, `data:image/png;base64,${file}`) + } + } + } + inventoryBundledConfig.resetRenderedSlots() } const repeatArr = (arr, i) => Array.from({ length: i }, () => arr) diff --git a/src/resourcesSource.ts b/src/resourcesSource.ts index 9cf1cfbf6..019e7dfa4 100644 --- a/src/resourcesSource.ts +++ b/src/resourcesSource.ts @@ -22,48 +22,4 @@ export const appReplacableResources: Array<{ path: '../node_modules/mc-assets/dist/other-textures/latest/gui/bars.png', cssVar: '--bars-gui-atlas', }, - // container - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/inventory.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/shulker_box.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/generic_54.png', - }, - { - name: 'furnace_gui_texture', - path: '../node_modules/mc-assets/dist/other-textures/1.20.2/gui/container/furnace.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/crafting_table.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/dispenser.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/hopper.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/horse.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/villager2.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/enchanting_table.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/anvil.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/beacon.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/latest/gui/container/smithing.png', - }, - { - path: '../node_modules/mc-assets/dist/other-textures/1.19.4/gui/container/smithing.png', - }, ] From e6b46e1ba198f771ef16c9c3ebd1cc2db1e90de1 Mon Sep 17 00:00:00 2001 From: zverev Date: Thu, 2 Apr 2026 20:13:51 +0300 Subject: [PATCH 04/42] fix: resolve cherry-pick conflicts and fix TS errors --- src/react/HotbarRenderApp.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index f36aa5faa..3a9f400c0 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -81,6 +81,7 @@ const HotbarInner = () => { useEffect(() => { const controller = new AbortController() + // @ts-expect-error -- openItemsCanvas removed during inventory rework (PR #518). HotbarInner is dead code (returns null). const inv = openItemsCanvas('HotbarWin', { _client: { write () {} @@ -115,6 +116,7 @@ const HotbarInner = () => { container.current.appendChild(inv.canvas) const upHotbarItems = () => { if (!appViewer.resourcesManager?.itemsAtlasParser) return + // @ts-expect-error -- upInventoryItems removed during inventory rework (PR #518). HotbarInner is dead code (returns null). globalThis.debugHotbarItems = upInventoryItems(true, inv) } From 59bce8f9d17e8c5f00261fda770ff1f97023bab8 Mon Sep 17 00:00:00 2001 From: zverev Date: Fri, 3 Apr 2026 21:40:11 +0300 Subject: [PATCH 05/42] feat: rewrite hotbar to React InventoryWindow component --- src/react/HotbarRenderApp.tsx | 156 ++++++++------------ src/react/inventory/Inventory.tsx | 138 +---------------- src/react/inventory/sharedConnectorSetup.ts | 137 +++++++++++++++++ 3 files changed, 202 insertions(+), 229 deletions(-) create mode 100644 src/react/inventory/sharedConnectorSetup.ts diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 3a9f400c0..694f2d99e 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -1,15 +1,23 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { createPortal } from 'react-dom' -import { subscribe, useSnapshot } from 'valtio' -import { activeModalStack, isGameActive, miscUiState } from '../globalState' -import { currentScaling } from '../scaleInterface' -import { watchUnloadForCleanup } from '../gameUnload' +import { useSnapshot } from 'valtio' +import { + TextureProvider, + ScaleProvider, + InventoryProvider, + InventoryWindow, + createMineflayerConnector, + type MineflayerBot, +} from 'minecraft-inventory/src' +import { activeModalStack, miscUiState } from '../globalState' +import { useAppScale } from '../scaleInterface' import { getItemNameRaw } from '../mineflayer/items' import { isInRealGameSession } from '../utils' import { triggerCommand } from '../controls' import MessageFormattedString from './MessageFormattedString' import SharedHudVars from './SharedHudVars' +import { textureConfig, buildItemMapper, clearInventoryCaches } from './inventory/sharedConnectorSetup' export const BASE_HOTBAR_HEIGHT = 25 @@ -72,79 +80,25 @@ const ItemName = ({ itemKey }: { itemKey: string }) => { } const HotbarInner = () => { - const container = useRef(null!) const [itemKey, setItemKey] = useState('') + const [textureVersion, setTextureVersion] = useState(0) const hasModals = useSnapshot(activeModalStack).length const { currentTouch, appConfig } = useSnapshot(miscUiState) - const mobileOpenInventory = currentTouch && !appConfig?.disabledCommands?.includes('general.inventory') + const appScale = useAppScale() - useEffect(() => { - const controller = new AbortController() - - // @ts-expect-error -- openItemsCanvas removed during inventory rework (PR #518). HotbarInner is dead code (returns null). - const inv = openItemsCanvas('HotbarWin', { - _client: { - write () {} - }, - clickWindow (slot, mouseButton, mode) { - if (mouseButton === 1) { - console.log('right click') - return - } - const hotbarSlot = slot - bot.inventory.hotbarStart - if (hotbarSlot < 0 || hotbarSlot > 8) return - bot.setQuickBarSlot(hotbarSlot) - }, - } as any) - const { canvasManager } = inv - inv.inventory.supportsOffhand = !bot.supportFeature('doesntHaveOffHandSlot') - inv.pwindow.disablePicking = true - - canvasManager.children[0].disableHighlight = true - canvasManager.minimizedWindow = true - canvasManager.minimizedWindow = true - - function setSize () { - canvasManager.setScale(currentScaling.scale) - - canvasManager.windowHeight = BASE_HOTBAR_HEIGHT * canvasManager.scale - canvasManager.windowWidth = (210 - (inv.inventory.supportsOffhand ? 0 : 25) + (mobileOpenInventory ? 28 : 0)) * canvasManager.scale - } - setSize() - watchUnloadForCleanup(subscribe(currentScaling, setSize)) - inv.canvas.style.pointerEvents = 'auto' - container.current.appendChild(inv.canvas) - const upHotbarItems = () => { - if (!appViewer.resourcesManager?.itemsAtlasParser) return - // @ts-expect-error -- upInventoryItems removed during inventory rework (PR #518). HotbarInner is dead code (returns null). - globalThis.debugHotbarItems = upInventoryItems(true, inv) - } + const supportsOffhand = !bot.supportFeature('doesntHaveOffHandSlot') + const isMobile = currentTouch && !appConfig?.disabledCommands?.includes('general.inventory') - canvasManager.canvas.onclick = (e) => { - if (!isGameActive(true)) return - const pos = inv.canvasManager.getMousePos(inv.canvas, e) - if (canvasManager.canvas.width - pos.x < 35 * inv.canvasManager.scale && mobileOpenInventory) { - triggerCommand('general.inventory', true) - triggerCommand('general.inventory', false) - } - } - - globalThis.debugUpHotbarItems = upHotbarItems - upHotbarItems() - bot.inventory.on('updateSlot', upHotbarItems) - appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems) - appViewer.resourcesManager.on('assetsInventoryReady', () => { - upHotbarItems() + const connector = useMemo(() => { + return createMineflayerConnector(bot as MineflayerBot, { + itemMapper: buildItemMapper(bot.version), }) + }, [textureVersion]) - const setSelectedSlot = (index: number) => { - if (index === bot.quickBarSlot) return - bot.setQuickBarSlot(index) - if (!bot.inventory.slots?.[bot.quickBarSlot + 36]) setItemKey('') - } - const heldItemChanged = () => { - inv.inventory.activeHotbarSlot = bot.quickBarSlot + useEffect(() => { + const controller = new AbortController() + const heldItemChanged = () => { if (!bot.inventory.slots?.[bot.quickBarSlot + 36]) { setItemKey('') return @@ -153,49 +107,54 @@ const HotbarInner = () => { const itemNbt = item.nbt ? JSON.stringify(item.nbt) : '' setItemKey(`${item.name}_split_${item.type}_split_${item.metadata}_split_${itemNbt}_split_${JSON.stringify(item['components'] ?? [])}`) } - heldItemChanged() + heldItemChanged() // initial call bot.on('heldItemChanged' as any, heldItemChanged) document.addEventListener('wheel', (e) => { if (!isInRealGameSession()) return e.preventDefault() const newSlot = ((bot.quickBarSlot + Math.sign(e.deltaY)) % 9 + 9) % 9 - setSelectedSlot(newSlot) + if (newSlot !== bot.quickBarSlot) bot.setQuickBarSlot(newSlot) }, { passive: false, - signal: controller.signal + signal: controller.signal, }) document.addEventListener('keydown', (e) => { if (!isInRealGameSession()) return const numPressed = +((/Digit(\d)/.exec(e.code))?.[1] ?? -1) if (numPressed < 1 || numPressed > 9) return - setSelectedSlot(numPressed - 1) + const newSlot = numPressed - 1 + if (newSlot !== bot.quickBarSlot) bot.setQuickBarSlot(newSlot) }, { passive: false, - signal: controller.signal + signal: controller.signal, }) let touchStart = 0 document.addEventListener('touchstart', (e) => { - if ((e.target as HTMLElement).closest('.hotbar')) { - touchStart = Date.now() - } else { - touchStart = 0 - } - }) + touchStart = (e.target as HTMLElement).closest('.hotbar') ? Date.now() : 0 + }, { signal: controller.signal }) document.addEventListener('touchend', (e) => { if (touchStart && (e.target as HTMLElement).closest('.hotbar') && Date.now() - touchStart > 700) { triggerCommand('general.dropStack', true) triggerCommand('general.dropStack', false) } touchStart = 0 - }) + }, { signal: controller.signal }) + + const refresh = () => { + clearInventoryCaches() + setTextureVersion(v => v + 1) + } + appViewer.resourcesManager.on('assetsTexturesUpdated', refresh) + appViewer.resourcesManager.on('assetsInventoryReady', refresh) return () => { - inv.destroy() controller.abort() - appViewer.resourcesManager.off('assetsTexturesUpdated', upHotbarItems) + bot.off('heldItemChanged' as any, heldItemChanged) + appViewer.resourcesManager.off('assetsTexturesUpdated', refresh) + appViewer.resourcesManager.off('assetsInventoryReady', refresh) } }, []) @@ -217,13 +176,26 @@ const HotbarInner = () => { }}>
+ > + + + + + + + +
@@ -232,13 +204,11 @@ const HotbarInner = () => { export default () => { const [gameMode, setGameMode] = useState(bot.game?.gameMode ?? 'creative') useEffect(() => { - bot.on('game', () => { - setGameMode(bot.game.gameMode) - }) + const onGame = () => setGameMode(bot.game.gameMode) + bot.on('game', onGame) + return () => { bot.off('game', onGame) } }, []) - - // return gameMode === 'spectator' ? null : - return null // TODO! + return gameMode === 'spectator' ? null : } const Portal = ({ children, to = document.body }) => { diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index ef4079b79..8d4d5716d 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -11,147 +11,13 @@ import { type JEIItem, type RecipeGuide, } from 'minecraft-inventory/src' -import type { ItemStack, BlockTextureRender } from 'minecraft-inventory/src/types' -import { flat } from '@xmcl/text-component' -import PItem from 'prismarine-item' -import type { Item } from 'prismarine-item' -import { renderSlot } from 'renderer/viewer/three/renderSlot' -import { getItemModelName, getItemNameRaw, RenderItem } from '../../mineflayer/items' import { useAppScale } from '../../scaleInterface' import { activeModalStack, hideCurrentModal } from '../../globalState' import { options } from '../../optionsStorage' import { getJeiItems, getItemRecipes, getItemUsages } from '../../inventoryWindows' -import { inventoryBundledConfig } from './inventoryTexturesConfig' +import { buildItemMapper, textureConfig, clearInventoryCaches } from './sharedConnectorSetup' -// ----- Atlas sprite extraction (for item textures with resource pack support) ----- - -const spriteCache = new Map() - -/** Clear sprite extraction cache (call when atlases are updated). */ -export function clearInventoryCaches (): void { - spriteCache.clear() - inventoryBundledConfig.resetRenderedSlots() -} - -function getAtlas (texture: string): CanvasImageSource | null { - if (!appViewer?.resourcesManager) return null - const r = appViewer.resourcesManager - if (texture === 'gui') return (r.currentResources?.guiAtlas?.image ?? null) as unknown as CanvasImageSource | null - if (texture === 'items') return (r.itemsAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null - if (texture === 'blocks') return (r.blocksAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null - return null -} - -/** Extract a single-face sprite from the GUI or items atlas as a data URL. */ -function extractSpriteDataUrl (texture: string, slice: number[]): string | undefined { - const atlas = getAtlas(texture) - if (!atlas || !slice) return undefined - const [x, y, w, h] = slice - const cacheKey = `${texture}:${x}:${y}:${w}:${h}` - if (spriteCache.has(cacheKey)) return spriteCache.get(cacheKey) - - try { - const canvas = document.createElement('canvas') - canvas.width = w - canvas.height = h - const ctx = canvas.getContext('2d')! - ctx.drawImage(atlas, x, y, w, h, 0, 0, w, h) - const url = canvas.toDataURL() - spriteCache.set(cacheKey, url) - return url - } catch { - return undefined - } -} - -/** Build an isometric BlockTextureRender from blockData returned by renderSlot. */ -function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { - const source = getAtlas('blocks') - if (!source) return undefined - - const getFace = (...names: string[]): { slice: [number, number, number, number] } | undefined => { - for (const n of names) { - const face = (blockData as any)[n] - if (face?.slice) return { slice: face.slice as [number, number, number, number] } - } - return undefined - } - - const top = getFace('top', 'up', 'all', 'south', 'east', 'west', 'north') - if (!top) return undefined - const left = getFace('west', 'north', 'all', 'south', 'east') ?? top - const right = getFace('south', 'east', 'all', 'north', 'west') ?? top - - return { - source: source as unknown as HTMLImageElement, - top, - left, - right, - } -} - -// ----- Item mapper – enriches raw bot slots with textures and display info ----- - -function buildItemMapper (version: string) { - const PrismarineItem = PItem(version) - - return (raw: { type: number; count: number; metadata?: number; nbt?: unknown }, - mapped: ItemStack): ItemStack => { - try { - const slot = new PrismarineItem(raw.type, raw.count, raw.metadata ?? 0) as Item & RenderItem - if (raw.nbt) (slot as any).nbt = raw.nbt - - const modelName = getItemModelName( - slot, - { 'minecraft:display_context': 'gui' }, - appViewer.resourcesManager, - appViewer.playerState.reactive - ) - const slotProps = renderSlot({ modelName, originalItemName: slot.name }, appViewer.resourcesManager) - - let texture: string | undefined - let blockTexture: BlockTextureRender | undefined - - if (slotProps.blockData) { - blockTexture = buildBlockTexture(slotProps.blockData as Record) - } else if (slotProps.slice) { - texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) - } - - const nameRaw = getItemNameRaw(slot, appViewer.resourcesManager) - const displayName = nameRaw - ? flat(nameRaw).map((p: any) => (typeof p === 'string' ? p : p.text)).join('') - : slot.displayName - - return { - ...mapped, - name: slot.name, - displayName, - texture, - blockTexture, - durability: (slot.durabilityUsed ?? undefined) as number | undefined, - maxDurability: (slot.maxDurability ?? undefined) as number | undefined, - enchantments: slot.enchants?.map((e: any) => ({ name: e.name, level: e.lvl })), - } - } catch { - return mapped - } - } -} - -// ----- Texture config – delegates GUI lookups to inventoryBundledConfig ----- - -const REMOTE_ASSETS = 'https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages/1.21.11/textures' - -const textureConfig = { - getGuiTextureUrl: (path: string) => inventoryBundledConfig.getGuiTextureUrl(path), - getItemTextureUrl (item: ItemStack) { - return `${REMOTE_ASSETS}/item/${item.name ?? item.type}.png` - }, - getBlockTextureUrl (item: ItemStack) { - return `${REMOTE_ASSETS}/block/${item.name ?? item.type}.png` - }, -} +export { clearInventoryCaches } from './sharedConnectorSetup' // ----- Inventory component ----- diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts new file mode 100644 index 000000000..c09765471 --- /dev/null +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -0,0 +1,137 @@ +import type { ItemStack, BlockTextureRender } from 'minecraft-inventory/src/types' +import { flat } from '@xmcl/text-component' +import PItem from 'prismarine-item' +import type { Item } from 'prismarine-item' +import { renderSlot } from 'minecraft-renderer/src/three/renderSlot' +import { getItemModelName, getItemNameRaw, RenderItem } from '../../mineflayer/items' +import { inventoryBundledConfig } from './inventoryTexturesConfig' + +// ----- Atlas sprite extraction (for item textures with resource pack support) ----- + +const spriteCache = new Map() + +/** Clear sprite extraction cache (call when atlases are updated). */ +export function clearInventoryCaches (): void { + spriteCache.clear() + inventoryBundledConfig.resetRenderedSlots() +} + +function getAtlas (texture: string): CanvasImageSource | null { + if (!appViewer?.resourcesManager) return null + const r = appViewer.resourcesManager + if (texture === 'gui') return (r.currentResources?.guiAtlas?.image ?? null) as unknown as CanvasImageSource | null + if (texture === 'items') return (r.itemsAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null + if (texture === 'blocks') return (r.blocksAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null + return null +} + +/** Extract a single-face sprite from the GUI or items atlas as a data URL. */ +function extractSpriteDataUrl (texture: string, slice: number[]): string | undefined { + const atlas = getAtlas(texture) + if (!atlas || !slice) return undefined + const [x, y, w, h] = slice + const cacheKey = `${texture}:${x}:${y}:${w}:${h}` + if (spriteCache.has(cacheKey)) return spriteCache.get(cacheKey) + + try { + const canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h + const ctx = canvas.getContext('2d')! + ctx.drawImage(atlas, x, y, w, h, 0, 0, w, h) + const url = canvas.toDataURL() + spriteCache.set(cacheKey, url) + return url + } catch { + return undefined + } +} + +/** Build an isometric BlockTextureRender from blockData returned by renderSlot. */ +function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { + const source = getAtlas('blocks') + if (!source) return undefined + + const getFace = (...names: string[]): { slice: [number, number, number, number] } | undefined => { + for (const n of names) { + const face = (blockData as any)[n] + if (face?.slice) return { slice: face.slice as [number, number, number, number] } + } + return undefined + } + + const top = getFace('top', 'up', 'all', 'south', 'east', 'west', 'north') + if (!top) return undefined + const left = getFace('west', 'north', 'all', 'south', 'east') ?? top + const right = getFace('south', 'east', 'all', 'north', 'west') ?? top + + return { + source: source as unknown as HTMLImageElement, + top, + left, + right, + } +} + +// ----- Item mapper – enriches raw bot slots with textures and display info ----- + +export function buildItemMapper (version: string) { + const PrismarineItem = PItem(version) + + return (raw: { type: number; count: number; metadata?: number; nbt?: unknown }, + mapped: ItemStack): ItemStack => { + try { + const slot = new PrismarineItem(raw.type, raw.count, raw.metadata ?? 0) as Item & RenderItem + if (raw.nbt) (slot as any).nbt = raw.nbt + + const modelName = getItemModelName( + slot, + { 'minecraft:display_context': 'gui' }, + appViewer.resourcesManager, + appViewer.playerState.reactive + ) + const slotProps = renderSlot({ modelName, originalItemName: slot.name }, appViewer.resourcesManager) + + let texture: string | undefined + let blockTexture: BlockTextureRender | undefined + + if (slotProps.blockData) { + blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + + const nameRaw = getItemNameRaw(slot, appViewer.resourcesManager) + const displayName = nameRaw + ? flat(nameRaw).map((p: any) => (typeof p === 'string' ? p : p.text)).join('') + : slot.displayName + + return { + ...mapped, + name: slot.name, + displayName, + texture, + blockTexture, + durability: (slot.durabilityUsed ?? undefined) as number | undefined, + maxDurability: (slot.maxDurability ?? undefined) as number | undefined, + enchantments: slot.enchants?.map((e: any) => ({ name: e.name, level: e.lvl })), + } + } catch { + return mapped + } + } +} + +// ----- Texture config – delegates GUI lookups to inventoryBundledConfig ----- + +const REMOTE_ASSETS = 'https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages/1.21.11/textures' + +export const textureConfig = { + getGuiTextureUrl: (path: string) => inventoryBundledConfig.getGuiTextureUrl(path), + getItemTextureUrl (item: ItemStack) { + return `${REMOTE_ASSETS}/item/${item.name ?? item.type}.png` + }, + getBlockTextureUrl (item: ItemStack) { + return `${REMOTE_ASSETS}/block/${item.name ?? item.type}.png` + }, +} From 87805dcf976bbc538a3e92d6011772ce7cdcb075 Mon Sep 17 00:00:00 2001 From: zverev Date: Sun, 5 Apr 2026 01:50:12 +0300 Subject: [PATCH 06/42] feat: integrate player model rendering in inventory --- src/react/OverlayModelViewer.tsx | 3 +- src/react/inventory/Inventory.tsx | 46 ++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index a4488555f..9a82cfc16 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -38,6 +38,7 @@ export const modelViewerState = proxy({ playModelAnimationSpeed?: number playModelAnimationLoop?: boolean followCursorCenterDebug?: boolean + zIndex?: number } }) globalThis.modelViewerState = modelViewerState @@ -706,7 +707,7 @@ export default () => {
(null) + + useEffect(() => { + const el = ref.current + if (!el) return + + const rect = el.getBoundingClientRect() + const skinUrl = (appViewer?.playerState?.reactive as any)?.playerSkin ?? '' + + modelViewerState.model = { + steveModelSkin: skinUrl, + positioning: { + windowWidth: window.innerWidth, + windowHeight: window.innerHeight, + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height, + }, + zIndex: 1001, + followCursor: true, + followCursorCenter: { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }, + } + + return () => { + modelViewerState.model = undefined + } + }, [width, height]) + + return
+} + // ----- Inventory component ----- export const Inventory = () => { @@ -76,6 +115,10 @@ export const Inventory = () => { hideCurrentModal() }, []) + const renderEntity = useCallback((w: number, h: number) => { + return + }, []) + if (!inventoryType || !connector) return null return createPortal( @@ -90,6 +133,7 @@ export const Inventory = () => { jeiOnGetRecipes={handleGetRecipes} jeiOnGetUsages={handleGetUsages} onClose={handleClose} + renderEntity={renderEntity} noWatermark /> From 12a6b737fc417290d9007f71c4b301a621496633 Mon Sep 17 00:00:00 2001 From: zverev Date: Sun, 5 Apr 2026 13:35:08 +0300 Subject: [PATCH 07/42] fix: use connector for inventory close and cleanup on unmount --- src/react/inventory/Inventory.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index a7a329e8c..d45de004e 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -79,6 +79,14 @@ export const Inventory = () => { }) }, [textureVersion, !!inventoryType]) + // Destroy connector on unmount — handles E-key close (unmount without handleClose) + useEffect(() => { + if (!connector) return + return () => { + connector.sendAction({ type: 'close' }) + } + }, [connector]) + // Clear caches and force connector refresh on resource-pack changes useEffect(() => { const refresh = () => { @@ -111,9 +119,9 @@ export const Inventory = () => { ) const handleClose = useCallback(() => { - if (bot.currentWindow) (bot.currentWindow as any).close?.() + connector?.sendAction({ type: 'close' }) hideCurrentModal() - }, []) + }, [connector]) const renderEntity = useCallback((w: number, h: number) => { return From b5a1061d013794b0ae808065c95c71393194530e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 6 Apr 2026 10:00:43 +0300 Subject: [PATCH 08/42] fix: wire hotbarOnly and formatTitle to inventory connectors --- src/react/HotbarRenderApp.tsx | 1 + src/react/inventory/Inventory.tsx | 3 +- src/react/inventory/sharedConnectorSetup.ts | 40 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 694f2d99e..8d95a34cf 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -92,6 +92,7 @@ const HotbarInner = () => { const connector = useMemo(() => { return createMineflayerConnector(bot as MineflayerBot, { itemMapper: buildItemMapper(bot.version), + hotbarOnly: true, }) }, [textureVersion]) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index d45de004e..67bed872c 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -15,7 +15,7 @@ import { useAppScale } from '../../scaleInterface' import { activeModalStack, hideCurrentModal } from '../../globalState' import { options } from '../../optionsStorage' import { getJeiItems, getItemRecipes, getItemUsages } from '../../inventoryWindows' -import { buildItemMapper, textureConfig, clearInventoryCaches } from './sharedConnectorSetup' +import { buildItemMapper, textureConfig, clearInventoryCaches, formatWindowTitle } from './sharedConnectorSetup' import { modelViewerState } from '../OverlayModelViewer' export { clearInventoryCaches } from './sharedConnectorSetup' @@ -76,6 +76,7 @@ export const Inventory = () => { if (!inventoryType) return null return createMineflayerConnector(bot as MineflayerBot, { itemMapper: buildItemMapper(bot.version), + formatTitle: formatWindowTitle, }) }, [textureVersion, !!inventoryType]) diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts index c09765471..eee958efe 100644 --- a/src/react/inventory/sharedConnectorSetup.ts +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -135,3 +135,43 @@ export const textureConfig = { return `${REMOTE_ASSETS}/block/${item.name ?? item.type}.png` }, } + +// ----- Window title formatter – resolves JSON text components to display strings ----- + +/** Parse a raw window title (JSON string, NBT object, or plain text) into a readable string. */ +export function formatWindowTitle (rawTitle: any): string { + if (rawTitle == null) return '' + if (typeof rawTitle === 'string') { + // Try to parse JSON text component + if (rawTitle.startsWith('{') || rawTitle.startsWith('"')) { + try { + return formatWindowTitle(JSON.parse(rawTitle)) + } catch { + // Not valid JSON — treat as plain text + } + } + return rawTitle + } + if (typeof rawTitle === 'object') { + // Handle NBT-simplified format: { value: "...", type: "string" } + if ('value' in rawTitle && rawTitle.type === 'string') { + return formatWindowTitle(rawTitle.value) + } + // Handle translate key: { translate: "container.chestDouble" } + if (rawTitle.translate) { + const lang = (globalThis as any).loadedData?.language + return lang?.[rawTitle.translate] ?? rawTitle.translate + } + // Handle text key: { text: "Custom Name" } + if (typeof rawTitle.text === 'string') { + return rawTitle.text + } + // Handle extra/with arrays by joining + if (rawTitle.extra) { + return (rawTitle.extra as any[]).map(formatWindowTitle).join('') + } + // Fallback: stringify for non-empty objects + if (typeof rawTitle[''] === 'string') return rawTitle[''] + } + return String(rawTitle) +} From 4a04ca5f2f470302d04da1124cfa0f92d62bafa6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 17:24:11 +0300 Subject: [PATCH 09/42] fix: use renderer/viewer renderSlot path after hotbar cherry-picks Made-with: Cursor --- src/react/inventory/sharedConnectorSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts index eee958efe..446f7d52a 100644 --- a/src/react/inventory/sharedConnectorSetup.ts +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -2,7 +2,7 @@ import type { ItemStack, BlockTextureRender } from 'minecraft-inventory/src/type import { flat } from '@xmcl/text-component' import PItem from 'prismarine-item' import type { Item } from 'prismarine-item' -import { renderSlot } from 'minecraft-renderer/src/three/renderSlot' +import { renderSlot } from 'renderer/viewer/three/renderSlot' import { getItemModelName, getItemNameRaw, RenderItem } from '../../mineflayer/items' import { inventoryBundledConfig } from './inventoryTexturesConfig' From f219eae8b59dcf1fe8f023e472910b72196f744c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 17:24:25 +0300 Subject: [PATCH 10/42] lint --- src/react/inventory/Inventory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 67bed872c..1999c50cb 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -15,14 +15,14 @@ import { useAppScale } from '../../scaleInterface' import { activeModalStack, hideCurrentModal } from '../../globalState' import { options } from '../../optionsStorage' import { getJeiItems, getItemRecipes, getItemUsages } from '../../inventoryWindows' -import { buildItemMapper, textureConfig, clearInventoryCaches, formatWindowTitle } from './sharedConnectorSetup' import { modelViewerState } from '../OverlayModelViewer' +import { buildItemMapper, textureConfig, clearInventoryCaches, formatWindowTitle } from './sharedConnectorSetup' export { clearInventoryCaches } from './sharedConnectorSetup' // ----- Entity model bridge ----- -function InventoryEntityBridge({ width, height }: { width: number; height: number }) { +function InventoryEntityBridge ({ width, height }: { width: number; height: number }) { const ref = useRef(null) useEffect(() => { From 6a2800303175a164b6fee1a5403a116d5753c5a0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 17:39:47 +0300 Subject: [PATCH 11/42] upinv --- package.json | 2 +- pnpm-lock.yaml | 70 +++++++++++++++++++++++++------------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index e0a452af1..39772f2d8 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.103.0", - "minecraft-inventory": "^0.1.6", + "minecraft-inventory": "^0.1.7", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa08b0ab8..d45f3e659 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,16 +140,16 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13)) minecraft-data: specifier: 3.103.0 version: 3.103.0 minecraft-inventory: - specifier: ^0.1.6 - version: 0.1.6(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.7 + version: 0.1.7(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(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) @@ -345,7 +345,7 @@ importers: version: 0.2.72 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.25 version: 0.1.25 @@ -6682,8 +6682,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.6: - resolution: {integrity: sha512-n/8jwIZNEZW2Iqb6yWQd+lLXwzEWt3OP2S7oS8cjYbp6w8GYiLLxku42dtcrxPXopgFeodmTt718RWHykhDPZw==} + minecraft-inventory@0.1.7: + resolution: {integrity: sha512-Im4JV2/cWRSoc3i/2S6Tz9pgJ1UDU6B2MBCMy9PtTZr10hV5MvRYuIN5vA3l1VFkKomGBBdS01Rj3kNWGVJ8/g==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -6694,9 +6694,9 @@ packages: mc-assets: optional: true - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd} - version: 1.64.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77} + version: 1.66.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6714,8 +6714,8 @@ packages: resolution: {integrity: sha512-6QyPT7b7ETNmCy5A34eQ5nSLkBWMNX7c4UWFo7VOU8igCzGag9oPKRlY7GGjZzmTdhe+bXGfA1qb63CaZnML1w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801} version: 8.0.0 engines: {node: '>=22'} @@ -7431,8 +7431,8 @@ packages: prismarine-entity@2.5.0: resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==} - prismarine-item@1.17.0: - resolution: {integrity: sha512-wN1OjP+f+Uvtjo3KzeCkVSy96CqZ8yG7cvuvlGwcYupQ6ct7LtNkubHp0AHuLMJ0vbbfAC0oZ2bWOgI1DYp8WA==} + prismarine-item@1.18.0: + resolution: {integrity: sha512-8pEq6YfcneVvarvUFnex09a3+MR8/4NCQVyawIKAa3kh/g9dHLexoEcpQEgM3cmpg4gbLmspSiARGwed5uGhlg==} prismarine-nbt@2.7.0: resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==} @@ -11372,9 +11372,9 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.103.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 transitivePeerDependencies: @@ -13133,14 +13133,14 @@ snapshots: long: 5.3.1 mc-bridge: 0.1.3(minecraft-data@3.103.0) minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.103.0) prismarine-windows: 2.9.0 @@ -13170,13 +13170,13 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.103.0) prismarine-windows: 2.9.0 @@ -17034,13 +17034,13 @@ snapshots: dependencies: minecraft-data: 3.103.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) - prismarine-item: 1.17.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) + prismarine-item: 1.18.0 ws: 8.18.1 transitivePeerDependencies: - bufferutil @@ -17350,7 +17350,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.6(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.7(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 @@ -17358,7 +17358,7 @@ snapshots: optionalDependencies: mc-assets: 0.2.72 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17410,7 +17410,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17420,22 +17420,22 @@ snapshots: dependencies: change-case: 5.4.4 debug: 4.4.1 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.103.0)(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/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) @@ -18230,7 +18230,7 @@ snapshots: minecraft-data: 3.103.0 prismarine-biome: 1.3.0(minecraft-data@3.103.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18256,11 +18256,11 @@ snapshots: prismarine-entity@2.5.0: dependencies: prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-registry: 1.11.0 vec3: 0.1.10 - prismarine-item@1.17.0: + prismarine-item@1.18.0: dependencies: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18314,7 +18314,7 @@ snapshots: prismarine-windows@2.9.0: dependencies: - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-registry: 1.11.0 typed-emitter: 2.1.0 From 0acb7c6db61cbbac1966d9b981aabc980fab4b7f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 17:44:46 +0300 Subject: [PATCH 12/42] never use remote fallback --- src/react/inventory/inventoryTexturesConfig.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/react/inventory/inventoryTexturesConfig.ts b/src/react/inventory/inventoryTexturesConfig.ts index 94a07086a..694d7b025 100644 --- a/src/react/inventory/inventoryTexturesConfig.ts +++ b/src/react/inventory/inventoryTexturesConfig.ts @@ -5,4 +5,6 @@ import { createBundledTexturesConfig } from 'minecraft-inventory/src/bundledText * Used by Inventory.tsx for getGuiTextureUrl and by resourcePack.ts * to push resource-pack overrides via setOverride(). */ -export const inventoryBundledConfig = createBundledTexturesConfig() +export const inventoryBundledConfig = createBundledTexturesConfig({ + remoteFallback: false +}) From edfa917b0a621c8823a8ce7c20d1c80305fbda98 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 17:48:21 +0300 Subject: [PATCH 13/42] enable notes as was intended --- src/react/inventory/Inventory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 1999c50cb..493db5cba 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -143,7 +143,7 @@ export const Inventory = () => { jeiOnGetUsages={handleGetUsages} onClose={handleClose} renderEntity={renderEntity} - noWatermark + enableNotes /> From a6e1edc545bd8ee382bc77a6442b4877d0a48615 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 7 Apr 2026 18:05:16 +0300 Subject: [PATCH 14/42] use mineflayer fix --- pnpm-lock.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d45f3e659..1b6b436d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,7 +140,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13)) minecraft-data: specifier: 3.103.0 version: 3.103.0 @@ -345,7 +345,7 @@ importers: version: 0.2.72 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.25 version: 0.1.25 @@ -6714,8 +6714,8 @@ packages: resolution: {integrity: sha512-6QyPT7b7ETNmCy5A34eQ5nSLkBWMNX7c4UWFo7VOU8igCzGag9oPKRlY7GGjZzmTdhe+bXGfA1qb63CaZnML1w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1} version: 8.0.0 engines: {node: '>=22'} @@ -11372,7 +11372,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.103.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.18.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -17034,12 +17034,12 @@ snapshots: dependencies: minecraft-data: 3.103.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) prismarine-item: 1.18.0 ws: 8.18.1 transitivePeerDependencies: @@ -17410,7 +17410,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17425,7 +17425,7 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4697107a98be5b8f0e1a0b130839cedc752e2801(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 minecraft-data: 3.103.0 From d77b2709122c9d6aea0335e9e692c209204ab436 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 8 Apr 2026 00:28:49 +0300 Subject: [PATCH 15/42] up mouse --- package.json | 2 +- pnpm-lock.yaml | 39 +++++++++++++-------------------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 39772f2d8..c78407178 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.25", + "mineflayer-mouse": "^0.1.27", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b6b436d1..f70773384 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -347,8 +347,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.25 - version: 0.1.25 + specifier: ^0.1.27 + version: 0.1.27 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -4406,15 +4406,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -6710,8 +6701,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.25: - resolution: {integrity: sha512-6QyPT7b7ETNmCy5A34eQ5nSLkBWMNX7c4UWFo7VOU8igCzGag9oPKRlY7GGjZzmTdhe+bXGfA1qb63CaZnML1w==} + mineflayer-mouse@0.1.27: + resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1: @@ -10338,7 +10329,7 @@ snapshots: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.1 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12919,7 +12910,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4) '@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4) - debug: 4.4.1 + debug: 4.4.3 eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.5.4) optionalDependencies: @@ -12951,7 +12942,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.1 + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -12966,7 +12957,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.1 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -14428,10 +14419,6 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -14998,7 +14985,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.18.20): dependencies: - debug: 4.4.1 + debug: 4.4.3 esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -16190,7 +16177,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color optional: true @@ -17416,10 +17403,10 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.25: + mineflayer-mouse@0.1.27: dependencies: change-case: 5.4.4 - debug: 4.4.1 + debug: 4.4.3 prismarine-item: 1.18.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c transitivePeerDependencies: @@ -18505,7 +18492,7 @@ snapshots: puppeteer-core@2.1.1: dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.1 + debug: 4.4.3 extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 From 78c608e52b890a101ae085911fec9763a440d15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Wed, 8 Apr 2026 14:46:30 +0300 Subject: [PATCH 16/42] fix: correct block faces, durability bar and enrich JEI textures --- src/inventoryWindows.ts | 36 +++++++++++++++++++-- src/react/inventory/sharedConnectorSetup.ts | 12 ++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 146048936..e0844848e 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -8,13 +8,15 @@ import { versionToNumber } from 'renderer/viewer/common/utils' import { getInventoryType } from 'minecraft-inventory/src/registry' import type { RecipeGuide, ItemStack as InventoryItemStack } from 'minecraft-inventory/src/types' import type { JEIItem } from 'minecraft-inventory/src/components/JEI/JEI' +import { renderSlot } from 'renderer/viewer/three/renderSlot' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' import { options } from './optionsStorage' import { displayClientChat } from './botUtils' import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' -import { getItemNameRaw, RenderItem } from './mineflayer/items' +import { getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' import { clearInventoryCaches } from './react/inventory/Inventory' +import { buildBlockTexture, extractSpriteDataUrl } from './react/inventory/sharedConnectorSetup' let PrismarineItem: typeof Item @@ -331,6 +333,7 @@ export const getItemUsages = (itemName: string): RecipeGuide[] => { /** * Returns all JEI items (custom categories + vanilla items) for the new inventory UI. + * Items are enriched with texture/blockTexture data from the rendering pipeline. */ export const getJeiItems = (): JEIItem[] => { if (!PrismarineItem) return [] @@ -347,5 +350,34 @@ export const getJeiItems = (): JEIItem[] => { displayName: item.displayName, })) - return [...customItems, ...vanillaItems] + const allItems = [...customItems, ...vanillaItems] + + // Enrich items with texture data if the rendering pipeline is available + if (!appViewer?.resourcesManager?.currentResources) return allItems + + const resourcesManager = appViewer.resourcesManager + const playerState = appViewer.playerState?.reactive + if (!playerState) return allItems + + for (const item of allItems) { + try { + const modelName = getItemModelName( + { name: item.name, nbt: null }, + { 'minecraft:display_context': 'gui' }, + resourcesManager, + playerState + ) + const slotProps = renderSlot({ modelName, originalItemName: item.name }, resourcesManager) + + if (slotProps.blockData) { + item.blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + item.texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + } catch { + // Skip texture enrichment for items that fail — they'll fall back to CDN sprites + } + } + + return allItems } diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts index 446f7d52a..b7b34c81e 100644 --- a/src/react/inventory/sharedConnectorSetup.ts +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -26,7 +26,7 @@ function getAtlas (texture: string): CanvasImageSource | null { } /** Extract a single-face sprite from the GUI or items atlas as a data URL. */ -function extractSpriteDataUrl (texture: string, slice: number[]): string | undefined { +export function extractSpriteDataUrl (texture: string, slice: number[]): string | undefined { const atlas = getAtlas(texture) if (!atlas || !slice) return undefined const [x, y, w, h] = slice @@ -48,7 +48,7 @@ function extractSpriteDataUrl (texture: string, slice: number[]): string | undef } /** Build an isometric BlockTextureRender from blockData returned by renderSlot. */ -function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { +export function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { const source = getAtlas('blocks') if (!source) return undefined @@ -62,8 +62,8 @@ function buildBlockTexture (blockData: Record ({ name: e.name, level: e.lvl })), } From 8612dcb11f946f57e78e687ea1aafee8991c89ed Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 9 Apr 2026 09:59:43 +0300 Subject: [PATCH 17/42] up inv --- package.json | 2 +- pnpm-lock.yaml | 96 +++++++++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 8f6019bc8..cbb066e9d 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.7", + "minecraft-inventory": "^0.1.9", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3ac8a679..166952583 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,13 +140,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13)) minecraft-data: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.7 - version: 0.1.7(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.9 + version: 0.1.9(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -173,7 +173,7 @@ importers: version: 6.1.1 prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything - version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.106.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -345,7 +345,7 @@ importers: version: 0.2.72 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.27 version: 0.1.27 @@ -433,10 +433,10 @@ 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/401a296892fbde312ade34685fdc2db3d31668ad prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.7: - resolution: {integrity: sha512-Im4JV2/cWRSoc3i/2S6Tz9pgJ1UDU6B2MBCMy9PtTZr10hV5MvRYuIN5vA3l1VFkKomGBBdS01Rj3kNWGVJ8/g==} + minecraft-inventory@0.1.9: + resolution: {integrity: sha512-/PZFWRgbXzYVKK60G5jujnDHmmi7YlaAPOejTH6uq2lcT+z0nj4bDBczixLA0wt1Uhmnussj/z5UlcYLY7521A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -6731,8 +6731,8 @@ packages: resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9} version: 8.0.0 engines: {node: '>=22'} @@ -7433,16 +7433,16 @@ packages: minecraft-data: 3.106.0 prismarine-registry: ^1.1.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9} - version: 1.21.0 + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad} + version: 1.23.0 prismarine-chat@1.11.0: resolution: {integrity: sha512-VJT/MWYB3qoiznUhrgvSQh76YFpzpCZpY85kJKxHLbd3UVoM0wsfs43Eg8dOltiZG92wc5/DTMLlT07TEeoa9w==} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332} - version: 1.39.0 + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a} + version: 1.40.0 engines: {node: '>=14'} prismarine-entity@2.5.0: @@ -7458,15 +7458,15 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b} version: 1.9.0 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7} - version: 2.8.0 + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9} + version: 2.13.0 prismarine-realms@1.3.2: resolution: {integrity: sha512-5apl9Ru8veTj5q2OozRc4GZOuSIcs3yY4UEtALiLKHstBe8bRw8vNlaz4Zla3jsQ8yP/ul1b1IJINTRbocuA6g==} - prismarine-recipe@1.3.1: - resolution: {integrity: sha512-xfa9E9ACoaDi+YzNQ+nk8kWSIqt5vSZOOCHIT+dTXscf/dng2HaJ/59uwe1D/jvOkAd2OvM6RRJM6fFe0q/LDA==} + prismarine-recipe@1.5.0: + resolution: {integrity: sha512-GRZHbsyBIUgVNF10vFRv2YWZj86vokCT5EWX6iK6gfx6h4FapgZT29V2DNkjv5+hmdzBCLZvfx1/RYr8VPeoGQ==} peerDependencies: prismarine-registry: ^1.4.0 @@ -11389,8 +11389,8 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.106.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-item: 1.18.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -13154,12 +13154,12 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.106.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13191,11 +13191,11 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.106.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14592,7 +14592,7 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: minecraft-data: 3.106.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -17047,12 +17047,12 @@ snapshots: dependencies: minecraft-data: 3.106.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) prismarine-item: 1.18.0 ws: 8.18.1 transitivePeerDependencies: @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.7(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.9(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 @@ -17423,7 +17423,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17438,20 +17438,21 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/f94428c114162ee5d0bfcf0c23654a356be7c2f1(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 minecraft-data: 3.106.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + mojangson: 2.0.4 prismarine-biome: 1.3.0(minecraft-data@3.106.0)(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/401a296892fbde312ade34685fdc2db3d31668ad prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b - prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) + prismarine-recipe: 1.5.0(prismarine-registry@1.11.0) prismarine-registry: 1.11.0 prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18238,7 +18239,7 @@ snapshots: minecraft-data: 3.106.0 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/401a296892fbde312ade34685fdc2db3d31668ad: dependencies: minecraft-data: 3.106.0 prismarine-biome: 1.3.0(minecraft-data@3.106.0)(prismarine-registry@1.11.0) @@ -18253,10 +18254,10 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.106.0)(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/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18288,12 +18289,11 @@ snapshots: prismarine-nbt: 2.7.0 vec3: 0.1.10 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.106.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.106.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-nbt: 2.7.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 vec3: 0.1.10 transitivePeerDependencies: @@ -18307,20 +18307,20 @@ snapshots: - encoding - supports-color - prismarine-recipe@1.3.1(prismarine-registry@1.11.0): + prismarine-recipe@1.5.0(prismarine-registry@1.11.0): dependencies: prismarine-registry: 1.11.0 prismarine-registry@1.11.0: dependencies: minecraft-data: 3.106.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.106.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 From 1cceaf8f100ae3de715a9d7d42e011077b90c26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 9 Apr 2026 11:22:24 +0300 Subject: [PATCH 18/42] fix: remove CDN texture fallback, use local atlases only --- src/react/inventory/sharedConnectorSetup.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts index b7b34c81e..f3755ed47 100644 --- a/src/react/inventory/sharedConnectorSetup.ts +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -126,15 +126,13 @@ export function buildItemMapper (version: string) { // ----- Texture config – delegates GUI lookups to inventoryBundledConfig ----- -const REMOTE_ASSETS = 'https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages/1.21.11/textures' - export const textureConfig = { getGuiTextureUrl: (path: string) => inventoryBundledConfig.getGuiTextureUrl(path), - getItemTextureUrl (item: ItemStack) { - return `${REMOTE_ASSETS}/item/${item.name ?? item.type}.png` + getItemTextureUrl (_item: ItemStack) { + return '' }, - getBlockTextureUrl (item: ItemStack) { - return `${REMOTE_ASSETS}/block/${item.name ?? item.type}.png` + getBlockTextureUrl (_item: ItemStack) { + return '' }, } From 26d0dbd5e813395c0b1e4d20d1e2d0eed2f9c5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 9 Apr 2026 12:23:52 +0300 Subject: [PATCH 19/42] feat: enrich recipe item textures from local atlas --- src/inventoryWindows.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index e0844848e..e8d13fb32 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -248,12 +248,35 @@ const ingredientToItem = (recipeItem) => (recipeItem === null ? null : new Prism // ----- New React inventory exports ----- +/** Enrich a single item with texture data from the rendering pipeline (same as JEI enrichment) */ +const enrichItemTexture = (item: InventoryItemStack): void => { + if (!appViewer?.resourcesManager?.currentResources) return + const playerState = appViewer.playerState?.reactive + if (!playerState) return + try { + const modelName = getItemModelName( + { name: item.name ?? '', nbt: null }, + { 'minecraft:display_context': 'gui' }, + appViewer.resourcesManager, + playerState + ) + const slotProps = renderSlot({ modelName, originalItemName: item.name ?? '' }, appViewer.resourcesManager) + if (slotProps.blockData) { + item.blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + item.texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + } catch { /* skip items that fail enrichment */ } +} + /** Helper: convert a minecraft-data item ID to a minimal ItemStack for recipe guides */ const idToItemStack = (id: number | null | undefined): InventoryItemStack | null => { if (!id) return null const data = loadedData.items[id] if (!data) return null - return { type: id, count: 1, name: data.name, displayName: data.displayName } + const stack: InventoryItemStack = { type: id, count: 1, name: data.name, displayName: data.displayName } + enrichItemTexture(stack) + return stack } /** @@ -281,6 +304,7 @@ export const getItemRecipes = (itemName: string): RecipeGuide[] => { const resultData = resultId ? loadedData.items[resultId as number] : undefined if (!resultData) continue const resultStack: InventoryItemStack = { type: resultId, count: resultCount, name: resultData.name, displayName: resultData.displayName } + enrichItemTexture(resultStack) if ('inShape' in recipe && recipe.inShape) { // Expand shaped recipe into a 9-element 3x3 grid (top-left aligned) From ab29c5de7960150da2e459dbf66a0f4ddbedd945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 9 Apr 2026 14:32:36 +0300 Subject: [PATCH 20/42] fix: use ImageBitmap atlas for item sprites, keep data URL for block renderer --- src/react/inventory/sharedConnectorSetup.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/react/inventory/sharedConnectorSetup.ts b/src/react/inventory/sharedConnectorSetup.ts index f3755ed47..a8da42f61 100644 --- a/src/react/inventory/sharedConnectorSetup.ts +++ b/src/react/inventory/sharedConnectorSetup.ts @@ -20,8 +20,16 @@ function getAtlas (texture: string): CanvasImageSource | null { if (!appViewer?.resourcesManager) return null const r = appViewer.resourcesManager if (texture === 'gui') return (r.currentResources?.guiAtlas?.image ?? null) as unknown as CanvasImageSource | null - if (texture === 'items') return (r.itemsAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null - if (texture === 'blocks') return (r.blocksAtlasParser?.latestImage ?? null) as unknown as CanvasImageSource | null + if (texture === 'items') return (r.currentResources?.itemsAtlasImage ?? null) as unknown as CanvasImageSource | null + if (texture === 'blocks') return (r.currentResources?.blocksAtlasImage ?? null) as unknown as CanvasImageSource | null + return null +} + +/** Get atlas source suitable for the block renderer (accepts string data URLs). */ +function getAtlasForBlockRenderer (texture: string): HTMLImageElement | string | null { + if (!appViewer?.resourcesManager) return null + const r = appViewer.resourcesManager + if (texture === 'blocks') return (r.blocksAtlasParser?.latestImage ?? null) return null } @@ -49,7 +57,7 @@ export function extractSpriteDataUrl (texture: string, slice: number[]): string /** Build an isometric BlockTextureRender from blockData returned by renderSlot. */ export function buildBlockTexture (blockData: Record): BlockTextureRender | undefined { - const source = getAtlas('blocks') + const source = getAtlasForBlockRenderer('blocks') if (!source) return undefined const getFace = (...names: string[]): { slice: [number, number, number, number] } | undefined => { From 83e7474c2152c1ed99c493fbfe5db15bbdbc53a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 9 Apr 2026 15:02:34 +0300 Subject: [PATCH 21/42] feat: enable noPlaceholders to hide armor slot labels --- src/react/inventory/Inventory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 493db5cba..683eaf38f 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -134,7 +134,7 @@ export const Inventory = () => {
- + Date: Fri, 10 Apr 2026 10:37:29 +0300 Subject: [PATCH 22/42] feat: restore JEI item giving in creative mode --- src/react/inventory/Inventory.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 683eaf38f..1c4a5c9e6 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -63,6 +63,13 @@ function InventoryEntityBridge ({ width, height }: { width: number; height: numb export const Inventory = () => { const appScale = useAppScale() const [textureVersion, setTextureVersion] = useState(0) + const [gameMode, setGameMode] = useState(bot.game?.gameMode ?? '') + + useEffect(() => { + const onGame = () => setGameMode(bot.game.gameMode) + bot.on('game', onGame) + return () => { bot.removeListener('game', onGame) } + }, []) const modalStack = useSnapshot(activeModalStack) as Array<{ reactType: string }> const activeInvModal = useMemo( @@ -119,6 +126,23 @@ export const Inventory = () => { [], ) + const handleJeiItemGive = useCallback((item: JEIItem, count: number) => { + if (!item.type || !loadedData.items[item.type]) return + const PrismarineItem = require('prismarine-item')(bot.version) + const pItem = new PrismarineItem(item.type, count, item.metadata ?? 0) + const freeSlot = bot.inventory.firstEmptyInventorySlot() + if (freeSlot === null) return + bot._client.write('set_creative_slot', { + slot: freeSlot, + item: PrismarineItem.toNotch(pItem) + }) + // @ts-expect-error _setSlot is private + bot._setSlot(freeSlot, pItem) + }, []) + + const handleJeiItemClick = useCallback((item: JEIItem) => handleJeiItemGive(item, 1), [handleJeiItemGive]) + const handleJeiItemRightClick = useCallback((item: JEIItem) => handleJeiItemGive(item, 64), [handleJeiItemGive]) + const handleClose = useCallback(() => { connector?.sendAction({ type: 'close' }) hideCurrentModal() @@ -141,6 +165,8 @@ export const Inventory = () => { jeiItems={jeiEnabled ? jeiItems : []} jeiOnGetRecipes={handleGetRecipes} jeiOnGetUsages={handleGetUsages} + jeiOnItemClick={gameMode === 'creative' ? handleJeiItemClick : undefined} + jeiOnItemRightClick={gameMode === 'creative' ? handleJeiItemRightClick : undefined} onClose={handleClose} renderEntity={renderEntity} enableNotes From 5cab51b657b351b51b7a6ebc5a397c2da0e2ddb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Fri, 10 Apr 2026 11:18:11 +0300 Subject: [PATCH 23/42] refactor: render player model directly inside inventory component --- src/react/inventory/Inventory.tsx | 43 +---- src/react/inventory/PlayerModelViewer.tsx | 183 ++++++++++++++++++++++ 2 files changed, 186 insertions(+), 40 deletions(-) create mode 100644 src/react/inventory/PlayerModelViewer.tsx diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 1c4a5c9e6..53e4dbcc6 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -1,5 +1,5 @@ import { createPortal } from 'react-dom' -import { useEffect, useMemo, useCallback, useRef, useState } from 'react' +import { useEffect, useMemo, useCallback, useState } from 'react' import { useSnapshot } from 'valtio' import { TextureProvider, @@ -15,48 +15,11 @@ import { useAppScale } from '../../scaleInterface' import { activeModalStack, hideCurrentModal } from '../../globalState' import { options } from '../../optionsStorage' import { getJeiItems, getItemRecipes, getItemUsages } from '../../inventoryWindows' -import { modelViewerState } from '../OverlayModelViewer' +import { PlayerModelViewer } from './PlayerModelViewer' import { buildItemMapper, textureConfig, clearInventoryCaches, formatWindowTitle } from './sharedConnectorSetup' export { clearInventoryCaches } from './sharedConnectorSetup' -// ----- Entity model bridge ----- - -function InventoryEntityBridge ({ width, height }: { width: number; height: number }) { - const ref = useRef(null) - - useEffect(() => { - const el = ref.current - if (!el) return - - const rect = el.getBoundingClientRect() - const skinUrl = (appViewer?.playerState?.reactive as any)?.playerSkin ?? '' - - modelViewerState.model = { - steveModelSkin: skinUrl, - positioning: { - windowWidth: window.innerWidth, - windowHeight: window.innerHeight, - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }, - zIndex: 1001, - followCursor: true, - followCursorCenter: { - x: rect.left + rect.width / 2, - y: rect.top + rect.height / 2, - }, - } - - return () => { - modelViewerState.model = undefined - } - }, [width, height]) - - return
-} // ----- Inventory component ----- @@ -149,7 +112,7 @@ export const Inventory = () => { }, [connector]) const renderEntity = useCallback((w: number, h: number) => { - return + return }, []) if (!inventoryType || !connector) return null diff --git a/src/react/inventory/PlayerModelViewer.tsx b/src/react/inventory/PlayerModelViewer.tsx new file mode 100644 index 000000000..728ed4d6e --- /dev/null +++ b/src/react/inventory/PlayerModelViewer.tsx @@ -0,0 +1,183 @@ +import { useEffect, useRef } from 'react' +import * as THREE from 'three' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' +import { createPlayerObject, applySkinToPlayerObject, PlayerObjectType } from '../../../renderer/viewer/lib/createPlayerObject' + +const setupMaterialTransparency = (material: THREE.Material): void => { + if ( + material instanceof THREE.MeshStandardMaterial || + material instanceof THREE.MeshBasicMaterial || + material instanceof THREE.MeshPhongMaterial + ) { + const hasAlpha = material.alphaMap || + (material.opacity !== undefined && material.opacity < 1) || + (material.map && material.map.format === THREE.RGBAFormat) + if (hasAlpha) { + material.transparent = true + material.alphaTest = 0.01 + material.side = THREE.DoubleSide + } else { + material.transparent = false + material.side = THREE.FrontSide + } + material.needsUpdate = true + } +} + +export function PlayerModelViewer ({ width, height }: { width: number; height: number }) { + const containerRef = useRef(null) + const stateRef = useRef<{ + renderer: THREE.WebGLRenderer + camera: THREE.PerspectiveCamera + scene: THREE.Scene + controls: OrbitControls + playerObject: PlayerObjectType + wrapper: THREE.Object3D + disposed: boolean + } | null>(null) + + useEffect(() => { + const container = containerRef.current + if (!container) return + + // Scene + const scene = new THREE.Scene() + scene.background = null + + // Camera + const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000) + camera.position.set(0, 0, 3) + + // Renderer + const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }) + renderer.useLegacyLights = false + renderer.outputColorSpace = THREE.LinearSRGBColorSpace + renderer.setPixelRatio(window.devicePixelRatio || 1) + renderer.setSize(width, height) + container.appendChild(renderer.domElement) + + // Controls + const controls = new OrbitControls(camera, renderer.domElement) + controls.minPolarAngle = Math.PI / 2 + controls.maxPolarAngle = Math.PI / 2 + controls.enableDamping = true + controls.dampingFactor = 0.05 + + // Lights + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 3) + scene.add(ambientLight) + const cameraLight = new THREE.PointLight(0xff_ff_ff, 0.6) + camera.add(cameraLight) + scene.add(camera) + + // Player model + const { playerObject, wrapper } = createPlayerObject({ scale: 1 }) + playerObject.ears.visible = false + playerObject.cape.visible = false + + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh && child.material) { + if (Array.isArray(child.material)) { + for (const mat of child.material) setupMaterialTransparency(mat) + } else { + setupMaterialTransparency(child.material) + } + } + }) + + // Scale to fit camera view + const box = new THREE.Box3().setFromObject(wrapper) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + const cameraDistance = camera.position.z + const fov = camera.fov * Math.PI / 180 + const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance + const visibleWidth = visibleHeight * (width / height) + const scaleFactor = Math.min(visibleHeight / size.y, visibleWidth / size.x) + wrapper.scale.multiplyScalar(scaleFactor) + wrapper.position.sub(center.multiplyScalar(scaleFactor)) + wrapper.rotation.set(0, 0, 0) + scene.add(wrapper) + + // Render helper + const render = () => { renderer.render(scene, camera) } + + // Apply skin + const skinUrl = (appViewer?.playerState?.reactive as any)?.playerSkin ?? '' + void applySkinToPlayerObject(playerObject, skinUrl).then(() => { render() }) + + // Render on orbit change + controls.addEventListener('change', render) + render() + + // Cursor following + let waitingRender = false + const handlePointerMove = (event: PointerEvent) => { + const el = containerRef.current + if (!el) return + + const rect = el.getBoundingClientRect() + const centerX = rect.left + rect.width / 2 + const centerY = rect.top + rect.height / 2 + const normalizedX = (event.clientX - centerX) / (rect.width / 2) + const normalizedY = (event.clientY - centerY) / (rect.height / 2) + + const maxAngle = Math.PI * (60 / 180) + const clampedX = THREE.MathUtils.clamp(normalizedX, -1, 1) + const clampedY = THREE.MathUtils.clamp(normalizedY, -1, 1) + const headYaw = clampedX * maxAngle + const headPitch = clampedY * maxAngle + + playerObject.skin.head.rotation.y = THREE.MathUtils.lerp(playerObject.skin.head.rotation.y, headYaw, 0.1) + playerObject.skin.head.rotation.x = THREE.MathUtils.lerp(playerObject.skin.head.rotation.x, headPitch, 0.1) + playerObject.rotation.y = THREE.MathUtils.lerp(playerObject.rotation.y, headYaw * 0.3, 0.05) + + if (!waitingRender) { + requestAnimationFrame(() => { + render() + waitingRender = false + }) + waitingRender = true + } + } + window.addEventListener('pointermove', handlePointerMove) + + stateRef.current = { renderer, camera, scene, controls, playerObject, wrapper, disposed: false } + + return () => { + if (stateRef.current) stateRef.current.disposed = true + window.removeEventListener('pointermove', handlePointerMove) + controls.removeEventListener('change', render) + controls.dispose() + + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh) { + if (Array.isArray(child.material)) { + for (const mat of child.material) mat.dispose() + } else { + child.material?.dispose() + } + child.geometry?.dispose() + } + }) + if (playerObject.skin.map) { + (playerObject.skin.map as unknown as THREE.Texture).dispose() + } + renderer.dispose() + renderer.domElement?.remove() + stateRef.current = null + } + }, []) + + // Handle resize + useEffect(() => { + const s = stateRef.current + if (!s || s.disposed) return + s.renderer.setSize(width, height) + s.camera.aspect = width / height + s.camera.updateProjectionMatrix() + s.renderer.render(s.scene, s.camera) + }, [width, height]) + + return
+} From 2b5f46a12ef09985a9a8e12827ac771fa49b6ff1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 11 Apr 2026 04:14:29 +0300 Subject: [PATCH 24/42] up inv --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cbb066e9d..b5d719714 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.9", + "minecraft-inventory": "^0.1.18", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 166952583..7366baf22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.9 - version: 0.1.9(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.18 + version: 0.1.18(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.9: - resolution: {integrity: sha512-/PZFWRgbXzYVKK60G5jujnDHmmi7YlaAPOejTH6uq2lcT+z0nj4bDBczixLA0wt1Uhmnussj/z5UlcYLY7521A==} + minecraft-inventory@0.1.18: + resolution: {integrity: sha512-2b1U1YnT33sR7PUWCFP/2Tqmc+rEYYa6DsdhBp55XAlHGLReiby8ezT8iHTSGLc0g4A8obZQHpY+d5hITXOgZg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.9(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.18(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 From a8b8481c09384981e207ad9aabab0a02eb9826b3 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 11 Apr 2026 04:32:48 +0300 Subject: [PATCH 25/42] a wa y to disable notes --- src/defaultOptions.ts | 1 + src/react/inventory/Inventory.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index f629afaa2..3869af60c 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -59,6 +59,7 @@ export const defaultOptions = { // todo ui setting, maybe enable by default? waitForChunksRender: false as 'sp-only' | boolean, jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, + inventoryNotesEnabled: true as boolean, modsSupport: false, modsAutoUpdate: 'check' as 'check' | 'never' | 'always', modsUpdatePeriodCheck: 24, // hours diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 53e4dbcc6..4071ce8a9 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -74,6 +74,7 @@ export const Inventory = () => { const jeiEnabled = options.jeiEnabled === true || (Array.isArray(options.jeiEnabled) && options.jeiEnabled.includes(bot.game?.gameMode as any)) + const { inventoryNotesEnabled } = options const jeiItems = useMemo( (): JEIItem[] => (inventoryType && jeiEnabled ? getJeiItems() : []), @@ -132,7 +133,7 @@ export const Inventory = () => { jeiOnItemRightClick={gameMode === 'creative' ? handleJeiItemRightClick : undefined} onClose={handleClose} renderEntity={renderEntity} - enableNotes + enableNotes={inventoryNotesEnabled} /> From e04a9f8dd4e802a426001d6440a4e3e5584cf80d Mon Sep 17 00:00:00 2001 From: zverev Date: Sun, 12 Apr 2026 11:44:01 +0300 Subject: [PATCH 26/42] fix: mobile hotbar slot selection and inventory button --- src/react/HotbarRenderApp.tsx | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 8d95a34cf..85c561246 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -15,6 +15,7 @@ import { useAppScale } from '../scaleInterface' import { getItemNameRaw } from '../mineflayer/items' import { isInRealGameSession } from '../utils' import { triggerCommand } from '../controls' +import { openPlayerInventory } from '../inventoryWindows' import MessageFormattedString from './MessageFormattedString' import SharedHudVars from './SharedHudVars' import { textureConfig, buildItemMapper, clearInventoryCaches } from './inventory/sharedConnectorSetup' @@ -96,6 +97,16 @@ const HotbarInner = () => { }) }, [textureVersion]) + // Listen for the hotbar connector's 'open-inventory' action → open the real inventory modal. + // The hotbar connector (hotbarOnly:true) only emits windowOpen via sendAction({type:'open-inventory'}). + useEffect(() => { + return connector.subscribe((event) => { + if (event.type === 'windowOpen') { + openPlayerInventory() + } + }) + }, [connector]) + useEffect(() => { const controller = new AbortController() @@ -179,9 +190,32 @@ const HotbarInner = () => { className='hotbar' style={{ position: 'absolute', - pointerEvents: 'none', + pointerEvents: isMobile ? 'auto' : 'none', bottom: 'var(--hud-bottom-raw)', }} + onTouchStart={(e) => { + (e.currentTarget as any)._touchStart = Date.now() + }} + onTouchEnd={(e) => { + const startTime = (e.currentTarget as any)._touchStart + if (!startTime || Date.now() - startTime > 300) return // Only quick taps + + const target = e.target as HTMLElement + if (target.closest('.mc-inv-hotbar-open-inv') || target.closest('.mc-inv-hotbar-offhand')) return + + const rect = e.currentTarget.getBoundingClientRect() + const touchX = e.changedTouches[0].clientX - rect.left + const touchY = e.changedTouches[0].clientY - rect.top + + if (touchX < 0 || touchX > rect.width) return + if (touchY < 0 || touchY > rect.height) return + + const slotWidth = rect.width / 9 + const slot = Math.min(8, Math.max(0, Math.floor(touchX / slotWidth))) + if (slot !== bot.quickBarSlot) { + bot.setQuickBarSlot(slot) + } + }} > From f20a780c7683a3b6c3686bf6ac66f450b66fc361 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 12 Apr 2026 12:29:55 +0300 Subject: [PATCH 27/42] up inv --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b5d719714..45f20704d 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.18", + "minecraft-inventory": "^0.1.19", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7366baf22..44d2e8fa9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.18 - version: 0.1.18(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.19 + version: 0.1.19(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.18: - resolution: {integrity: sha512-2b1U1YnT33sR7PUWCFP/2Tqmc+rEYYa6DsdhBp55XAlHGLReiby8ezT8iHTSGLc0g4A8obZQHpY+d5hITXOgZg==} + minecraft-inventory@0.1.19: + resolution: {integrity: sha512-1ylHLXSeU8US/p0tY3BgY52vfEqdWksCWwamfh1JHJ3c5uWuSlTGldQwBSKhRncZc6o84K95cZzY5gR6LUyOPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.18(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.19(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 From 5c6c4d4cd05f671a61e8169fb9b799eb6f6ea8ff Mon Sep 17 00:00:00 2001 From: zverev Date: Sun, 12 Apr 2026 12:23:30 +0300 Subject: [PATCH 28/42] perf: defer JEI mount and cache JEI items for faster inventory open --- src/inventoryWindows.ts | 13 ++++++++++--- src/react/inventory/Inventory.tsx | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index e8d13fb32..e3bc02264 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -1,4 +1,4 @@ -import { proxy } from 'valtio' +import { proxy, subscribe } from 'valtio' import { RecipeItem } from 'minecraft-data' import { flat } from '@xmcl/text-component' @@ -24,6 +24,11 @@ export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) +// ----- JEI items cache ----- +let jeiItemsCache: JEIItem[] | null = null +const clearJeiItemsCache = () => { jeiItemsCache = null } +subscribe(jeiCustomCategories, clearJeiItemsCache) + export const onGameLoad = () => { PrismarineItem = PItem(bot.version) @@ -116,8 +121,8 @@ export const onGameLoad = () => { if (!appViewer.resourcesManager['_inventoryChangeTracked']) { appViewer.resourcesManager['_inventoryChangeTracked'] = true - appViewer.resourcesManager.on('assetsInventoryReady', () => clearInventoryCaches()) - appViewer.resourcesManager.on('assetsTexturesUpdated', () => clearInventoryCaches()) + appViewer.resourcesManager.on('assetsInventoryReady', () => { clearJeiItemsCache(); clearInventoryCaches() }) + appViewer.resourcesManager.on('assetsTexturesUpdated', () => { clearJeiItemsCache(); clearInventoryCaches() }) } } @@ -360,6 +365,7 @@ export const getItemUsages = (itemName: string): RecipeGuide[] => { * Items are enriched with texture/blockTexture data from the rendering pipeline. */ export const getJeiItems = (): JEIItem[] => { + if (jeiItemsCache) return jeiItemsCache if (!PrismarineItem) return [] const customItems: JEIItem[] = jeiCustomCategories.value.flatMap(cat => (cat.items).filter(Boolean).map(item => ({ @@ -403,5 +409,6 @@ export const getJeiItems = (): JEIItem[] => { } } + jeiItemsCache = allItems return allItems } diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 4071ce8a9..46032afd2 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -76,9 +76,25 @@ export const Inventory = () => { || (Array.isArray(options.jeiEnabled) && options.jeiEnabled.includes(bot.game?.gameMode as any)) const { inventoryNotesEnabled } = options + // Defer JEI mount by 2 animation frames so the inventory window appears first + const [jeiReady, setJeiReady] = useState(false) + useEffect(() => { + if (!inventoryType || !jeiEnabled) { + setJeiReady(false) + return + } + let cancelled = false + requestAnimationFrame(() => { + requestAnimationFrame(() => { + if (!cancelled) setJeiReady(true) + }) + }) + return () => { cancelled = true } + }, [!!inventoryType, jeiEnabled]) + const jeiItems = useMemo( - (): JEIItem[] => (inventoryType && jeiEnabled ? getJeiItems() : []), - [!!inventoryType, jeiEnabled], + (): JEIItem[] => (jeiReady ? getJeiItems() : []), + [jeiReady], ) const handleGetRecipes = useCallback( @@ -125,8 +141,8 @@ export const Inventory = () => { Date: Mon, 13 Apr 2026 14:14:45 +0300 Subject: [PATCH 29/42] feat: wire up anvil cost client-side computation --- src/react/inventory/Inventory.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 46032afd2..838bfa8a4 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -1,6 +1,7 @@ import { createPortal } from 'react-dom' import { useEffect, useMemo, useCallback, useState } from 'react' import { useSnapshot } from 'valtio' +import PItem from 'prismarine-item' import { TextureProvider, ScaleProvider, @@ -44,9 +45,19 @@ export const Inventory = () => { // Recreate connector when textures refresh so itemMapper re-extracts sprites const connector = useMemo(() => { if (!inventoryType) return null + const Item = PItem(bot.version) return createMineflayerConnector(bot as MineflayerBot, { itemMapper: buildItemMapper(bot.version), formatTitle: formatWindowTitle, + computeAnvilCost: (item1, item2) => { + if (!item1) return null + try { + const result = Item.anvil(item1 as any, item2 as any, bot.game.gameMode === 'creative', undefined) + return result.xpCost + } catch (e) { + return null + } + }, }) }, [textureVersion, !!inventoryType]) From 690027e04735eb57731296243d69286774f32673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=97=D0=B2=D0=B5=D1=80=D0=B5=D0=B2=20=D0=90=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 13 Apr 2026 14:42:21 +0300 Subject: [PATCH 30/42] feat: pass resolveEnchantmentName to inventory provider --- src/react/inventory/Inventory.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/react/inventory/Inventory.tsx b/src/react/inventory/Inventory.tsx index 838bfa8a4..9e18cc83f 100644 --- a/src/react/inventory/Inventory.tsx +++ b/src/react/inventory/Inventory.tsx @@ -149,7 +149,11 @@ export const Inventory = () => {
- + (globalThis as any).loadedData?.enchantments?.[id]?.displayName} + > Date: Tue, 14 Apr 2026 14:04:12 +0300 Subject: [PATCH 31/42] FINISH player model display perfect not duplicated code --- package.json | 2 +- pnpm-lock.yaml | 28 +- src/react/OverlayModelViewer.tsx | 381 ++++++++++++---------- src/react/inventory/PlayerModelViewer.tsx | 195 ++--------- 4 files changed, 248 insertions(+), 358 deletions(-) diff --git a/package.json b/package.json index 45f20704d..26aa4a7e5 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.19", + "minecraft-inventory": "^0.1.24", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44d2e8fa9..709fd2de0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,13 +140,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13)) minecraft-data: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.19 - version: 0.1.19(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.24 + version: 0.1.24(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -345,7 +345,7 @@ importers: version: 0.2.72 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.27 version: 0.1.27 @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.19: - resolution: {integrity: sha512-1ylHLXSeU8US/p0tY3BgY52vfEqdWksCWwamfh1JHJ3c5uWuSlTGldQwBSKhRncZc6o84K95cZzY5gR6LUyOPQ==} + minecraft-inventory@0.1.24: + resolution: {integrity: sha512-0dvdlVvDGfR+rF+bL51iR615h5STR2tzD1KDLxvpEr9MN2KBjEyNtRAzMHFwlnof3VbQsMVm5tAah/3hSRFNxg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -6731,8 +6731,8 @@ packages: resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e} version: 8.0.0 engines: {node: '>=22'} @@ -11389,7 +11389,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.106.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-item: 1.18.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -17047,12 +17047,12 @@ snapshots: dependencies: minecraft-data: 3.106.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) prismarine-item: 1.18.0 ws: 8.18.1 transitivePeerDependencies: @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.19(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.24(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 @@ -17423,7 +17423,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17438,7 +17438,7 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/6dd654c3f7f6cd0cf3260fa963a96a113e32fcb9(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 minecraft-data: 3.106.0 diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index 9a82cfc16..a72a6a349 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -127,6 +127,176 @@ const isMeshTransparent = (mesh: THREE.Mesh): boolean => { return mesh.material.transparent } +/** + * Shared player model renderer that mounts directly into its host container. + * Unlike the default OverlayModelViewer (which uses position:fixed + global modelViewerState), + * this component renders at the given dimensions without any absolute positioning — + * suitable for embedding inside inventory UI slots or any other container. + * + * The `skinUrl` prop is reactive: changing it re-applies the skin on the already-running scene. + */ +export const PlayerModelCanvas = ({ + width, + height, + skinUrl = '', + followCursor = true, + computeNormalized, +}: { + width: number + height: number + skinUrl?: string + followCursor?: boolean + /** Optional override for cursor normalisation. Receives raw clientX/Y and must return + * values in [-1, 1] relative to the desired center point. When omitted the canvas + * bounding-rect center is used (correct for inline/container mounting). Pass this when + * the viewer is inside a scaled overlay so cursor coords can be projected relative to + * the virtual window instead of the physical canvas element. */ + computeNormalized?: (clientX: number, clientY: number) => { normalizedX: number; normalizedY: number } +}) => { + const containerRef = useRef(null) + const playerObjectRef = useRef(null) + const sceneRef = useRef<{ + renderer: THREE.WebGLRenderer + camera: THREE.PerspectiveCamera + scene: THREE.Scene + controls: OrbitControls + render: () => void + } | null>(null) + // Always-fresh ref so the pointer-move closure never goes stale + const computeNormalizedRef = useRef(computeNormalized) + useEffect(() => { computeNormalizedRef.current = computeNormalized }) + + // Three.js scene setup — runs once on mount + useEffect(() => { + const container = containerRef.current + if (!container) return + + const scene = new THREE.Scene() + scene.background = null + + const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000) + camera.position.set(0, 0, 3) + + const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }) + renderer.useLegacyLights = false + renderer.outputColorSpace = THREE.LinearSRGBColorSpace + renderer.setPixelRatio(window.devicePixelRatio || 1) + renderer.setSize(width, height) + container.appendChild(renderer.domElement) + + const controls = new OrbitControls(camera, renderer.domElement) + controls.minPolarAngle = Math.PI / 2 + controls.maxPolarAngle = Math.PI / 2 + controls.enableDamping = true + controls.dampingFactor = 0.05 + + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 3) + scene.add(ambientLight) + const cameraLight = new THREE.PointLight(0xff_ff_ff, 0.6) + camera.add(cameraLight) + scene.add(camera) + + const { playerObject, wrapper } = createPlayerObject({ scale: 1 }) + playerObject.ears.visible = false + playerObject.cape.visible = false + + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh && child.material) { + const mats = Array.isArray(child.material) ? child.material : [child.material] + for (const mat of mats) setupMaterialTransparency(mat) + } + }) + + // Scale to fill camera view + const box = new THREE.Box3().setFromObject(wrapper) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + const cameraDistance = camera.position.z + const fov = camera.fov * Math.PI / 180 + const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance + const visibleWidth = visibleHeight * (width / height) + const scaleFactor = Math.min(visibleHeight / size.y, visibleWidth / size.x) + wrapper.scale.multiplyScalar(scaleFactor) + wrapper.position.sub(center.multiplyScalar(scaleFactor)) + wrapper.rotation.set(0, 0, 0) + scene.add(wrapper) + + playerObjectRef.current = playerObject + const render = () => { renderer.render(scene, camera) } + sceneRef.current = { renderer, camera, scene, controls, render } + + controls.addEventListener('change', render) + render() + + // Cursor-following: rotate head/body toward pointer + let waitingRender = false + const handlePointerMove = (event: PointerEvent) => { + const el = containerRef.current + if (!el) return + let nx: number + let ny: number + if (computeNormalizedRef.current) { + const result = computeNormalizedRef.current(event.clientX, event.clientY) + nx = THREE.MathUtils.clamp(result.normalizedX, -1, 1) + ny = THREE.MathUtils.clamp(result.normalizedY, -1, 1) + } else { + const rect = el.getBoundingClientRect() + nx = THREE.MathUtils.clamp((event.clientX - (rect.left + rect.width / 2)) / (rect.width / 2), -1, 1) + ny = THREE.MathUtils.clamp((event.clientY - (rect.top + rect.height / 2)) / (rect.height / 2), -1, 1) + } + const maxAngle = Math.PI * (60 / 180) + playerObject.skin.head.rotation.y = THREE.MathUtils.lerp(playerObject.skin.head.rotation.y, nx * maxAngle, 0.1) + playerObject.skin.head.rotation.x = THREE.MathUtils.lerp(playerObject.skin.head.rotation.x, ny * maxAngle, 0.1) + playerObject.rotation.y = THREE.MathUtils.lerp(playerObject.rotation.y, nx * maxAngle * 0.3, 0.05) + if (!waitingRender) { + requestAnimationFrame(() => { render(); waitingRender = false }) + waitingRender = true + } + } + if (followCursor) window.addEventListener('pointermove', handlePointerMove) + + return () => { + if (followCursor) window.removeEventListener('pointermove', handlePointerMove) + controls.removeEventListener('change', render) + controls.dispose() + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh) { + const mats = Array.isArray(child.material) ? child.material : [child.material] + for (const mat of mats) (mat as THREE.Material).dispose() + child.geometry?.dispose() + } + }) + if ((playerObject.skin as any).map) { + ((playerObject.skin as any).map as THREE.Texture).dispose() + } + renderer.dispose() + renderer.domElement?.remove() + sceneRef.current = null + playerObjectRef.current = null + } + }, []) + + // Reactively re-apply skin whenever the skinUrl prop changes (including initial mount) + useEffect(() => { + const playerObject = playerObjectRef.current + const s = sceneRef.current + if (!playerObject || !s) return + void applySkinToPlayerObject(playerObject, skinUrl).then(s.render) + }, [skinUrl]) + + // Propagate size changes to the running renderer + useEffect(() => { + const s = sceneRef.current + if (!s) return + s.renderer.setSize(width, height) + s.camera.aspect = width / height + s.camera.updateProjectionMatrix() + s.render() + }, [width, height]) + + return
+} + export default () => { const { model } = useSnapshot(modelViewerState) const containerRef = useRef(null) @@ -135,7 +305,6 @@ export default () => { camera: THREE.PerspectiveCamera renderer: THREE.WebGLRenderer controls: OrbitControls - playerObject?: PlayerObjectType dispose: () => void }>() const initialScale = useMemo(() => { @@ -143,14 +312,12 @@ export default () => { }, []) globalThis.sceneRef = sceneRef - // Cursor following state - const cursorPosition = useRef<{ x: number, y: number }>({ x: 0, y: 0 }) // window clientX/clientY in px - const isFollowingCursor = useRef(false) const getUiScaleFactor = (scaled?: boolean, onlyInitialScale?: boolean) => { return scaled ? (onlyInitialScale ? initialScale : currentScaling.scale) : 1 } const windowRef = useRef(null) - // Shared helper to compute normalized cursor from window clientX/Y taking scale & center into account + // Compute normalised cursor coords relative to the virtual window space (used by PlayerModelCanvas + // when mounted inside the overlay so head-tracking works across the scaled inventory window). const computeNormalizedFromClient = (clientX: number, clientY: number) => { const { positioning, followCursorCenter } = modelViewerState.model! const { windowWidth, windowHeight } = positioning @@ -407,7 +574,9 @@ export default () => { }, [model?.models]) useEffect(() => { - if (!model || !containerRef.current) return + // Steve/player model is handled declaratively by in the JSX below. + // This effect only manages the Three.js scene for GLTF/OBJ external models. + if (!model || !containerRef.current || model.steveModelSkin !== undefined) return // Setup scene const scene = new THREE.Scene() @@ -453,51 +622,6 @@ export default () => { camera.add(cameraLight) scene.add(camera) - // Cursor following function - const updatePlayerLookAt = () => { - if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return - - const { playerObject } = sceneRef.current - const { x, y } = cursorPosition.current - - // Convert clientX/clientY to normalized coordinates centered by followCursorCenter - const { normalizedX, normalizedY } = computeNormalizedFromClient(x, y) - - // Calculate head rotation based on cursor position - // Limit head movement to ±60 degrees - const maxHeadYaw = Math.PI * (60 / 180) - const maxHeadPitch = Math.PI * (60 / 180) - - const clampedX = THREE.MathUtils.clamp(normalizedX, -1, 1) - const clampedY = THREE.MathUtils.clamp(normalizedY, -1, 1) - - const headYaw = clampedX * maxHeadYaw - const headPitch = clampedY * maxHeadPitch - - // Apply head rotation with smooth interpolation - const lerpFactor = 0.1 // Smooth interpolation factor - playerObject.skin.head.rotation.y = THREE.MathUtils.lerp( - playerObject.skin.head.rotation.y, - headYaw, - lerpFactor - ) - playerObject.skin.head.rotation.x = THREE.MathUtils.lerp( - playerObject.skin.head.rotation.x, - headPitch, - lerpFactor - ) - - // Apply slight body rotation for more natural movement - const bodyYaw = headYaw * 0.3 // Body follows head but with less rotation - playerObject.rotation.y = THREE.MathUtils.lerp( - playerObject.rotation.y, - bodyYaw, - lerpFactor * 0.5 // Slower body movement - ) - - render() - } - // Render function const render = () => { renderer.render(scene, camera) @@ -518,107 +642,8 @@ export default () => { controls.addEventListener('change', render) // Initial render render() - // Render after model loads - if (model.steveModelSkin !== undefined) { - // Create player model - const { playerObject, wrapper } = createPlayerObject({ - scale: 1 // Start with base scale, will adjust below - }) - playerObject.ears.visible = false - playerObject.cape.visible = false - - // Enable shadows for player object and setup transparency - wrapper.traverse((child) => { - if (child instanceof THREE.Mesh) { - child.castShadow = true - child.receiveShadow = true - - // Setup transparency for player object materials - if (child.material) { - if (Array.isArray(child.material)) { - for (const mat of child.material) { - setupMaterialTransparency(mat) - } - } else { - setupMaterialTransparency(child.material) - } - } - } - }) - - // Calculate proper scale and positioning for camera view - const box = new THREE.Box3().setFromObject(wrapper) - const size = box.getSize(new THREE.Vector3()) - const center = box.getCenter(new THREE.Vector3()) - - // Calculate scale to fit within camera view (considering FOV and distance) - const cameraDistance = camera.position.z - const fov = camera.fov * Math.PI / 180 // Convert to radians - const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance - const visibleWidth = visibleHeight * (model.positioning.width / model.positioning.height) - - const scaleFactor = Math.min( - (visibleHeight) / size.y, - (visibleWidth) / size.x - ) - - wrapper.scale.multiplyScalar(scaleFactor) - - // Center the player object - wrapper.position.sub(center.multiplyScalar(scaleFactor)) - - // Rotate to face camera (remove the default 180° rotation) - wrapper.rotation.set(0, 0, 0) - - scene.add(wrapper) - sceneRef.current = { - ...sceneRef.current!, - playerObject - } - - void applySkinToPlayerObject(playerObject, model.steveModelSkin).then(() => { - setTimeout(render, 0) - }) - - // Set up cursor following if enabled - if (model.followCursor) { - isFollowingCursor.current = true - } - } } - // Window cursor tracking for followCursor - let lastCursorUpdate = 0 - let waitingRender = false - const handleWindowPointerMove = (event: PointerEvent) => { - if (!model.followCursor) return - - // Track cursor position as window clientX/clientY in px - const newPosition = { - x: event.clientX, - y: event.clientY - } - cursorPosition.current = newPosition - globalThis.cursorPosition = newPosition // Expose for debug - lastCursorUpdate = Date.now() - updatePlayerLookAt() - if (!waitingRender) { - requestAnimationFrame(() => { - render() - waitingRender = false - }) - waitingRender = true - } - } - - // Add window event listeners - if (model.followCursor) { - window.addEventListener('pointermove', handleWindowPointerMove) - isFollowingCursor.current = true - } - - // Note: animation state subscriptions moved to useEffect hooks below to satisfy TS types - // Store refs for cleanup sceneRef.current = { ...sceneRef.current!, @@ -630,12 +655,9 @@ export default () => { if (!model.continiousRender) { controls.removeEventListener('change', render) } - if (model.followCursor) { - window.removeEventListener('pointermove', handleWindowPointerMove) - } if (rafIdRef.current !== undefined) cancelAnimationFrame(rafIdRef.current) - // Clean up loaded models + // Clean up loaded GLTF/OBJ models for (const [modelUrl, model] of loadedModels.current) { scene.remove(model) model.traverse((child) => { @@ -661,10 +683,6 @@ export default () => { animationMixers.current.clear() gltfClips.current.clear() - const playerObject = sceneRef.current?.playerObject - if (playerObject?.skin.map) { - (playerObject.skin.map as unknown as THREE.Texture).dispose() - } renderer.dispose() renderer.domElement?.remove() } @@ -752,19 +770,40 @@ export default () => { ) })() ) : null} -
+ {model.steveModelSkin !== undefined ? ( +
+ +
+ ) : ( +
+ )}
) diff --git a/src/react/inventory/PlayerModelViewer.tsx b/src/react/inventory/PlayerModelViewer.tsx index 728ed4d6e..8fd743e21 100644 --- a/src/react/inventory/PlayerModelViewer.tsx +++ b/src/react/inventory/PlayerModelViewer.tsx @@ -1,183 +1,34 @@ -import { useEffect, useRef } from 'react' -import * as THREE from 'three' -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' -import { createPlayerObject, applySkinToPlayerObject, PlayerObjectType } from '../../../renderer/viewer/lib/createPlayerObject' - -const setupMaterialTransparency = (material: THREE.Material): void => { - if ( - material instanceof THREE.MeshStandardMaterial || - material instanceof THREE.MeshBasicMaterial || - material instanceof THREE.MeshPhongMaterial - ) { - const hasAlpha = material.alphaMap || - (material.opacity !== undefined && material.opacity < 1) || - (material.map && material.map.format === THREE.RGBAFormat) - if (hasAlpha) { - material.transparent = true - material.alphaTest = 0.01 - material.side = THREE.DoubleSide - } else { - material.transparent = false - material.side = THREE.FrontSide - } - material.needsUpdate = true - } -} - +import { useEffect, useState } from 'react' +import { subscribeKey } from 'valtio/utils' +import { loadSkinFromUsername } from '../../../renderer/viewer/lib/utils/skins' +import { PlayerModelCanvas } from '../OverlayModelViewer' + +/** + * Thin wrapper around PlayerModelCanvas that handles skin resolution: + * 1. Uses appViewer.playerState.reactive.playerSkin when available (reactive, updates on change) + * 2. Falls back to loadSkinFromUsername(bot.username) when no local skin is set + */ export function PlayerModelViewer ({ width, height }: { width: number; height: number }) { - const containerRef = useRef(null) - const stateRef = useRef<{ - renderer: THREE.WebGLRenderer - camera: THREE.PerspectiveCamera - scene: THREE.Scene - controls: OrbitControls - playerObject: PlayerObjectType - wrapper: THREE.Object3D - disposed: boolean - } | null>(null) + const [skinUrl, setSkinUrl] = useState(() => appViewer?.playerState?.reactive?.playerSkin ?? '') useEffect(() => { - const container = containerRef.current - if (!container) return - - // Scene - const scene = new THREE.Scene() - scene.background = null - - // Camera - const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000) - camera.position.set(0, 0, 3) - - // Renderer - const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }) - renderer.useLegacyLights = false - renderer.outputColorSpace = THREE.LinearSRGBColorSpace - renderer.setPixelRatio(window.devicePixelRatio || 1) - renderer.setSize(width, height) - container.appendChild(renderer.domElement) + const reactive = appViewer?.playerState?.reactive + if (!reactive) return - // Controls - const controls = new OrbitControls(camera, renderer.domElement) - controls.minPolarAngle = Math.PI / 2 - controls.maxPolarAngle = Math.PI / 2 - controls.enableDamping = true - controls.dampingFactor = 0.05 - - // Lights - const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 3) - scene.add(ambientLight) - const cameraLight = new THREE.PointLight(0xff_ff_ff, 0.6) - camera.add(cameraLight) - scene.add(camera) - - // Player model - const { playerObject, wrapper } = createPlayerObject({ scale: 1 }) - playerObject.ears.visible = false - playerObject.cape.visible = false - - wrapper.traverse((child) => { - if (child instanceof THREE.Mesh && child.material) { - if (Array.isArray(child.material)) { - for (const mat of child.material) setupMaterialTransparency(mat) - } else { - setupMaterialTransparency(child.material) - } - } + // Keep skinUrl in sync with playerState changes (e.g. resource pack swap, skin update) + const unsubscribe = subscribeKey(reactive, 'playerSkin', (skin) => { + if (skin) setSkinUrl(skin) }) - // Scale to fit camera view - const box = new THREE.Box3().setFromObject(wrapper) - const size = box.getSize(new THREE.Vector3()) - const center = box.getCenter(new THREE.Vector3()) - const cameraDistance = camera.position.z - const fov = camera.fov * Math.PI / 180 - const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance - const visibleWidth = visibleHeight * (width / height) - const scaleFactor = Math.min(visibleHeight / size.y, visibleWidth / size.x) - wrapper.scale.multiplyScalar(scaleFactor) - wrapper.position.sub(center.multiplyScalar(scaleFactor)) - wrapper.rotation.set(0, 0, 0) - scene.add(wrapper) - - // Render helper - const render = () => { renderer.render(scene, camera) } - - // Apply skin - const skinUrl = (appViewer?.playerState?.reactive as any)?.playerSkin ?? '' - void applySkinToPlayerObject(playerObject, skinUrl).then(() => { render() }) - - // Render on orbit change - controls.addEventListener('change', render) - render() - - // Cursor following - let waitingRender = false - const handlePointerMove = (event: PointerEvent) => { - const el = containerRef.current - if (!el) return - - const rect = el.getBoundingClientRect() - const centerX = rect.left + rect.width / 2 - const centerY = rect.top + rect.height / 2 - const normalizedX = (event.clientX - centerX) / (rect.width / 2) - const normalizedY = (event.clientY - centerY) / (rect.height / 2) - - const maxAngle = Math.PI * (60 / 180) - const clampedX = THREE.MathUtils.clamp(normalizedX, -1, 1) - const clampedY = THREE.MathUtils.clamp(normalizedY, -1, 1) - const headYaw = clampedX * maxAngle - const headPitch = clampedY * maxAngle - - playerObject.skin.head.rotation.y = THREE.MathUtils.lerp(playerObject.skin.head.rotation.y, headYaw, 0.1) - playerObject.skin.head.rotation.x = THREE.MathUtils.lerp(playerObject.skin.head.rotation.x, headPitch, 0.1) - playerObject.rotation.y = THREE.MathUtils.lerp(playerObject.rotation.y, headYaw * 0.3, 0.05) - - if (!waitingRender) { - requestAnimationFrame(() => { - render() - waitingRender = false - }) - waitingRender = true - } - } - window.addEventListener('pointermove', handlePointerMove) - - stateRef.current = { renderer, camera, scene, controls, playerObject, wrapper, disposed: false } - - return () => { - if (stateRef.current) stateRef.current.disposed = true - window.removeEventListener('pointermove', handlePointerMove) - controls.removeEventListener('change', render) - controls.dispose() - - wrapper.traverse((child) => { - if (child instanceof THREE.Mesh) { - if (Array.isArray(child.material)) { - for (const mat of child.material) mat.dispose() - } else { - child.material?.dispose() - } - child.geometry?.dispose() - } + // If there is no locally cached skin, fetch it from Mojang by username + if (!reactive.playerSkin) { + void loadSkinFromUsername(bot.username, 'skin').then(url => { + if (url) setSkinUrl(url) }) - if (playerObject.skin.map) { - (playerObject.skin.map as unknown as THREE.Texture).dispose() - } - renderer.dispose() - renderer.domElement?.remove() - stateRef.current = null } - }, []) - // Handle resize - useEffect(() => { - const s = stateRef.current - if (!s || s.disposed) return - s.renderer.setSize(width, height) - s.camera.aspect = width / height - s.camera.updateProjectionMatrix() - s.renderer.render(s.scene, s.camera) - }, [width, height]) + return unsubscribe + }, []) - return
+ return } From 632378311db3ad1ab97ec7d8b01a4d7bf588b6b1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 14 Apr 2026 23:25:37 +0300 Subject: [PATCH 32/42] up mouse --- package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- src/mineflayer/plugins/mouse.ts | 4 +++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 26aa4a7e5..6e2a037aa 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.24", + "minecraft-inventory": "^0.1.25", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -157,7 +157,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.27", + "mineflayer-mouse": "^0.1.28", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 709fd2de0..443b7e751 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.24 - version: 0.1.24(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.25 + version: 0.1.25(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -347,8 +347,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.27 - version: 0.1.27 + specifier: ^0.1.28 + version: 0.1.28 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.24: - resolution: {integrity: sha512-0dvdlVvDGfR+rF+bL51iR615h5STR2tzD1KDLxvpEr9MN2KBjEyNtRAzMHFwlnof3VbQsMVm5tAah/3hSRFNxg==} + minecraft-inventory@0.1.25: + resolution: {integrity: sha512-CfiTVW8RcgPDyY6y/PMAB/C8EoKKhFwwtNIEqa48MlJUjnGDhKLzUFsBvHqEsj5hWnxex8X9VgoSrF7sGpJ1QA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -6727,8 +6727,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.27: - resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} + mineflayer-mouse@0.1.28: + resolution: {integrity: sha512-31r73CqU7eiv8cQSVA+NMR0WfA8Qs+p6of+Bd7ApWzR4a56W6fgfY3MExNmxxbb1/w9IbxTcMhDbcoKmE/5+nw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e: @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.24(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.25(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 @@ -17429,7 +17429,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.27: + mineflayer-mouse@0.1.28: dependencies: change-case: 5.4.4 debug: 4.4.3 diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 14e193455..1ca73ceb2 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -45,7 +45,9 @@ function cursorBlockDisplay (bot: Bot) { } export default (bot: Bot) => { - bot.loadPlugin(createMouse({})) + bot.loadPlugin(createMouse({ + useMineflayerInteractMethods: false, + })) domListeners(bot) cursorBlockDisplay(bot) From 62aad92230b99aa6ba4f260bf6b91752943ddae0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 16 Apr 2026 20:06:38 +0300 Subject: [PATCH 33/42] revert mouse upadte & up inv --- package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 6e2a037aa..987625233 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", "minecraft-data": "3.106.0", - "minecraft-inventory": "^0.1.25", + "minecraft-inventory": "^0.1.27", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -157,7 +157,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.28", + "mineflayer-mouse": "0.1.27", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 443b7e751..f00f029a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: 3.106.0 version: 3.106.0 minecraft-inventory: - specifier: ^0.1.25 - version: 0.1.25(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.1.27 + version: 0.1.27(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) @@ -347,8 +347,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.28 - version: 0.1.28 + specifier: 0.1.27 + version: 0.1.27 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -6699,8 +6699,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory@0.1.25: - resolution: {integrity: sha512-CfiTVW8RcgPDyY6y/PMAB/C8EoKKhFwwtNIEqa48MlJUjnGDhKLzUFsBvHqEsj5hWnxex8X9VgoSrF7sGpJ1QA==} + minecraft-inventory@0.1.27: + resolution: {integrity: sha512-Em+9uK4H2ad3X0uoLkwA9Rh0qJCIHTogGsGRji/pFWi6644xcpKU2zgxwPE1S71wU+uG9C/wVlpNoES33ucVhQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@xmcl/text-component': '*' @@ -6727,8 +6727,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.28: - resolution: {integrity: sha512-31r73CqU7eiv8cQSVA+NMR0WfA8Qs+p6of+Bd7ApWzR4a56W6fgfY3MExNmxxbb1/w9IbxTcMhDbcoKmE/5+nw==} + mineflayer-mouse@0.1.27: + resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e: @@ -17363,7 +17363,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory@0.1.25(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + minecraft-inventory@0.1.27(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@xmcl/text-component': 2.1.3 react: 18.3.1 @@ -17429,7 +17429,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.28: + mineflayer-mouse@0.1.27: dependencies: change-case: 5.4.4 debug: 4.4.3 From 4ed11b5c2da41ade7f1d7b9302b8d2a97d19974b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 16 Apr 2026 20:28:27 +0300 Subject: [PATCH 34/42] re-update --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 987625233..323913c5e 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "0.1.27", + "mineflayer-mouse": "0.1.28", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f00f029a1..3db4755bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -347,8 +347,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e(encoding@0.1.13) mineflayer-mouse: - specifier: 0.1.27 - version: 0.1.27 + specifier: 0.1.28 + version: 0.1.28 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -6727,8 +6727,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.27: - resolution: {integrity: sha512-rir2u/WeQfJECbfvuzzwrybmMtFPYjQMpI/nx6Cv78keRlNPyjifnuCBwFJb17NcQgdnhpL9U8RST0010qhx+A==} + mineflayer-mouse@0.1.28: + resolution: {integrity: sha512-31r73CqU7eiv8cQSVA+NMR0WfA8Qs+p6of+Bd7ApWzR4a56W6fgfY3MExNmxxbb1/w9IbxTcMhDbcoKmE/5+nw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/87f87433d8164f1d1bb6247bc72f70569ff39b9e: @@ -17429,7 +17429,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.27: + mineflayer-mouse@0.1.28: dependencies: change-case: 5.4.4 debug: 4.4.3 From f94d9448a8fb9f97ba766a40704a866fa0bd0e7a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 16 Apr 2026 20:41:19 +0300 Subject: [PATCH 35/42] add settings! --- src/defaultOptions.ts | 6 ++- src/optionsGuiScheme.tsx | 67 ++++++++++++++++++++++++++++--- src/optionsStorage.ts | 4 ++ src/react/inventory/Inventory.tsx | 14 ++++--- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 3869af60c..264ecfaf0 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -58,8 +58,10 @@ export const defaultOptions = { preciseMouseInput: false, // todo ui setting, maybe enable by default? waitForChunksRender: false as 'sp-only' | boolean, - jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, - inventoryNotesEnabled: true as boolean, + inventoryJei: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, + inventoryNotes: true as boolean, + inventoryPlaceholders: false, + inventoryPlayerModel: true, modsSupport: false, modsAutoUpdate: 'check' as 'check' | 'never' | 'always', modsUpdatePeriodCheck: 24, // hours diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 64189dbc4..c821cbc45 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -194,16 +194,16 @@ export const guiOptionsScheme: { return } if (choice === 'Uninstall') { - // todo make hidable + // todo make hidable setLoadingScreenStatus('Uninstalling texturepack') await uninstallResourcePack() setLoadingScreenStatus(undefined) } } else { - // if (!fsState.inMemorySave && isGameActive(false)) { - // alert('Unable to install resource pack in loaded save for now') - // return - // } + // if (!fsState.inMemorySave && isGameActive(false)) { + // alert('Unable to install resource pack in loaded save for now') + // return + // } openFilePicker('resourcepack') } }} @@ -255,6 +255,13 @@ export const guiOptionsScheme: { unit: '', delayApply: true, }, + }, + { + custom () { + return