From 9a25d60991f89e4de7638ea209313f3d7b95aebc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 5 Sep 2025 21:35:16 +0300 Subject: [PATCH 01/22] a little less fog --- renderer/viewer/three/skyboxRenderer.ts | 28 +++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index aa8c3bb6..1039bc59 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -76,12 +76,14 @@ export class SkyboxRenderer { } } - update (cameraPosition: THREE.Vector3, newViewDistance: number) { + update (cameraPosition: THREE.Vector3, newViewDistance: number, partialTicks = 0) { if (newViewDistance !== this.viewDistance) { this.viewDistance = newViewDistance - this.updateSkyColors() } + // Update partial ticks for interpolation + this.partialTicks = partialTicks + if (this.mesh) { // Update skybox position this.mesh.position.copy(cameraPosition) @@ -89,7 +91,7 @@ export class SkyboxRenderer { // Update gradient sky position this.skyMesh.position.copy(cameraPosition) this.voidMesh?.position.copy(cameraPosition) - this.updateSkyColors() // Update colors based on time of day + this.updateSkyColors() } } @@ -156,8 +158,8 @@ export class SkyboxRenderer { this.updateSkyColors() } - private getFogColor (partialTicks = 0): THREE.Vector3 { - const angle = this.getCelestialAngle(partialTicks) + private getFogColor (): THREE.Vector3 { + const angle = this.getCelestialAngle() let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5 rotation = Math.max(0, Math.min(1, rotation)) @@ -172,8 +174,8 @@ export class SkyboxRenderer { return new THREE.Vector3(x, y, z) } - private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 { - const angle = this.getCelestialAngle(partialTicks) + private getSkyColor (x = 0, z = 0): THREE.Vector3 { + const angle = this.getCelestialAngle() let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5 if (brightness < 0) brightness = 0 @@ -210,8 +212,8 @@ export class SkyboxRenderer { return angle } - private getCelestialAngle (partialTicks: number): number { - return this.calculateCelestialAngle(this.worldTime, partialTicks) + private getCelestialAngle (): number { + return this.calculateCelestialAngle(this.worldTime, this.partialTicks) } private getTemperature (x: number, z: number): number { @@ -302,9 +304,9 @@ export class SkyboxRenderer { const viewDistance = this.viewDistance * 16 const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25 - const angle = this.getCelestialAngle(this.partialTicks) - const skyColor = this.getSkyColor(0, 0, this.partialTicks) - const fogColor = this.getFogColor(this.partialTicks) + const angle = this.getCelestialAngle() + const skyColor = this.getSkyColor(0, 0) + const fogColor = this.getFogColor() const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5 const clampedBrightness = Math.max(0, Math.min(1, brightness)) @@ -317,7 +319,7 @@ export class SkyboxRenderer { const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness this.scene.background = new THREE.Color(red, green, blue) - this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2) + this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2.5) ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( From 265d02d18d46fbde79437857eb58254fb68b1cf0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 7 Sep 2025 18:23:13 +0000 Subject: [PATCH 02/22] up protocol for 1.21.8 --- pnpm-lock.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6188b6d..fd8be62b 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/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(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: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@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/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.21 version: 0.1.21 @@ -6668,8 +6668,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074} + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} version: 1.61.0 engines: {node: '>=22'} @@ -6688,8 +6688,8 @@ packages: resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e} version: 8.0.0 engines: {node: '>=22'} @@ -11344,7 +11344,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(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 @@ -13106,7 +13106,7 @@ snapshots: long: 5.3.1 mc-bridge: 0.1.3(minecraft-data@3.92.0) minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13143,7 +13143,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16990,12 +16990,12 @@ snapshots: dependencies: minecraft-data: 3.92.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(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/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17313,7 +17313,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17365,7 +17365,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/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17380,11 +17380,11 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 From 7f7a14ac65105649621ed825245ca23c2f2c5f85 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:19:38 +0300 Subject: [PATCH 03/22] feat: Add overlay model viewer. Already integrated into inventory to display player! --- renderer/viewer/lib/createPlayerObject.ts | 55 +++ renderer/viewer/three/entities.ts | 7 +- src/inventoryWindows.ts | 35 ++ src/react/OverlayModelViewer.tsx | 505 ++++++++++++++++++++++ src/reactUi.tsx | 2 + 5 files changed, 598 insertions(+), 6 deletions(-) create mode 100644 renderer/viewer/lib/createPlayerObject.ts create mode 100644 src/react/OverlayModelViewer.tsx diff --git a/renderer/viewer/lib/createPlayerObject.ts b/renderer/viewer/lib/createPlayerObject.ts new file mode 100644 index 00000000..836c8062 --- /dev/null +++ b/renderer/viewer/lib/createPlayerObject.ts @@ -0,0 +1,55 @@ +import { PlayerObject, PlayerAnimation } from 'skinview3d' +import * as THREE from 'three' +import { WalkingGeneralSwing } from '../three/entity/animations' +import { loadSkinImage, stevePngUrl } from './utils/skins' + +export type PlayerObjectType = PlayerObject & { + animation?: PlayerAnimation + realPlayerUuid: string + realUsername: string +} + +export function createPlayerObject (options: { + username?: string + uuid?: string + scale?: number +}): { + playerObject: PlayerObjectType + wrapper: THREE.Group + } { + const wrapper = new THREE.Group() + const playerObject = new PlayerObject() as PlayerObjectType + + playerObject.realPlayerUuid = options.uuid ?? '' + playerObject.realUsername = options.username ?? '' + playerObject.position.set(0, 16, 0) + + // fix issues with starfield + playerObject.traverse((obj) => { + if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) { + obj.material.transparent = true + } + }) + + wrapper.add(playerObject as any) + const scale = options.scale ?? (1 / 16) + wrapper.scale.set(scale, scale, scale) + wrapper.rotation.set(0, Math.PI, 0) + + // Set up animation + playerObject.animation = new WalkingGeneralSwing() + ;(playerObject.animation as WalkingGeneralSwing).isMoving = false + playerObject.animation.update(playerObject, 0) + + return { playerObject, wrapper } +} + +export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => { + return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => { + const skinTexture = new THREE.CanvasTexture(canvas) + skinTexture.magFilter = THREE.NearestFilter + skinTexture.minFilter = THREE.NearestFilter + skinTexture.needsUpdate = true + playerObject.skin.map = skinTexture as any + }).catch(console.error) +} diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index 7849686b..fad30182 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -20,6 +20,7 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState' import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins' import { renderComponent } from '../sign-renderer' import { createCanvas } from '../lib/utils' +import { PlayerObjectType } from '../lib/createPlayerObject' import { getBlockMeshFromModel } from './holdingBlock' import { createItemMesh } from './itemMesh' import * as Entity from './entity/EntityMesh' @@ -33,12 +34,6 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl) export const TWEEN_DURATION = 120 -type PlayerObjectType = PlayerObject & { - animation?: PlayerAnimation - realPlayerUuid: string - realUsername: string -} - function convert2sComplementToHex (complement: number) { if (complement < 0) { complement = (0xFF_FF_FF_FF + complement + 1) >>> 0 diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 166e42a7..d16fee20 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -12,6 +12,7 @@ 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 { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' @@ -23,6 +24,7 @@ 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' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -40,6 +42,34 @@ export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) +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, + }, + // 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(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive + remotePlayerSkin = a ?? '' + return remotePlayerSkin + }) + } +} + export const onGameLoad = () => { version = bot.version @@ -392,7 +422,12 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = miscUiState.displaySearchInput = false destroyFn() skipClosePacketSending = false + + modelViewerState.model = undefined }) + if (type === undefined) { + showInventoryPlayer() + } cleanLoadedImagesCache() const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx new file mode 100644 index 00000000..24dc836d --- /dev/null +++ b/src/react/OverlayModelViewer.tsx @@ -0,0 +1,505 @@ +import { proxy, useSnapshot, subscribe } from 'valtio' +import { useEffect, useMemo, useRef } from 'react' +import * as THREE from 'three' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' +import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' +import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject' +import { currentScaling } from '../scaleInterface' + +THREE.ColorManagement.enabled = false + +export const modelViewerState = proxy({ + model: undefined as undefined | { + models?: string[] // Array of model URLs (URL itself is the cache key) + steveModelSkin?: string + debug?: boolean + // absolute positioning + positioning: { + windowWidth: number + windowHeight: number + x: number + y: number + width: number + height: number + scaled?: boolean + onlyInitialScale?: boolean + followCursor?: boolean + } + modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } } + resetRotationOnReleae?: boolean + continiousRender?: boolean + } +}) +globalThis.modelViewerState = modelViewerState + +// Global debug function to get camera and model values +globalThis.getModelViewerValues = () => { + const scene = globalThis.sceneRef?.current + if (!scene) return null + + const { camera, playerObject } = scene + if (!playerObject) return null + + const wrapper = playerObject.parent + if (!wrapper) return null + + const box = new THREE.Box3().setFromObject(wrapper) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + + return { + camera: { + position: camera.position.clone(), + fov: camera.fov, + aspect: camera.aspect + }, + model: { + position: wrapper.position.clone(), + rotation: wrapper.rotation.clone(), + scale: wrapper.scale.clone(), + size, + center + }, + cursor: { + position: globalThis.cursorPosition || { x: 0, y: 0 }, + normalized: globalThis.cursorPosition ? { + x: globalThis.cursorPosition.x * 2 - 1, + y: globalThis.cursorPosition.y * 2 - 1 + } : { x: 0, y: 0 } + }, + visibleArea: { + height: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z, + width: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z * camera.aspect + } + } +} + +export default () => { + const { model } = useSnapshot(modelViewerState) + const containerRef = useRef(null) + const sceneRef = useRef<{ + scene: THREE.Scene + camera: THREE.PerspectiveCamera + renderer: THREE.WebGLRenderer + controls: OrbitControls + playerObject?: PlayerObjectType + dispose: () => void + }>() + const initialScale = useMemo(() => { + return currentScaling.scale + }, []) + globalThis.sceneRef = sceneRef + + // Cursor following state + const cursorPosition = useRef({ x: 0, y: 0 }) + const isFollowingCursor = useRef(false) + + // Model management state + const loadedModels = useRef>(new Map()) + const modelLoaders = useRef>(new Map()) + + // Model management functions + const loadModel = (modelUrl: string) => { + if (loadedModels.current.has(modelUrl)) return // Already loaded + + const isGLTF = modelUrl.toLowerCase().endsWith('.gltf') || modelUrl.toLowerCase().endsWith('.glb') + const loader = isGLTF ? new GLTFLoader() : new OBJLoader() + modelLoaders.current.set(modelUrl, loader) + + const onLoad = (object: THREE.Object3D) => { + // Apply customization if available + const customization = model?.modelCustomization?.[modelUrl] + if (customization) { + object.traverse((child) => { + if (child instanceof THREE.Mesh && child.material) { + const material = child.material as THREE.MeshStandardMaterial + if (customization.color) { + material.color.setHex(parseInt(customization.color.replace('#', ''), 16)) + } + if (customization.opacity !== undefined) { + material.opacity = customization.opacity + material.transparent = customization.opacity < 1 + } + if (customization.metalness !== undefined) { + material.metalness = customization.metalness + } + if (customization.roughness !== undefined) { + material.roughness = customization.roughness + } + } + }) + } + + // Center and scale model + const box = new THREE.Box3().setFromObject(object) + const center = box.getCenter(new THREE.Vector3()) + const size = box.getSize(new THREE.Vector3()) + const maxDim = Math.max(size.x, size.y, size.z) + const scale = 2 / maxDim + object.scale.setScalar(scale) + object.position.sub(center.multiplyScalar(scale)) + + // Store the model using URL as key + loadedModels.current.set(modelUrl, object) + sceneRef.current?.scene.add(object) + + // Trigger render + if (sceneRef.current) { + setTimeout(() => { + const render = () => sceneRef.current?.renderer.render(sceneRef.current.scene, sceneRef.current.camera) + render() + }, 0) + } + } + + if (isGLTF) { + (loader as GLTFLoader).load(modelUrl, (gltf) => { + onLoad(gltf.scene) + }) + } else { + (loader as OBJLoader).load(modelUrl, onLoad) + } + } + + const removeModel = (modelUrl: string) => { + const model = loadedModels.current.get(modelUrl) + if (model) { + sceneRef.current?.scene.remove(model) + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + if (child.material) { + if (Array.isArray(child.material)) { + for (const mat of child.material) { + mat.dispose() + } + } else { + child.material.dispose() + } + } + if (child.geometry) { + child.geometry.dispose() + } + } + }) + loadedModels.current.delete(modelUrl) + } + modelLoaders.current.delete(modelUrl) + } + + // Subscribe to model changes + useEffect(() => { + if (!modelViewerState.model?.models) return + + const modelsChanged = () => { + const currentModels = modelViewerState.model?.models || [] + const currentModelUrls = new Set(currentModels) + const loadedModelUrls = new Set(loadedModels.current.keys()) + + // Remove models that are no longer in the state + for (const modelUrl of loadedModelUrls) { + if (!currentModelUrls.has(modelUrl)) { + removeModel(modelUrl) + } + } + + // Add new models + for (const modelUrl of currentModels) { + if (!loadedModelUrls.has(modelUrl)) { + loadModel(modelUrl) + } + } + } + const unsubscribe = subscribe(modelViewerState.model.models, modelsChanged) + + let unmounted = false + setTimeout(() => { + if (unmounted) return + modelsChanged() + }) + + return () => { + unmounted = true + unsubscribe?.() + } + }, [model?.models]) + + useEffect(() => { + if (!model || !containerRef.current) return + + // Setup scene + const scene = new THREE.Scene() + scene.background = null // Transparent background + + // Setup camera with optimal settings for player model viewing + const camera = new THREE.PerspectiveCamera( + 50, // Reduced FOV for better model viewing + model.positioning.width / model.positioning.height, + 0.1, + 1000 + ) + camera.position.set(0, 0, 3) // Position camera to view player model optimally + + // Setup renderer with pixel density awareness + const renderer = new THREE.WebGLRenderer({ alpha: true }) + let scale = window.devicePixelRatio || 1 + if (modelViewerState.model?.positioning.scaled) { + scale *= currentScaling.scale + } + renderer.setPixelRatio(scale) + renderer.setSize(model.positioning.width, model.positioning.height) + containerRef.current.appendChild(renderer.domElement) + + // Setup controls + const controls = new OrbitControls(camera, renderer.domElement) + // controls.enableZoom = false + // controls.enablePan = false + controls.minPolarAngle = Math.PI / 2 // Lock vertical rotation + controls.maxPolarAngle = Math.PI / 2 + controls.enableDamping = true + controls.dampingFactor = 0.05 + + // Add ambient light + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 1) + scene.add(ambientLight) + + // Cursor following function + const updatePlayerLookAt = () => { + if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return + + const { playerObject } = sceneRef.current + const { x, y } = cursorPosition.current + + // Convert 0-1 cursor position to normalized coordinates (-1 to 1) + const normalizedX = x * 2 - 1 + const normalizedY = y * 2 - 1 // Inverted: top of screen = negative pitch, bottom = positive pitch + + // Calculate head rotation based on cursor position + // Limit head movement to realistic angles + const maxHeadYaw = Math.PI / 3 // 60 degrees + const maxHeadPitch = Math.PI / 4 // 45 degrees + + const headYaw = normalizedX * maxHeadYaw + const headPitch = normalizedY * 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) + } + + // Setup animation/render strategy + if (model.continiousRender) { + // Continuous animation loop + const animate = () => { + requestAnimationFrame(animate) + render() + } + animate() + } else { + // Render only on camera movement + 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 + }) + + // 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.positioning.followCursor) { + isFollowingCursor.current = true + } + } + } + + // Window cursor tracking for followCursor + let lastCursorUpdate = 0 + let waitingRender = false + const handleWindowPointerMove = (event: PointerEvent) => { + if (!model.positioning.followCursor) return + + // Track cursor position as 0-1 across the entire window + const newPosition = { + x: event.clientX / window.innerWidth, + y: event.clientY / window.innerHeight + } + 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.positioning.followCursor) { + window.addEventListener('pointermove', handleWindowPointerMove) + isFollowingCursor.current = true + } + + // Store refs for cleanup + sceneRef.current = { + ...sceneRef.current!, + scene, + camera, + renderer, + controls, + dispose () { + if (!model.continiousRender) { + controls.removeEventListener('change', render) + } + if (model.positioning.followCursor) { + window.removeEventListener('pointermove', handleWindowPointerMove) + } + + // Clean up loaded models + for (const [modelUrl, model] of loadedModels.current) { + scene.remove(model) + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + if (child.material) { + if (Array.isArray(child.material)) { + for (const mat of child.material) { + mat.dispose() + } + } else { + child.material.dispose() + } + } + if (child.geometry) { + child.geometry.dispose() + } + } + }) + } + loadedModels.current.clear() + modelLoaders.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() + } + } + + return () => { + sceneRef.current?.dispose() + } + }, [model]) + + if (!model) return null + + const { x, y, width, height, scaled, onlyInitialScale } = model.positioning + const { windowWidth } = model.positioning + const { windowHeight } = model.positioning + const scaleValue = onlyInitialScale ? initialScale : 'var(--guiScale)' + + return ( +
+
+
+
+
+ ) +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 1e6f13eb..6339686e 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -68,6 +68,7 @@ import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' import FireRenderer from './react/FireRenderer' import MonacoEditor from './react/MonacoEditor' +import OverlayModelViewer from './react/OverlayModelViewer' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -259,6 +260,7 @@ const App = () => {
+ From 739a6fad24478c5b13b03f96babd853c569dcbbc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:34:17 +0300 Subject: [PATCH 04/22] fix lockfile --- pnpm-lock.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd8be62b..516370ce 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/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 @@ -345,7 +345,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@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/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.21 version: 0.1.21 @@ -6670,7 +6670,7 @@ packages: minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} - version: 1.61.0 + version: 1.62.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6688,8 +6688,8 @@ packages: resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659} version: 8.0.0 engines: {node: '>=22'} @@ -11344,7 +11344,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(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 @@ -16990,12 +16990,12 @@ snapshots: dependencies: minecraft-data: 3.92.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(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/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17365,7 +17365,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/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17380,7 +17380,7 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 From 0b1183f541bb76a47ed15cfd63ef59340f91be9f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:36:09 +0300 Subject: [PATCH 05/22] up minecraft-data --- package.json | 4 ++-- scripts/makeOptimizedMcData.mjs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b5f66bfb..ff673726 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "jszip": "^3.10.1", "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -205,7 +205,7 @@ "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prismarine-physics": "github:zardoy/prismarine-physics", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index 76e0f1c2..a572d067 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -371,6 +371,7 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS const { defaultVersion } = MCProtocol const data = MinecraftData(defaultVersion) +console.log('defaultVersion', defaultVersion, !!data) const initialMcData = { [defaultVersion]: { version: data.version, From f24cb49a8728a63cab19b8cc76fdbfb1888566eb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:55:43 +0300 Subject: [PATCH 06/22] up lockfile --- pnpm-lock.yaml | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 516370ce..5bcd74a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: diamond-square: github:zardoy/diamond-square prismarine-block: github:zardoy/prismarine-block#next-era prismarine-world: github:zardoy/prismarine-world#next-era - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything prismarine-physics: github:zardoy/prismarine-physics minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master @@ -142,8 +142,8 @@ importers: specifier: ^0.1.23 version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) minecraft-data: - specifier: 3.92.0 - version: 3.92.0 + specifier: 3.98.0 + version: 3.98.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) @@ -170,7 +170,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.92.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -436,7 +436,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6448,7 +6448,7 @@ packages: resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} @@ -6658,8 +6658,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minecraft-data@3.92.0: - resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==} + minecraft-data@3.98.0: + resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==} minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} @@ -7387,7 +7387,7 @@ packages: prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} peerDependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: ^1.1.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: @@ -11343,7 +11343,7 @@ snapshots: '@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 @@ -13104,18 +13104,18 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - mc-bridge: 0.1.3(minecraft-data@3.92.0) - minecraft-data: 3.92.0 + mc-bridge: 0.1.3(minecraft-data@3.98.0) + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(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/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13142,16 +13142,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(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/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14542,8 +14542,8 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: - minecraft-data: 3.92.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + minecraft-data: 3.98.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -16986,9 +16986,9 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mc-bridge@0.1.3(minecraft-data@3.92.0): + mc-bridge@0.1.3(minecraft-data@3.98.0): dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)): dependencies: @@ -17302,7 +17302,7 @@ snapshots: min-indent@1.0.1: {} - minecraft-data@3.92.0: {} + minecraft-data@3.98.0: {} minecraft-folder-path@1.2.0: {} @@ -17322,7 +17322,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) endian-toggle: 0.0.0 lodash.merge: 4.6.2 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 @@ -17383,12 +17383,12 @@ snapshots: mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.98.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/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 @@ -18174,15 +18174,15 @@ snapshots: transitivePeerDependencies: - supports-color - prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0): + prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0): dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: 1.11.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: - minecraft-data: 3.92.0 - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 @@ -18194,9 +18194,9 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0): dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18225,14 +18225,14 @@ snapshots: prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 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.92.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.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/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 @@ -18254,13 +18254,13 @@ snapshots: prismarine-registry@1.11.0: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-schematic@1.2.3: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c From 1525fac2a192f1fe7e2f858480c444948bbe9651 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:22:24 +0300 Subject: [PATCH 07/22] fix: some visual camera world view issues (visible lines between blocks) --- renderer/viewer/three/cameraShake.ts | 25 ++++++++++++++++++++-- renderer/viewer/three/world/cursorBlock.ts | 18 ++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/renderer/viewer/three/cameraShake.ts b/renderer/viewer/three/cameraShake.ts index 593b4628..7b159509 100644 --- a/renderer/viewer/three/cameraShake.ts +++ b/renderer/viewer/three/cameraShake.ts @@ -80,8 +80,12 @@ export class CameraShake { camera.setRotationFromQuaternion(yawQuat) } else { // For regular camera, apply all rotations - const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch) - const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw) + // Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees) + const pitchOffset = this.addAntiZfightingOffset(this.basePitch) + const yawOffset = this.addAntiZfightingOffset(this.baseYaw) + + const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset) + const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset) const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle)) // Combine rotations in the correct order: pitch -> yaw -> roll const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat) @@ -96,4 +100,21 @@ export class CameraShake { private easeInOut (t: number): number { return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2 } + + private addAntiZfightingOffset (angle: number): number { + const offset = 0.001 // Very small offset in radians (about 0.057 degrees) + + // Check if the angle is close to ideal angles (0, π/2, π, 3π/2) + const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) + const tolerance = 0.01 // Tolerance for considering an angle "ideal" + + if (Math.abs(normalizedAngle) < tolerance || + Math.abs(normalizedAngle - Math.PI / 2) < tolerance || + Math.abs(normalizedAngle - Math.PI) < tolerance || + Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) { + return angle + offset + } + + return angle + } } diff --git a/renderer/viewer/three/world/cursorBlock.ts b/renderer/viewer/three/world/cursorBlock.ts index b71c1b8d..a03a6999 100644 --- a/renderer/viewer/three/world/cursorBlock.ts +++ b/renderer/viewer/three/world/cursorBlock.ts @@ -28,7 +28,7 @@ export class CursorBlock { } cursorLineMaterial: LineMaterial - interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null + interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null prevColor: string | undefined blockBreakMesh: THREE.Mesh breakTextures: THREE.Texture[] = [] @@ -62,6 +62,13 @@ export class CursorBlock { this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => { this.updateLineMaterial() }) + // todo figure out why otherwise fog from skybox breaks it + setTimeout(() => { + this.updateLineMaterial() + if (this.interactionLines) { + this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true) + } + }) } // Update functions @@ -69,6 +76,9 @@ export class CursorBlock { const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative' const pixelRatio = this.worldRenderer.renderer.getPixelRatio() + if (this.cursorLineMaterial) { + this.cursorLineMaterial.dispose() + } this.cursorLineMaterial = new LineMaterial({ color: (() => { switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) { @@ -115,8 +125,8 @@ export class CursorBlock { } } - setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void { - if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { + setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void { + if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) { return } if (this.interactionLines !== null) { @@ -140,7 +150,7 @@ export class CursorBlock { } this.worldRenderer.scene.add(group) group.visible = !this.cursorLinesHidden - this.interactionLines = { blockPos, mesh: group } + this.interactionLines = { blockPos, mesh: group, shapePositions } } render () { From c4097975bf8ea14764bf7f2b70aa00e45dc5b399 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:29:34 +0300 Subject: [PATCH 08/22] add a way to disable sky box for old behavior (not tested) --- renderer/viewer/lib/worldrendererCommon.ts | 1 + renderer/viewer/three/skyboxRenderer.ts | 25 ++++++++++++++++++++- renderer/viewer/three/worldrendererThree.ts | 5 ++++- src/defaultOptions.ts | 1 + src/watchOptions.ts | 4 ++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index e2455915..4140e3fa 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -47,6 +47,7 @@ export const defaultWorldRendererConfig = { smoothLighting: true, enableLighting: true, starfield: true, + defaultSkybox: true, renderEntities: true, extraBlockRenderers: true, foreground: true, diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index aa8c3bb6..cd7bd879 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -18,7 +18,7 @@ export class SkyboxRenderer { private fogBrightness = 0 private prevFogBrightness = 0 - constructor (private readonly scene: THREE.Scene, public initialImage: string | null) { + constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { if (!initialImage) { this.createGradientSky() } @@ -119,6 +119,12 @@ export class SkyboxRenderer { this.updateSkyColors() } + // Update default skybox setting + updateDefaultSkybox (defaultSkybox: boolean) { + this.defaultSkybox = defaultSkybox + this.updateSkyColors() + } + private createGradientSky () { const size = 64 const scale = 256 / size + 2 @@ -279,6 +285,23 @@ export class SkyboxRenderer { private updateSkyColors () { if (!this.skyMesh || !this.voidMesh) return + // If default skybox is disabled, hide the skybox meshes + if (!this.defaultSkybox) { + this.skyMesh.visible = false + this.voidMesh.visible = false + if (this.mesh) { + this.mesh.visible = false + } + return + } + + // Show skybox meshes when default skybox is enabled + this.skyMesh.visible = true + this.voidMesh.visible = true + if (this.mesh) { + this.mesh.visible = true + } + // Update fog brightness with smooth transition this.prevFogBrightness = this.fogBrightness const renderDistance = this.viewDistance / 32 diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 29e9223c..1b4e6152 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -98,7 +98,7 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlockLeft = new HoldingBlock(this, true) // Initialize skybox renderer - this.skyboxRenderer = new SkyboxRenderer(this.scene, null) + this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null) void this.skyboxRenderer.init() this.addDebugOverlay() @@ -206,6 +206,9 @@ export class WorldRendererThree extends WorldRendererCommon { this.onReactiveConfigUpdated('showChunkBorders', (value) => { this.updateShowChunksBorder(value) }) + this.onReactiveConfigUpdated('defaultSkybox', (value) => { + this.skyboxRenderer.updateDefaultSkybox(value) + }) } changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 6045e70b..361879be 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -41,6 +41,7 @@ export const defaultOptions = { renderEars: true, lowMemoryMode: false, starfieldRendering: true, + defaultSkybox: true, enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', diff --git a/src/watchOptions.ts b/src/watchOptions.ts index da75cc74..779aa29f 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -116,6 +116,10 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering }) + watchValue(options, o => { + appViewer.inWorldRenderingConfig.defaultSkybox = o.defaultSkybox + }) + watchValue(options, o => { // appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates }) From 06dc3cb033129f925dd492b01a809e9e5f3cd19c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:38:16 +0300 Subject: [PATCH 09/22] feat: Add saveLoginPassword option to control password saving behavior in browser for offline auth on servers --- src/defaultOptions.ts | 1 + src/optionsGuiScheme.tsx | 10 ++++++++++ src/react/ChatProvider.tsx | 20 ++++++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 361879be..85ebae17 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -84,6 +84,7 @@ export const defaultOptions = { localServerOptions: { gameMode: 1 } as any, + saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always', preferLoadReadonly: false, experimentalClientSelfReload: false, remoteSoundsSupport: false, diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index b03db37d..a47c06eb 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -550,6 +550,16 @@ export const guiOptionsScheme: { return Server Connection }, }, + { + saveLoginPassword: { + tooltip: 'Controls whether to save login passwords for servers in this browser memory.', + values: [ + 'prompt', + 'always', + 'never' + ] + }, + }, { custom () { const { serversAutoVersionSelect } = useSnapshot(options) diff --git a/src/react/ChatProvider.tsx b/src/react/ChatProvider.tsx index 0bb13285..066bc48a 100644 --- a/src/react/ChatProvider.tsx +++ b/src/react/ChatProvider.tsx @@ -73,16 +73,28 @@ export default () => { } const builtinHandled = tryHandleBuiltinCommand(message) - if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) { - showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => { + if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register')) && options.saveLoginPassword !== 'never') { + const savePassword = () => { + let hadPassword = false updateLoadedServerData((server) => { server.autoLogin ??= {} const password = message.split(' ')[1] + hadPassword = !!server.autoLogin[bot.username] server.autoLogin[bot.username] = password return { ...server } }) - hideNotification() - }) + if (options.saveLoginPassword === 'always') { + const message = hadPassword ? 'Password updated in browser for auto-login' : 'Password saved in browser for auto-login' + showNotification(message, undefined, false, undefined) + } else { + hideNotification() + } + } + if (options.saveLoginPassword === 'prompt') { + showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, savePassword) + } else { + savePassword() + } notificationProxy.id = 'auto-login' const listener = () => { hideNotification() From 852dd737aefc045a1d4193d17803d10a0cce1b48 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Sep 2025 22:24:04 +0300 Subject: [PATCH 10/22] fix: fix some UI like error screen was not visible fully (buttons were clipped behind the screen) on larger scale on large screens --- src/screens.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/screens.css b/src/screens.css index f0040e2d..e503c305 100644 --- a/src/screens.css +++ b/src/screens.css @@ -26,6 +26,10 @@ display: flex; justify-content: center; z-index: 12; + /* Account for GUI scaling */ + width: calc(100dvw / var(--guiScale, 1)); + height: calc(100dvh / var(--guiScale, 1)); + overflow: hidden; } .screen-content { From c930365e329aef0c44845ab06aa42266f5a6a16e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 18 Sep 2025 07:49:44 +0300 Subject: [PATCH 11/22] fix sometimes inventory player should not be rendered --- src/inventoryWindows.ts | 2 ++ src/react/OverlayModelViewer.tsx | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index d16fee20..d40260df 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -470,6 +470,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = const isRightClick = type === 'rightclick' const isLeftClick = type === 'leftclick' if (isLeftClick || isRightClick) { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) } } else { @@ -501,6 +502,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = if (freeSlot === null) return void bot.creative.setInventorySlot(freeSlot, item) } else { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) } } diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index 24dc836d..0fdeae75 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -6,6 +6,7 @@ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject' import { currentScaling } from '../scaleInterface' +import { activeModalStack } from '../globalState' THREE.ColorManagement.enabled = false @@ -29,6 +30,7 @@ export const modelViewerState = proxy({ modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } } resetRotationOnReleae?: boolean continiousRender?: boolean + alwaysRender?: boolean } }) globalThis.modelViewerState = modelViewerState @@ -75,6 +77,15 @@ globalThis.getModelViewerValues = () => { } } +subscribe(activeModalStack, () => { + if (!modelViewerState.model || !modelViewerState.model?.alwaysRender) { + return + } + if (activeModalStack.length === 0) { + modelViewerState.model = undefined + } +}) + export default () => { const { model } = useSnapshot(modelViewerState) const containerRef = useRef(null) From 636a7fdb54a2fec622b5b31da4691a632049b5f0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 19 Sep 2025 04:42:22 +0200 Subject: [PATCH 12/22] feat: improve fog a little (#427) --- renderer/viewer/three/skyboxRenderer.ts | 43 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index cd7bd879..fb9edae6 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -1,4 +1,5 @@ import * as THREE from 'three' +import { DebugGui } from '../lib/DebugGui' export const DEFAULT_TEMPERATURE = 0.75 @@ -17,11 +18,33 @@ export class SkyboxRenderer { private waterBreathing = false private fogBrightness = 0 private prevFogBrightness = 0 + private readonly fogOrangeness = 0 // Debug property to control sky color orangeness + private readonly distanceFactor = 2.7 + + private readonly brightnessAtPosition = 1 + debugGui: DebugGui constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { + this.debugGui = new DebugGui('skybox_renderer', this, [ + 'temperature', + 'worldTime', + 'inWater', + 'waterBreathing', + 'fogOrangeness', + 'brightnessAtPosition', + 'distanceFactor' + ], { + brightnessAtPosition: { min: 0, max: 1, step: 0.01 }, + temperature: { min: 0, max: 1, step: 0.01 }, + worldTime: { min: 0, max: 24_000, step: 1 }, + fogOrangeness: { min: -1, max: 1, step: 0.01 }, + distanceFactor: { min: 0, max: 5, step: 0.01 }, + }) + if (!initialImage) { this.createGradientSky() } + // this.debugGui.activate() } async init () { @@ -95,6 +118,7 @@ export class SkyboxRenderer { // Update world time updateTime (timeOfDay: number, partialTicks = 0) { + if (this.debugGui.visible) return this.worldTime = timeOfDay this.partialTicks = partialTicks this.updateSkyColors() @@ -108,12 +132,14 @@ export class SkyboxRenderer { // Update temperature (for biome support) updateTemperature (temperature: number) { + if (this.debugGui.visible) return this.temperature = temperature this.updateSkyColors() } // Update water state updateWaterState (inWater: boolean, waterBreathing: boolean) { + if (this.debugGui.visible) return this.inWater = inWater this.waterBreathing = waterBreathing this.updateSkyColors() @@ -121,6 +147,7 @@ export class SkyboxRenderer { // Update default skybox setting updateDefaultSkybox (defaultSkybox: boolean) { + if (this.debugGui.visible) return this.defaultSkybox = defaultSkybox this.updateSkyColors() } @@ -229,8 +256,15 @@ export class SkyboxRenderer { if (temperature < -1) temperature = -1 if (temperature > 1) temperature = 1 - const hue = 0.622_222_2 - temperature * 0.05 - const saturation = 0.5 + temperature * 0.1 + // Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange + const baseHue = 0.622_222_2 - temperature * 0.05 + // Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange + // Use a more dramatic shift and also increase saturation for more noticeable effect + const orangeHue = 0.12 // Orange hue value + const hue = this.fogOrangeness > 0 + ? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange + : baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values + const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness const brightness = 1 return this.hsbToRgb(hue, saturation, brightness) @@ -305,8 +339,7 @@ export class SkyboxRenderer { // Update fog brightness with smooth transition this.prevFogBrightness = this.fogBrightness const renderDistance = this.viewDistance / 32 - const brightnessAtPosition = 1 // Could be affected by light level in future - const targetBrightness = brightnessAtPosition * (1 - renderDistance) + renderDistance + const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1 // Handle water fog @@ -340,7 +373,7 @@ export class SkyboxRenderer { const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness this.scene.background = new THREE.Color(red, green, blue) - this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2) + this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor) ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( From 3b94889bed40e9c687be52c5ca9a87172c6c6a9d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 20 Sep 2025 01:57:59 +0200 Subject: [PATCH 13/22] feat: make arrows colorful and metadata (#430) Co-authored-by: Cursor Agent --- renderer/viewer/three/waypointSprite.ts | 36 ++++++++++++++++++++----- renderer/viewer/three/waypoints.ts | 4 ++- src/customChannels.ts | 17 +++++++++++- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/renderer/viewer/three/waypointSprite.ts b/renderer/viewer/three/waypointSprite.ts index 7c8cf1f6..6a30e6db 100644 --- a/renderer/viewer/three/waypointSprite.ts +++ b/renderer/viewer/three/waypointSprite.ts @@ -16,7 +16,7 @@ export const WAYPOINT_CONFIG = { CANVAS_SCALE: 2, ARROW: { enabledDefault: false, - pixelSize: 30, + pixelSize: 50, paddingPx: 50, }, } @@ -50,6 +50,7 @@ export function createWaypointSprite (options: { depthTest?: boolean, // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this) labelYOffset?: number, + metadata?: any, }): WaypointSprite { const color = options.color ?? 0xFF_00_00 const depthTest = options.depthTest ?? false @@ -131,16 +132,22 @@ export function createWaypointSprite (options: { canvas.height = size const ctx = canvas.getContext('2d')! ctx.clearRect(0, 0, size, size) + + // Draw arrow shape ctx.beginPath() - ctx.moveTo(size * 0.2, size * 0.5) - ctx.lineTo(size * 0.8, size * 0.5) - ctx.lineTo(size * 0.5, size * 0.2) + ctx.moveTo(size * 0.15, size * 0.5) + ctx.lineTo(size * 0.85, size * 0.5) + ctx.lineTo(size * 0.5, size * 0.15) ctx.closePath() - ctx.lineWidth = 4 + + // Use waypoint color for arrow + const colorHex = `#${color.toString(16).padStart(6, '0')}` + ctx.lineWidth = 6 ctx.strokeStyle = 'black' ctx.stroke() - ctx.fillStyle = 'white' + ctx.fillStyle = colorHex ctx.fill() + const texture = new THREE.CanvasTexture(canvas) const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false }) arrowSprite = new THREE.Sprite(material) @@ -169,6 +176,9 @@ export function createWaypointSprite (options: { ensureArrow() if (!arrowSprite) return true + // Check if onlyLeftRight is enabled in metadata + const onlyLeftRight = options.metadata?.onlyLeftRight === true + // Build camera basis using camera.up to respect custom orientations const forward = new THREE.Vector3() camera.getWorldDirection(forward) // camera look direction @@ -213,6 +223,20 @@ export function createWaypointSprite (options: { } } + // Apply onlyLeftRight logic - restrict arrows to left/right edges only + if (onlyLeftRight) { + // Force the arrow to appear only on left or right edges + if (Math.abs(rx) > Math.abs(ry)) { + // Horizontal direction is dominant, keep it + ry = 0 + } else { + // Vertical direction is dominant, but we want only left/right + // So choose left or right based on the sign of rx + rx = rx >= 0 ? 1 : -1 + ry = 0 + } + } + // Place on the rectangle border [-1,1]x[-1,1] const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1 let ndcX = rx / s diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts index cebd779a..256ca6df 100644 --- a/renderer/viewer/three/waypoints.ts +++ b/renderer/viewer/three/waypoints.ts @@ -17,6 +17,7 @@ interface WaypointOptions { color?: number label?: string minDistance?: number + metadata?: any } export class WaypointsRenderer { @@ -71,13 +72,14 @@ export class WaypointsRenderer { this.removeWaypoint(id) const color = options.color ?? 0xFF_00_00 - const { label } = options + const { label, metadata } = options const minDistance = options.minDistance ?? 0 const sprite = createWaypointSprite({ position: new THREE.Vector3(x, y, z), color, label: (label || id), + metadata, }) sprite.enableOffscreenArrow(true) sprite.setArrowParent(this.waypointScene) diff --git a/src/customChannels.ts b/src/customChannels.ts index b566f9dd..506ea776 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -82,15 +82,30 @@ const registerWaypointChannels = () => { { name: 'color', type: 'i32' + }, + { + name: 'metadataJson', + type: ['pstring', { countType: 'i16' }] } ] ] registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => { + // Parse metadata if provided + let metadata: any = {} + if (data.metadataJson && data.metadataJson.trim() !== '') { + try { + metadata = JSON.parse(data.metadataJson) + } catch (error) { + console.warn('Failed to parse waypoint metadataJson:', error) + } + } + getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, { minDistance: data.minDistance, label: data.label || undefined, - color: data.color || undefined + color: data.color || undefined, + metadata }) }) From 4f421ae45fda892cc364cc60de47e4fc79799eee Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 28 Sep 2025 21:59:00 +0300 Subject: [PATCH 14/22] respect loadPlayerSkins option for inventory skin --- src/watchOptions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 779aa29f..de7d30d3 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -3,6 +3,7 @@ import { subscribeKey } from 'valtio/utils' import { isMobile } from 'renderer/viewer/lib/simpleUtils' import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' +import { setSkinsConfig } from 'renderer/viewer/lib/utils/skins' import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' import { miscUiState } from './globalState' @@ -97,6 +98,8 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks + + setSkinsConfig({ apiEnabled: o.loadPlayerSkins }) }) appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting From b239636356c9bb828181cf069c3756c722cebd33 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 28 Sep 2025 22:04:17 +0300 Subject: [PATCH 15/22] feat: add debugServerPacketNames and debugClientPacketNames for quick access of names with intellisense of packets for current protocol. Should be used with `window.inspectPacket` in console --- src/devtools.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/devtools.ts b/src/devtools.ts index 6c47f73d..1f8ef8e8 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -5,6 +5,17 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree' import { enable, disable, enabled } from 'debug' import { Vec3 } from 'vec3' +customEvents.on('mineflayerBotCreated', () => { + window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) + window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) +}) + window.Vec3 = Vec3 window.cursorBlockRel = (x = 0, y = 0, z = 0) => { const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z) From 05cd560d6b67e287acd6684ffeacc0db4b0b2386 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 29 Sep 2025 02:01:04 +0300 Subject: [PATCH 16/22] add shadow and directional light for player in inventory (model viewer) --- src/react/OverlayModelViewer.tsx | 54 +++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index 0fdeae75..e48a2f0b 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -119,11 +119,15 @@ export default () => { modelLoaders.current.set(modelUrl, loader) const onLoad = (object: THREE.Object3D) => { - // Apply customization if available + // Apply customization if available and enable shadows const customization = model?.modelCustomization?.[modelUrl] - if (customization) { - object.traverse((child) => { - if (child instanceof THREE.Mesh && child.material) { + object.traverse((child) => { + if (child instanceof THREE.Mesh) { + // Enable shadow casting and receiving for all meshes + child.castShadow = true + child.receiveShadow = true + + if (child.material && customization) { const material = child.material as THREE.MeshStandardMaterial if (customization.color) { material.color.setHex(parseInt(customization.color.replace('#', ''), 16)) @@ -139,8 +143,8 @@ export default () => { material.roughness = customization.roughness } } - }) - } + } + }) // Center and scale model const box = new THREE.Box3().setFromObject(object) @@ -259,6 +263,12 @@ export default () => { } renderer.setPixelRatio(scale) renderer.setSize(model.positioning.width, model.positioning.height) + + // Enable shadow rendering for depth and realism + renderer.shadowMap.enabled = true + renderer.shadowMap.type = THREE.PCFSoftShadowMap // Soft shadows for better quality + renderer.shadowMap.autoUpdate = true + containerRef.current.appendChild(renderer.domElement) // Setup controls @@ -270,10 +280,30 @@ export default () => { controls.enableDamping = true controls.dampingFactor = 0.05 - // Add ambient light - const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 1) + // Add ambient light for overall illumination + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 0.4) // Reduced intensity to allow shadows scene.add(ambientLight) + // Add directional light for shadows and depth (similar to Minecraft inventory lighting) + const directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.6) + directionalLight.position.set(2, 2, 2) // Position light from top-right-front + directionalLight.target.position.set(0, 0, 0) // Point towards center of scene + + // Configure shadow properties for optimal quality + directionalLight.castShadow = true + directionalLight.shadow.mapSize.width = 2048 // High resolution shadow map + directionalLight.shadow.mapSize.height = 2048 + directionalLight.shadow.camera.near = 0.1 + directionalLight.shadow.camera.far = 10 + directionalLight.shadow.camera.left = -3 + directionalLight.shadow.camera.right = 3 + directionalLight.shadow.camera.top = 3 + directionalLight.shadow.camera.bottom = -3 + directionalLight.shadow.bias = -0.0001 // Reduce shadow acne + + scene.add(directionalLight) + scene.add(directionalLight.target) + // Cursor following function const updatePlayerLookAt = () => { if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return @@ -342,6 +372,14 @@ export default () => { scale: 1 // Start with base scale, will adjust below }) + // Enable shadows for player object + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = true + child.receiveShadow = true + } + }) + // Calculate proper scale and positioning for camera view const box = new THREE.Box3().setFromObject(wrapper) const size = box.getSize(new THREE.Vector3()) From f51254d97a9a04be3eb5750d214c59e0c41ffe76 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 30 Sep 2025 07:20:30 +0300 Subject: [PATCH 17/22] fix: dont stop local replay server with keep alive connection error --- src/packetsReplay/replayPackets.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index d0d95da8..54b3d652 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -59,6 +59,7 @@ export const startLocalReplayServer = (contents: string) => { const server = createServer({ Server: LocalServer as any, version: header.minecraftVersion, + keepAlive: false, 'online-mode': false }) From a88c8b547044c1dab9c759e56794d614cc41ffa4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 30 Sep 2025 09:38:37 +0300 Subject: [PATCH 18/22] possible fix for rare edgecase where skins from server were not applied. Cause: renderer due to rare circumnstances could be loaded AFTER gameLoaded which is fired only when starting rendering 3d world. classic no existing data handling issue why not mineflayerBotCreated? because getThreeJsRendererMethods not available at that time so would make things only much complex --- src/entities.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/entities.ts b/src/entities.ts index dcec6143..674f91ef 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -246,22 +246,29 @@ customEvents.on('gameLoaded', () => { } } // even if not found, still record to cache - void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) + void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) } catch (err) { - console.error('Error decoding player texture:', err) + reportError(new Error('Error applying skin texture:', { cause: err })) } } bot.on('playerJoined', updateSkin) bot.on('playerUpdated', updateSkin) + for (const entity of Object.values(bot.players)) { + updateSkin(entity) + } - bot.on('teamUpdated', (team: Team) => { + const teamUpdated = (team: Team) => { for (const entity of Object.values(bot.entities)) { if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) { bot.emit('entityUpdate', entity) } } - }) + } + bot.on('teamUpdated', teamUpdated) + for (const team of Object.values(bot.teams)) { + teamUpdated(team) + } const updateEntityNameTags = (team: Team) => { for (const entity of Object.values(bot.entities)) { From 634df8d03dfd90aa978433e39c23376a4116a15d Mon Sep 17 00:00:00 2001 From: Colbster937 Date: Fri, 10 Oct 2025 17:52:06 -0500 Subject: [PATCH 19/22] Add WebMC & WS changes (#431) Co-authored-by: Colbster937 <96893162+colbychittenden@users.noreply.github.com> --- config.json | 4 ++++ src/appConfig.ts | 2 +- src/mineflayer/websocket-core.ts | 7 +++++-- src/react/AddServerOrConnect.tsx | 2 +- src/react/ServersListProvider.tsx | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 940fb738..2bfa9cfe 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,10 @@ { "ip": "wss://play.mcraft.fun" }, + { + "ip": "wss://play.webmc.fun", + "name": "WebMC" + }, { "ip": "wss://ws.fuchsmc.net" }, diff --git a/src/appConfig.ts b/src/appConfig.ts index 92fde21a..c29d74e8 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -35,7 +35,7 @@ export type AppConfig = { // defaultVersion?: string peerJsServer?: string peerJsServerFallback?: string - promoteServers?: Array<{ ip, description, version? }> + promoteServers?: Array<{ ip, description, name?, version?, }> mapsProvider?: string appParams?: Record // query string params diff --git a/src/mineflayer/websocket-core.ts b/src/mineflayer/websocket-core.ts index 0edd2497..f8163102 100644 --- a/src/mineflayer/websocket-core.ts +++ b/src/mineflayer/websocket-core.ts @@ -15,9 +15,12 @@ class CustomDuplex extends Duplex { } export const getWebsocketStream = async (host: string) => { - const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss' + const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss' const hostClean = host.replace('ws://', '').replace('wss://', '') - const ws = new WebSocket(`${baseProtocol}://${hostClean}`) + const hostURL = new URL(`${baseProtocol}://${hostClean}`) + const hostParams = hostURL.searchParams + hostParams.append('client_mcraft', '') + const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`) const clientDuplex = new CustomDuplex(undefined, data => { ws.send(data) }) diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index d478b3e7..36fd5264 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -117,7 +117,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ } const displayConnectButton = qsParamIp - const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg'] + const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg', 'wss://play.webmc.fun'] // pick random example const example = serverExamples[Math.floor(Math.random() * serverExamples.length)] diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index 75f95d3f..42ef2aaa 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -119,6 +119,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL ...serversListProvided, ...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({ ip: server.ip, + name: server.name, versionOverride: server.version, description: server.description, isRecommended: true @@ -167,6 +168,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) data = { formattedText: pingResult.fullInfo.description, + icon: pingResult.fullInfo.favicon, textNameRight: `ws ${pingResult.latency}ms`, textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, offline: false From e9f91f8ecda1488c636f35f58cc522f459a29f82 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 11 Oct 2025 02:24:51 +0300 Subject: [PATCH 20/22] feat: enable music by default, add slider for controlling its volume --- src/basicSounds.ts | 30 ++++++++++++++++++++---------- src/defaultOptions.ts | 3 ++- src/optionsGuiScheme.tsx | 18 ++++++++++++++++++ src/react/OptionsItems.tsx | 13 +++++++++++-- src/sounds/musicSystem.ts | 4 ++-- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 37f8dccd..54af0d35 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -7,7 +7,12 @@ let audioContext: AudioContext const sounds: Record = {} // Track currently playing sounds and their gain nodes -const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = [] +const activeSounds: Array<{ + source: AudioBufferSourceNode; + gainNode: GainNode; + volumeMultiplier: number; + isMusic: boolean; +}> = [] window.activeSounds = activeSounds // load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded @@ -43,7 +48,7 @@ export async function loadSound (path: string, contents = path) { } } -export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false) => { +export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => { const soundBuffer = sounds[url] if (!soundBuffer) { const start = Date.now() @@ -51,11 +56,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option if (cancelled || Date.now() - start > loadTimeout) return } - return playSound(url, soundVolume, loop) + return playSound(url, soundVolume, loop, isMusic) } -export async function playSound (url, soundVolume = 1, loop = false) { - const volume = soundVolume * (options.volume / 100) +export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) { + const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1) if (!volume) return @@ -82,7 +87,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { source.start(0) // Add to active sounds - activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume }) + activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic }) const callbacks = [] as Array<() => void> source.onended = () => { @@ -110,6 +115,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { console.warn('Failed to stop sound:', err) } }, + gainNode, } } @@ -137,11 +143,11 @@ export function stopSound (url: string) { } } -export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { +export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) { const normalizedVolume = newVolume / 100 - for (const { gainNode, volumeMultiplier } of activeSounds) { + for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) { try { - gainNode.gain.value = normalizedVolume * volumeMultiplier + gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1) } catch (err) { console.warn('Failed to change sound volume:', err) } @@ -149,5 +155,9 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { } subscribeKey(options, 'volume', () => { - changeVolumeOfCurrentlyPlayingSounds(options.volume) + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) +}) + +subscribeKey(options, 'musicVolume', () => { + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) }) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 85ebae17..48c1cfad 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -16,7 +16,8 @@ export const defaultOptions = { chatOpacityOpened: 100, messagesLimit: 200, volume: 50, - enableMusic: false, + enableMusic: true, + musicVolume: 50, // fov: 70, fov: 75, defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front', diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index a47c06eb..0cb0fe1e 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -480,6 +480,24 @@ export const guiOptionsScheme: { ], sound: [ { volume: {} }, + { + custom () { + return { + options.musicVolume = value + }} + item={{ + type: 'slider', + id: 'musicVolume', + text: 'Music Volume', + min: 0, + max: 100, + unit: '%', + }} + /> + }, + }, { custom () { return