pick most of changes from webgpu for better stability (#322)
This commit is contained in:
parent
c025a1c75a
commit
908fa64f2f
34 changed files with 304 additions and 164 deletions
|
|
@ -150,7 +150,7 @@
|
|||
"http-browserify": "^1.7.0",
|
||||
"http-server": "^14.1.1",
|
||||
"https-browserify": "^1.0.0",
|
||||
"mc-assets": "^0.2.50",
|
||||
"mc-assets": "^0.2.52",
|
||||
"mineflayer-mouse": "^0.1.7",
|
||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||
"mineflayer": "github:zardoy/mineflayer",
|
||||
|
|
|
|||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
|
|
@ -353,8 +353,8 @@ importers:
|
|||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
mc-assets:
|
||||
specifier: ^0.2.50
|
||||
version: 0.2.50
|
||||
specifier: ^0.2.52
|
||||
version: 0.2.52
|
||||
minecraft-inventory-gui:
|
||||
specifier: github:zardoy/minecraft-inventory-gui#next
|
||||
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00(@types/react@18.2.20)(react@18.2.0)
|
||||
|
|
@ -3546,6 +3546,9 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
'@zardoy/maxrects-packer@2.7.4':
|
||||
resolution: {integrity: sha512-ZIDcSdtSg6EhKFxGYWCcTnA/0YVbpixBL+psUS6ncw4IvdDF5hWauMU3XeCfYwrT/88QFgAq/Pafxt+P9OJyoQ==}
|
||||
|
||||
'@zardoy/react-util@0.2.4':
|
||||
resolution: {integrity: sha512-YRBbXi54QOgWGDSn3NLEMMGrWbfL/gn2khxO31HT0WPFB6IW2rSnB4hcy+S/nc+2D6PRNq4kQxGs4vTAe4a7Xg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
|
@ -6691,11 +6694,8 @@ packages:
|
|||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
maxrects-packer@2.7.3:
|
||||
resolution: {integrity: sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==}
|
||||
|
||||
mc-assets@0.2.50:
|
||||
resolution: {integrity: sha512-X27KLLTyeEAVTlBVKqGmoG/YvZq3tDG29kyRgy3Hj9s03m6+/TI8HvCyxumRjEQE8IYPcjPiX+7iuEZtNQ9N+w==}
|
||||
mc-assets@0.2.52:
|
||||
resolution: {integrity: sha512-6mUI63fcUIjB0Ghjls7bLMnse2XUgvhPajsFkRQf10PcXYbfS/OAnX51X8sNx2pzfoHSlA81U7v+v906YwoAUw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
mcraft-fun-mineflayer@0.1.14:
|
||||
|
|
@ -13688,6 +13688,8 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@zardoy/maxrects-packer@2.7.4': {}
|
||||
|
||||
'@zardoy/react-util@0.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
classnames: 2.5.1
|
||||
|
|
@ -17603,11 +17605,9 @@ snapshots:
|
|||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
maxrects-packer@2.7.3: {}
|
||||
|
||||
mc-assets@0.2.50:
|
||||
mc-assets@0.2.52:
|
||||
dependencies:
|
||||
maxrects-packer: 2.7.3
|
||||
maxrects-packer: '@zardoy/maxrects-packer@2.7.4'
|
||||
zod: 3.24.1
|
||||
|
||||
mcraft-fun-mineflayer@0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { EventEmitter } from 'events'
|
|||
import { Vec3 } from 'vec3'
|
||||
import TypedEmitter from 'typed-emitter'
|
||||
import { ItemSelector } from 'mc-assets/dist/itemDefinitions'
|
||||
import { proxy } from 'valtio'
|
||||
import { proxy, ref } from 'valtio'
|
||||
import { GameMode } from 'mineflayer'
|
||||
import { HandItemBlock } from '../three/holdingBlock'
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ export interface IPlayerState {
|
|||
reactive: {
|
||||
playerSkin: string | undefined
|
||||
inWater: boolean
|
||||
waterBreathing: boolean
|
||||
backgroundColor: [number, number, number]
|
||||
ambientLight: number
|
||||
directionalLight: number
|
||||
|
|
@ -45,7 +46,8 @@ export class BasePlayerState implements IPlayerState {
|
|||
reactive = proxy({
|
||||
playerSkin: undefined as string | undefined,
|
||||
inWater: false,
|
||||
backgroundColor: [0, 0, 0] as [number, number, number],
|
||||
waterBreathing: false,
|
||||
backgroundColor: ref([0, 0, 0]) as [number, number, number],
|
||||
ambientLight: 0,
|
||||
directionalLight: 0,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,9 +41,10 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
private readonly lastPos: Vec3
|
||||
private eventListeners: Record<string, any> = {}
|
||||
private readonly emitter: WorldDataEmitter
|
||||
keepChunksDistance = 0
|
||||
addWaitTime = 1
|
||||
isPlayground = false
|
||||
/* config */ keepChunksDistance = 0
|
||||
/* config */ isPlayground = false
|
||||
/* config */ allowPositionUpdate = true
|
||||
|
||||
public reactive = proxy({
|
||||
cursorBlock: null as Vec3 | null,
|
||||
|
|
@ -165,6 +166,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
console.error('error processing entity', err)
|
||||
}
|
||||
}
|
||||
|
||||
void this.init(bot.entity.position)
|
||||
}
|
||||
|
||||
removeListenersFromBot (bot: import('mineflayer').Bot) {
|
||||
|
|
@ -253,6 +256,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
}
|
||||
|
||||
async updatePosition (pos: Vec3, force = false) {
|
||||
if (!this.allowPositionUpdate) return
|
||||
const [lastX, lastZ] = chunkPos(this.lastPos)
|
||||
const [botX, botZ] = chunkPos(pos)
|
||||
if (lastX !== botX || lastZ !== botZ || force) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import TypedEmitter from 'typed-emitter'
|
|||
import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer'
|
||||
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
||||
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
|
||||
import { toMajorVersion } from '../../../src/utils'
|
||||
import { ResourcesManager } from '../../../src/resourcesManager'
|
||||
|
|
@ -15,7 +16,7 @@ import { SoundSystem } from '../three/threeJsSound'
|
|||
import { buildCleanupDecorator } from './cleanupDecorator'
|
||||
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
|
||||
import { chunkPos } from './simpleUtils'
|
||||
import { removeStat, updateStatText } from './ui/newStats'
|
||||
import { removeAllStats, removeStat, updateStatText } from './ui/newStats'
|
||||
import { WorldDataEmitter } from './worldDataEmitter'
|
||||
import { IPlayerState } from './basePlayerState'
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
workersProcessAverageTime = 0
|
||||
workersProcessAverageTimeCount = 0
|
||||
maxWorkersProcessTime = 0
|
||||
geometryReceiveCount = {}
|
||||
geometryReceiveCount = {} as Record<number, number>
|
||||
allLoadedIn: undefined | number
|
||||
onWorldSwitched = [] as Array<() => void>
|
||||
|
||||
|
|
@ -137,6 +138,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
abortController = new AbortController()
|
||||
lastRendered = 0
|
||||
renderingActive = true
|
||||
geometryReceiveCountPerSec = 0
|
||||
workerLogger = {
|
||||
contents: [] as string[],
|
||||
active: new URL(location.href).searchParams.get('mesherlog') === 'true'
|
||||
|
|
@ -155,6 +157,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
})
|
||||
|
||||
this.connect(this.displayOptions.worldView)
|
||||
|
||||
setInterval(() => {
|
||||
this.geometryReceiveCountPerSec = Object.values(this.geometryReceiveCount).reduce((acc, curr) => acc + curr, 0)
|
||||
this.geometryReceiveCount = {}
|
||||
this.updateChunksStats()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
logWorkerWork (message: string | (() => string)) {
|
||||
|
|
@ -164,6 +172,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
|
||||
init () {
|
||||
if (this.active) throw new Error('WorldRendererCommon is already initialized')
|
||||
this.watchReactivePlayerState()
|
||||
void this.setVersion(this.version).then(() => {
|
||||
this.resourcesManager.on('assetsTexturesUpdated', () => {
|
||||
if (!this.active) return
|
||||
|
|
@ -238,6 +247,17 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
}
|
||||
}
|
||||
|
||||
onReactiveValueUpdated<T extends keyof typeof this.displayOptions.playerState.reactive>(key: T, callback: (value: typeof this.displayOptions.playerState.reactive[T]) => void) {
|
||||
callback(this.displayOptions.playerState.reactive[key])
|
||||
subscribeKey(this.displayOptions.playerState.reactive, key, callback)
|
||||
}
|
||||
|
||||
watchReactivePlayerState () {
|
||||
this.onReactiveValueUpdated('backgroundColor', (value) => {
|
||||
this.changeBackgroundColor(value)
|
||||
})
|
||||
}
|
||||
|
||||
async processMessageQueue (source: string) {
|
||||
if (this.isProcessingQueue || this.messageQueue.length === 0) return
|
||||
this.logWorkerWork(`# ${source} processing queue`)
|
||||
|
|
@ -503,7 +523,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength
|
||||
this.reactiveState.world.allChunksLoaded = this.allChunksFinished
|
||||
|
||||
updateStatText('downloaded-chunks', `${Object.keys(this.loadedChunks).length}/${this.chunksLength} chunks D (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`)
|
||||
updateStatText('downloaded-chunks', `${Object.keys(this.loadedChunks).length}/${this.chunksLength} chunks D (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.geometryReceiveCountPerSec}ss/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`)
|
||||
}
|
||||
|
||||
addColumn (x: number, z: number, chunk: any, isLightUpdate: boolean) {
|
||||
|
|
@ -884,7 +904,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.renderUpdateEmitter.removeAllListeners()
|
||||
this.displayOptions.worldView.removeAllListeners() // todo
|
||||
this.abortController.abort()
|
||||
removeStat('chunks-loaded')
|
||||
removeStat('chunks-read')
|
||||
removeAllStats()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,4 +117,5 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO
|
|||
return backend
|
||||
}
|
||||
|
||||
createGraphicsBackend.id = 'threejs'
|
||||
export default createGraphicsBackend
|
||||
|
|
|
|||
|
|
@ -911,6 +911,6 @@ export const getBlockMeshFromModel = (material: THREE.Material, model: BlockMode
|
|||
const worldRenderModel = blockProvider.transformModel(model, {
|
||||
name,
|
||||
properties: {}
|
||||
})
|
||||
}) as any
|
||||
return getThreeBlockModelGroup(material, [[worldRenderModel]], undefined, 'plains', loadedData)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
|
||||
this.addDebugOverlay()
|
||||
this.resetScene()
|
||||
this.watchReactivePlayerState()
|
||||
this.init()
|
||||
void initVR(this)
|
||||
|
||||
|
|
@ -120,22 +119,15 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000)
|
||||
}
|
||||
|
||||
watchReactivePlayerState () {
|
||||
const updateValue = <T extends keyof typeof this.displayOptions.playerState.reactive>(key: T, callback: (value: typeof this.displayOptions.playerState.reactive[T]) => void) => {
|
||||
callback(this.displayOptions.playerState.reactive[key])
|
||||
subscribeKey(this.displayOptions.playerState.reactive, key, callback)
|
||||
}
|
||||
updateValue('backgroundColor', (value) => {
|
||||
this.changeBackgroundColor(value)
|
||||
override watchReactivePlayerState () {
|
||||
this.onReactiveValueUpdated('inWater', (value) => {
|
||||
this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.displayOptions.playerState.reactive.waterBreathing ? 100 : 20) : null
|
||||
})
|
||||
updateValue('inWater', (value) => {
|
||||
this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, 100) : null
|
||||
})
|
||||
updateValue('ambientLight', (value) => {
|
||||
this.onReactiveValueUpdated('ambientLight', (value) => {
|
||||
if (!value) return
|
||||
this.ambientLight.intensity = value
|
||||
})
|
||||
updateValue('directionalLight', (value) => {
|
||||
this.onReactiveValueUpdated('directionalLight', (value) => {
|
||||
if (!value) return
|
||||
this.directionalLight.intensity = value
|
||||
})
|
||||
|
|
@ -613,8 +605,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
|
||||
destroy (): void {
|
||||
removeAllStats()
|
||||
this.media.onWorldGone()
|
||||
super.destroy()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,11 +49,13 @@ const defaultGraphicsBackendConfig: GraphicsBackendConfig = {
|
|||
sceneBackground: 'lightblue'
|
||||
}
|
||||
|
||||
export interface GraphicsInitOptions {
|
||||
export interface GraphicsInitOptions<S = any> {
|
||||
resourcesManager: ResourcesManager
|
||||
config: GraphicsBackendConfig
|
||||
rendererSpecificSettings: S
|
||||
|
||||
displayCriticalError: (error: Error) => void
|
||||
setRendererSpecificSettings: (key: string, value: any) => void
|
||||
}
|
||||
|
||||
export interface DisplayWorldOptions {
|
||||
|
|
@ -65,7 +67,9 @@ export interface DisplayWorldOptions {
|
|||
nonReactiveState: NonReactiveState
|
||||
}
|
||||
|
||||
export type GraphicsBackendLoader = (options: GraphicsInitOptions) => GraphicsBackend
|
||||
export type GraphicsBackendLoader = ((options: GraphicsInitOptions) => GraphicsBackend) & {
|
||||
id: string
|
||||
}
|
||||
|
||||
// no sync methods
|
||||
export interface GraphicsBackend {
|
||||
|
|
@ -73,7 +77,7 @@ export interface GraphicsBackend {
|
|||
displayName?: string
|
||||
startPanorama: () => void
|
||||
// prepareResources: (version: string, progressReporter: ProgressReporter) => Promise<void>
|
||||
startWorld: (options: DisplayWorldOptions) => void
|
||||
startWorld: (options: DisplayWorldOptions) => Promise<void> | void
|
||||
disconnect: () => void
|
||||
setRendering: (rendering: boolean) => void
|
||||
getDebugOverlay?: () => Record<string, any>
|
||||
|
|
@ -116,6 +120,13 @@ export class AppViewer {
|
|||
}
|
||||
|
||||
this.backendLoader = loader
|
||||
const rendererSpecificSettings = {} as Record<string, any>
|
||||
const rendererSettingsKey = `renderer.${this.backendLoader?.id}`
|
||||
for (const key in options) {
|
||||
if (key.startsWith(rendererSettingsKey)) {
|
||||
rendererSpecificSettings[key.slice(rendererSettingsKey.length + 1)] = options[key]
|
||||
}
|
||||
}
|
||||
const loaderOptions: GraphicsInitOptions = {
|
||||
resourcesManager: this.resourcesManager,
|
||||
config: this.config,
|
||||
|
|
@ -123,6 +134,10 @@ export class AppViewer {
|
|||
console.error(error)
|
||||
setLoadingScreenStatus(error.message, true)
|
||||
},
|
||||
rendererSpecificSettings,
|
||||
setRendererSpecificSettings (key: string, value: any) {
|
||||
options[`${rendererSettingsKey}.${key}`] = value
|
||||
}
|
||||
}
|
||||
this.backend = loader(loaderOptions)
|
||||
|
||||
|
|
@ -135,12 +150,12 @@ export class AppViewer {
|
|||
const { method, args } = this.currentState
|
||||
this.backend[method](...args)
|
||||
if (method === 'startWorld') {
|
||||
void this.worldView!.init(args[0].playerState.getPosition())
|
||||
// void this.worldView!.init(args[0].playerState.getPosition())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startWorld (world, renderDistance: number, playerStateSend: IPlayerState = this.playerState) {
|
||||
async startWorld (world, renderDistance: number, playerStateSend: IPlayerState = this.playerState) {
|
||||
if (this.currentDisplay === 'world') throw new Error('World already started')
|
||||
this.currentDisplay = 'world'
|
||||
const startPosition = playerStateSend.getPosition()
|
||||
|
|
@ -156,14 +171,17 @@ export class AppViewer {
|
|||
rendererState: this.rendererState,
|
||||
nonReactiveState: this.nonReactiveState
|
||||
}
|
||||
let promise: undefined | Promise<void>
|
||||
if (this.backend) {
|
||||
this.backend.startWorld(displayWorldOptions)
|
||||
void this.worldView.init(startPosition)
|
||||
promise = this.backend.startWorld(displayWorldOptions) ?? undefined
|
||||
// void this.worldView.init(startPosition)
|
||||
}
|
||||
this.currentState = { method: 'startWorld', args: [displayWorldOptions] }
|
||||
|
||||
await promise
|
||||
// Resolve the promise after world is started
|
||||
this.resolveWorldReady()
|
||||
return !!promise
|
||||
}
|
||||
|
||||
resetBackend (cleanState = false) {
|
||||
|
|
@ -237,13 +255,15 @@ const initialMenuStart = async () => {
|
|||
}
|
||||
appViewer.startPanorama()
|
||||
|
||||
// await appViewer.resourcesManager.loadMcData('1.21.4')
|
||||
// const world = getSyncWorld('1.21.4')
|
||||
// world.setBlockStateId(new Vec3(0, 64, 0), 1)
|
||||
// appViewer.resourcesManager.currentConfig = { version: '1.21.4' }
|
||||
// const version = '1.18.2'
|
||||
// const version = '1.21.4'
|
||||
// await appViewer.resourcesManager.loadMcData(version)
|
||||
// const world = getSyncWorld(version)
|
||||
// world.setBlockStateId(new Vec3(0, 64, 0), loadedData.blocksByName.water.defaultState)
|
||||
// appViewer.resourcesManager.currentConfig = { version }
|
||||
// await appViewer.resourcesManager.updateAssetsData({})
|
||||
// appViewer.playerState = new BasePlayerState() as any
|
||||
// appViewer.startWorld(world, 3)
|
||||
// await appViewer.startWorld(world, 3)
|
||||
// appViewer.backend?.updateCamera(new Vec3(0, 64, 2), 0, 0)
|
||||
// void appViewer.worldView!.init(new Vec3(0, 64, 0))
|
||||
}
|
||||
|
|
|
|||
34
src/appViewerLoad.ts
Normal file
34
src/appViewerLoad.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { subscribeKey } from 'valtio/utils'
|
||||
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
|
||||
import { options } from './optionsStorage'
|
||||
import { appViewer } from './appViewer'
|
||||
import { miscUiState } from './globalState'
|
||||
import { watchOptionsAfterViewerInit } from './watchOptions'
|
||||
|
||||
const loadBackend = () => {
|
||||
if (options.activeRenderer === 'webgpu') {
|
||||
// appViewer.loadBackend(createWebgpuBackend)
|
||||
} else {
|
||||
appViewer.loadBackend(createGraphicsBackend)
|
||||
}
|
||||
}
|
||||
window.loadBackend = loadBackend
|
||||
if (process.env.SINGLE_FILE_BUILD_MODE) {
|
||||
const unsub = subscribeKey(miscUiState, 'fsReady', () => {
|
||||
if (miscUiState.fsReady) {
|
||||
// don't do it earlier to load fs and display menu faster
|
||||
loadBackend()
|
||||
unsub()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
loadBackend()
|
||||
}
|
||||
|
||||
const animLoop = () => {
|
||||
for (const fn of beforeRenderFrame) fn()
|
||||
requestAnimationFrame(animLoop)
|
||||
}
|
||||
requestAnimationFrame(animLoop)
|
||||
|
||||
watchOptionsAfterViewerInit()
|
||||
|
|
@ -2,12 +2,13 @@ import fs from 'fs'
|
|||
import { join } from 'path'
|
||||
import JSZip from 'jszip'
|
||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||
import { readLevelDat } from './loadSave'
|
||||
import { fsState, readLevelDat } from './loadSave'
|
||||
import { closeWan, openToWanAndCopyJoinLink } from './localServerMultiplayer'
|
||||
import { copyFilesAsync, uniqueFileNameFromWorldName } from './browserfs'
|
||||
import { saveServer } from './flyingSquidUtils'
|
||||
import { setLoadingScreenStatus } from './appStatus'
|
||||
import { displayClientChat } from './botUtils'
|
||||
import { miscUiState } from './globalState'
|
||||
|
||||
const notImplemented = () => {
|
||||
return 'Not implemented yet'
|
||||
|
|
@ -69,7 +70,7 @@ export const exportWorld = async (path: string, type: 'zip' | 'folder', zipName
|
|||
// todo include in help
|
||||
const exportLoadedWorld = async () => {
|
||||
await saveServer()
|
||||
let { worldFolder } = localServer!.options
|
||||
let worldFolder = fsState.inMemorySavePath
|
||||
if (!worldFolder.startsWith('/')) worldFolder = `/${worldFolder}`
|
||||
await exportWorld(worldFolder, 'zip')
|
||||
}
|
||||
|
|
@ -141,12 +142,12 @@ export const commands: Array<{
|
|||
]
|
||||
//@ts-format-ignore-endregion
|
||||
|
||||
export const getBuiltinCommandsList = () => commands.filter(command => command.alwaysAvailable || localServer).flatMap(command => command.command)
|
||||
export const getBuiltinCommandsList = () => commands.filter(command => command.alwaysAvailable || miscUiState.singleplayer).flatMap(command => command.command)
|
||||
|
||||
export const tryHandleBuiltinCommand = (message: string) => {
|
||||
const [userCommand, ...args] = message.split(' ')
|
||||
|
||||
for (const command of commands.filter(command => command.alwaysAvailable || localServer)) {
|
||||
for (const command of commands.filter(command => command.alwaysAvailable || miscUiState.singleplayer)) {
|
||||
if (command.command.includes(userCommand)) {
|
||||
void command.invoke(args) // ignoring for now
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -417,8 +417,9 @@ const alwaysPressedHandledCommand = (command: Command) => {
|
|||
|
||||
export function lockUrl () {
|
||||
let newQs = ''
|
||||
if (fsState.saveLoaded) {
|
||||
const save = localServer!.options.worldFolder.split('/').at(-1)
|
||||
if (fsState.saveLoaded && fsState.inMemorySave) {
|
||||
const worldFolder = fsState.inMemorySavePath
|
||||
const save = worldFolder.split('/').at(-1)
|
||||
newQs = `loadSave=${save}`
|
||||
} else if (process.env.NODE_ENV === 'development') {
|
||||
newQs = `reconnect=1`
|
||||
|
|
@ -579,7 +580,7 @@ contro.on('release', ({ command }) => {
|
|||
|
||||
export const f3Keybinds: Array<{
|
||||
key?: string,
|
||||
action: () => void,
|
||||
action: () => void | Promise<void>,
|
||||
mobileTitle: string
|
||||
enabled?: () => boolean
|
||||
}> = [
|
||||
|
|
@ -694,7 +695,7 @@ document.addEventListener('keydown', (e) => {
|
|||
if (hardcodedPressedKeys.has('F3')) {
|
||||
const keybind = f3Keybinds.find((v) => v.key === e.code)
|
||||
if (keybind && (keybind.enabled?.() ?? true)) {
|
||||
keybind.action()
|
||||
void keybind.action()
|
||||
e.stopPropagation()
|
||||
}
|
||||
return
|
||||
|
|
@ -933,13 +934,17 @@ window.addEventListener('keydown', (e) => {
|
|||
if (e.code === 'KeyL' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
console.clear()
|
||||
}
|
||||
if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
if (e.code === 'KeyK' && e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
if (sessionStorage.delayLoadUntilFocus) {
|
||||
sessionStorage.removeItem('delayLoadUntilFocus')
|
||||
} else {
|
||||
sessionStorage.setItem('delayLoadUntilFocus', 'true')
|
||||
}
|
||||
}
|
||||
if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger
|
||||
}
|
||||
})
|
||||
// #endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -124,12 +124,12 @@ export const preventThrottlingWithSound = () => {
|
|||
// Start playing
|
||||
oscillator.start()
|
||||
|
||||
return () => {
|
||||
return async () => {
|
||||
try {
|
||||
oscillator.stop()
|
||||
audioContext.close()
|
||||
await audioContext.close()
|
||||
} catch (err) {
|
||||
console.error('Error stopping silent audio:', err)
|
||||
console.warn('Error stopping silent audio:', err)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ window.inspectPlayer = () => require('fs').promises.readFile('/world/playerdata/
|
|||
|
||||
Object.defineProperty(window, 'debugSceneChunks', {
|
||||
get () {
|
||||
return (window.world as WorldRendererThree)?.getLoadedChunksRelative?.(bot.entity.position, true)
|
||||
if (!(window.world instanceof WorldRendererThree)) return undefined
|
||||
return (window.world)?.getLoadedChunksRelative?.(bot.entity.position, true)
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -149,7 +150,7 @@ Object.defineProperty(window, 'debugToggle', {
|
|||
})
|
||||
|
||||
customEvents.on('gameLoaded', () => {
|
||||
window.holdingBlock = (window.world as WorldRendererThree).holdingBlock
|
||||
window.holdingBlock = (window.world as WorldRendererThree | undefined)?.holdingBlock
|
||||
})
|
||||
|
||||
window.clearStorage = (...keysToKeep: string[]) => {
|
||||
|
|
|
|||
13
src/globals.d.ts
vendored
13
src/globals.d.ts
vendored
|
|
@ -34,4 +34,17 @@ declare interface Document {
|
|||
exitPointerLock?(): void
|
||||
}
|
||||
|
||||
declare module '*.frag' {
|
||||
const png: string
|
||||
export default png
|
||||
}
|
||||
declare module '*.vert' {
|
||||
const png: string
|
||||
export default png
|
||||
}
|
||||
declare module '*.wgsl' {
|
||||
const png: string
|
||||
export default png
|
||||
}
|
||||
|
||||
declare interface Window extends Record<string, any> { }
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ window.worldView = undefined
|
|||
window.viewer = undefined
|
||||
window.loadedData = undefined
|
||||
window.customEvents = new EventEmitter()
|
||||
window.customEvents.setMaxListeners(10_000)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import React from 'react'
|
|||
import { loadScript } from 'renderer/viewer/lib/utils'
|
||||
import { loadGoogleDriveApi, loadInMemorySave } from './react/SingleplayerProvider'
|
||||
import { setLoadingScreenStatus } from './appStatus'
|
||||
import { mountGoogleDriveFolder } from './browserfs'
|
||||
import { showOptionsModal } from './react/SelectOption'
|
||||
import { appQueryParams } from './appParams'
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ export const possiblyHandleStateVariable = async () => {
|
|||
}
|
||||
setLoadingScreenStatus('Opening world in read only mode...')
|
||||
googleProviderState.accessToken = response.access_token
|
||||
await mountGoogleDriveFolder(true, parsed.ids[0])
|
||||
// await mountGoogleDriveFolder(true, parsed.ids[0])
|
||||
await loadInMemorySave('/google')
|
||||
}
|
||||
})
|
||||
|
|
|
|||
33
src/index.ts
33
src/index.ts
|
|
@ -95,8 +95,7 @@ import { startLocalReplayServer } from './packetsReplay/replayPackets'
|
|||
import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording'
|
||||
import { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter'
|
||||
import { appViewer } from './appViewer'
|
||||
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import './appViewerLoad'
|
||||
|
||||
window.debug = debug
|
||||
window.beforeRenderFrame = []
|
||||
|
|
@ -115,30 +114,6 @@ customChannels()
|
|||
|
||||
if (appQueryParams.testCrashApp === '2') throw new Error('test')
|
||||
|
||||
const loadBackend = () => {
|
||||
appViewer.loadBackend(createGraphicsBackend)
|
||||
}
|
||||
window.loadBackend = loadBackend
|
||||
if (process.env.SINGLE_FILE_BUILD_MODE) {
|
||||
const unsub = subscribeKey(miscUiState, 'fsReady', () => {
|
||||
if (miscUiState.fsReady) {
|
||||
// don't do it earlier to load fs and display menu faster
|
||||
loadBackend()
|
||||
unsub()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
loadBackend()
|
||||
}
|
||||
|
||||
const animLoop = () => {
|
||||
for (const fn of beforeRenderFrame) fn()
|
||||
requestAnimationFrame(animLoop)
|
||||
}
|
||||
requestAnimationFrame(animLoop)
|
||||
|
||||
watchOptionsAfterViewerInit()
|
||||
|
||||
function hideCurrentScreens () {
|
||||
activeModalStacks['main-menu'] = [...activeModalStack]
|
||||
insertActiveModalStack('', [])
|
||||
|
|
@ -223,6 +198,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
let bot!: typeof __type_bot
|
||||
const destroyAll = () => {
|
||||
if (ended) return
|
||||
errorAbortController.abort()
|
||||
ended = true
|
||||
progress.end()
|
||||
// dont reset viewer so we can still do debugging
|
||||
|
|
@ -240,7 +216,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
//@ts-expect-error
|
||||
window.bot = bot = undefined
|
||||
}
|
||||
resetStateAfterDisconnect()
|
||||
cleanFs()
|
||||
}
|
||||
const cleanFs = () => {
|
||||
|
|
@ -261,7 +236,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
if (err === 'ResizeObserver loop completed with undelivered notifications.') {
|
||||
return
|
||||
}
|
||||
errorAbortController.abort()
|
||||
if (isCypress()) throw err
|
||||
miscUiState.hasErrors = true
|
||||
if (miscUiState.gameLoaded) return
|
||||
|
|
@ -272,6 +246,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
destroyAll()
|
||||
}
|
||||
|
||||
// todo(hard): remove it!
|
||||
const errorAbortController = new AbortController()
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
if (e.reason.name === 'ServerPluginLoadFailure') {
|
||||
|
|
@ -732,7 +707,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
|
||||
|
||||
console.log('bot spawned - starting viewer')
|
||||
appViewer.startWorld(bot.world, renderDistance)
|
||||
await appViewer.startWorld(bot.world, renderDistance)
|
||||
appViewer.worldView!.listenToBot(bot)
|
||||
|
||||
initMotionTracking()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ export const fsState = proxy({
|
|||
saveLoaded: false,
|
||||
openReadOperations: 0,
|
||||
openWriteOperations: 0,
|
||||
remoteBackend: false
|
||||
remoteBackend: false,
|
||||
inMemorySavePath: ''
|
||||
})
|
||||
|
||||
const PROPOSE_BACKUP = true
|
||||
|
|
@ -181,6 +182,7 @@ export const loadSave = async (root = '/world') => {
|
|||
|
||||
// todo should not be set here
|
||||
fsState.saveLoaded = true
|
||||
fsState.inMemorySavePath = root
|
||||
window.dispatchEvent(new CustomEvent('singleplayer', {
|
||||
// todo check gamemode level.dat data etc
|
||||
detail: {
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ export const guiOptionsScheme: {
|
|||
custom () {
|
||||
return <Button label='Guide: Disable VSync' onClick={() => openURL('https://gist.github.com/zardoy/6e5ce377d2b4c1e322e660973da069cd')} inScreen />
|
||||
},
|
||||
},
|
||||
{
|
||||
backgroundRendering: {
|
||||
text: 'Background FPS limit',
|
||||
values: [
|
||||
|
|
@ -71,6 +69,12 @@ export const guiOptionsScheme: {
|
|||
['20fps', '20 FPS'],
|
||||
],
|
||||
},
|
||||
activeRenderer: {
|
||||
text: 'Renderer',
|
||||
values: [
|
||||
['threejs', 'Three.js (stable)'],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
|
|
@ -108,12 +112,12 @@ export const guiOptionsScheme: {
|
|||
},
|
||||
{
|
||||
custom () {
|
||||
const { _renderByChunks } = useSnapshot(options).rendererOptions.three
|
||||
const { _renderByChunks } = useSnapshot(options).rendererSharedOptions
|
||||
return <Button
|
||||
inScreen
|
||||
label={`Batch Chunks Display ${_renderByChunks ? 'ON' : 'OFF'}`}
|
||||
onClick={() => {
|
||||
options.rendererOptions.three._renderByChunks = !_renderByChunks
|
||||
options.rendererSharedOptions._renderByChunks = !_renderByChunks
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const defaultOptions = {
|
|||
touchButtonsOpacity: 80,
|
||||
touchButtonsPosition: 12,
|
||||
touchControlsPositions: getDefaultTouchControlsPositions(),
|
||||
touchControlsSize: getTouchControlsSize(),
|
||||
touchMovementType: 'modern' as 'modern' | 'classic',
|
||||
touchInteractionType: 'classic' as 'classic' | 'buttons',
|
||||
gpuPreference: 'default' as 'default' | 'high-performance' | 'low-power',
|
||||
|
|
@ -109,11 +110,10 @@ const defaultOptions = {
|
|||
disabledUiParts: [] as string[],
|
||||
neighborChunkUpdates: true,
|
||||
highlightBlockColor: 'auto' as 'auto' | 'blue' | 'classic',
|
||||
rendererOptions: {
|
||||
three: {
|
||||
_experimentalSmoothChunkLoading: true,
|
||||
_renderByChunks: false
|
||||
}
|
||||
activeRenderer: 'threejs',
|
||||
rendererSharedOptions: {
|
||||
_experimentalSmoothChunkLoading: true,
|
||||
_renderByChunks: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +138,16 @@ function getDefaultTouchControlsPositions () {
|
|||
} as Record<string, [number, number]>
|
||||
}
|
||||
|
||||
function getTouchControlsSize () {
|
||||
return {
|
||||
joystick: 60,
|
||||
action: 36,
|
||||
break: 36,
|
||||
jump: 36,
|
||||
sneak: 36,
|
||||
}
|
||||
}
|
||||
|
||||
// const qsOptionsRaw = new URLSearchParams(location.search).getAll('setting')
|
||||
const qsOptionsRaw = appQueryParamsArray.setting ?? []
|
||||
export const qsOptions = Object.fromEntries(qsOptionsRaw.map(o => {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { ParsedReplayPacket, parseReplayContents } from 'mcraft-fun-mineflayer/b
|
|||
import { PACKETS_REPLAY_FILE_EXTENSION, WORLD_STATE_FILE_EXTENSION } from 'mcraft-fun-mineflayer/build/worldState'
|
||||
import MinecraftData from 'minecraft-data'
|
||||
import { GameMode } from 'mineflayer'
|
||||
import { LocalServer } from '../customServer'
|
||||
import { UserError } from '../mineflayer/userError'
|
||||
import { packetsReplayState } from '../react/state/packetsReplayState'
|
||||
import { getFixedFilesize } from '../react/simpleUtils'
|
||||
import { appQueryParams } from '../appParams'
|
||||
import { LocalServer } from '../customServer'
|
||||
|
||||
const SUPPORTED_FORMAT_VERSION = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -71,12 +71,36 @@ export default () => {
|
|||
useDidUpdateEffect(() => {
|
||||
// todo play effect only when world successfully loaded
|
||||
if (!isOpen) {
|
||||
const divingElem: HTMLElement = document.querySelector('#viewer-canvas')!
|
||||
divingElem.style.animationName = 'dive-animation'
|
||||
divingElem.parentElement!.style.perspective = '1200px'
|
||||
divingElem.onanimationend = () => {
|
||||
divingElem.parentElement!.style.perspective = ''
|
||||
divingElem.onanimationend = null
|
||||
const startDiveAnimation = (divingElem: HTMLElement) => {
|
||||
divingElem.style.animationName = 'dive-animation'
|
||||
divingElem.parentElement!.style.perspective = '1200px'
|
||||
divingElem.onanimationend = () => {
|
||||
divingElem.parentElement!.style.perspective = ''
|
||||
divingElem.onanimationend = null
|
||||
}
|
||||
}
|
||||
|
||||
const divingElem = document.querySelector('#viewer-canvas')
|
||||
let observer: MutationObserver | null = null
|
||||
if (divingElem) {
|
||||
startDiveAnimation(divingElem as HTMLElement)
|
||||
} else {
|
||||
observer = new MutationObserver((mutations) => {
|
||||
const divingElem = document.querySelector('#viewer-canvas')
|
||||
if (divingElem) {
|
||||
startDiveAnimation(divingElem as HTMLElement)
|
||||
observer!.disconnect()
|
||||
}
|
||||
})
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isOpen])
|
||||
|
|
|
|||
|
|
@ -9,14 +9,15 @@ interface Props extends React.ComponentProps<typeof Button> {
|
|||
localStorageKey?: string | null
|
||||
offset?: number
|
||||
}
|
||||
alwaysTooltip?: string
|
||||
}
|
||||
|
||||
const ARROW_HEIGHT = 7
|
||||
const GAP = 0
|
||||
|
||||
export default ({ initialTooltip, ...args }: Props) => {
|
||||
export default ({ initialTooltip, alwaysTooltip, ...args }: Props) => {
|
||||
const { localStorageKey = 'firstTimeTooltip', offset = 0 } = initialTooltip
|
||||
const [showTooltips, setShowTooltips] = useState(localStorageKey ? localStorage[localStorageKey] !== 'false' : true)
|
||||
const [showTooltips, setShowTooltips] = useState(alwaysTooltip || (localStorageKey ? localStorage[localStorageKey] !== 'false' : true))
|
||||
|
||||
useEffect(() => {
|
||||
let timeout
|
||||
|
|
@ -67,7 +68,7 @@ export default ({ initialTooltip, ...args }: Props) => {
|
|||
zIndex: 11
|
||||
}}
|
||||
>
|
||||
{initialTooltip.content}
|
||||
{alwaysTooltip || initialTooltip.content}
|
||||
<FloatingArrow ref={arrowRef} context={context} style={{ opacity: 0.7 }} />
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@
|
|||
}
|
||||
|
||||
.debug-left-side {
|
||||
top: 1px;
|
||||
top: 25px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.debug-right-side {
|
||||
top: 5px;
|
||||
top: 25px;
|
||||
right: 1px;
|
||||
/* limit renderer long text width */
|
||||
width: 50%;
|
||||
|
|
|
|||
|
|
@ -112,8 +112,10 @@ export default () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
let mapsProviderUrl = appConfig?.mapsProvider
|
||||
if (mapsProviderUrl && location.origin !== 'https://mcraft.fun') mapsProviderUrl = mapsProviderUrl + '?to=' + encodeURIComponent(location.href)
|
||||
const mapsProviderUrl = appConfig?.mapsProvider && new URL(appConfig?.mapsProvider)
|
||||
if (mapsProviderUrl && location.origin !== 'https://mcraft.fun') {
|
||||
mapsProviderUrl.searchParams.set('to', location.href)
|
||||
}
|
||||
|
||||
// todo clean, use custom csstransition
|
||||
return <Transition in={!noDisplay} timeout={disableAnimation ? 0 : 100} mountOnEnter unmountOnExit>
|
||||
|
|
@ -134,7 +136,7 @@ export default () => {
|
|||
openFilePicker()
|
||||
}
|
||||
}}
|
||||
mapsProvider={mapsProviderUrl}
|
||||
mapsProvider={mapsProviderUrl?.toString()}
|
||||
versionStatus={versionStatus}
|
||||
versionTitle={versionTitle}
|
||||
onVersionStatusClick={async () => {
|
||||
|
|
|
|||
|
|
@ -53,9 +53,8 @@ export const saveToBrowserMemory = async () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
//@ts-expect-error
|
||||
const { worldFolder } = localServer.options
|
||||
const saveRootPath = await uniqueFileNameFromWorldName(worldFolder.split('/').pop(), `/data/worlds`)
|
||||
const worldFolder = fsState.inMemorySavePath
|
||||
const saveRootPath = await uniqueFileNameFromWorldName(worldFolder.split('/').pop()!, `/data/worlds`)
|
||||
await mkdirRecursive(saveRootPath)
|
||||
console.log('made world folder', saveRootPath)
|
||||
const allRootPaths = [...usedServerPathsV1]
|
||||
|
|
@ -290,7 +289,7 @@ export default () => {
|
|||
) : null}
|
||||
{!lockConnect && <>
|
||||
<Button className="button" style={{ width: '204px' }} onClick={disconnect}>
|
||||
{localServer && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
|
||||
{fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
|
||||
</Button>
|
||||
</>}
|
||||
{noConnection && (
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export class LoadedResources {
|
|||
export interface ResourcesCurrentConfig {
|
||||
version: string
|
||||
texturesVersion?: string
|
||||
noBlockstatesModels?: boolean
|
||||
// noBlockstatesModels?: boolean
|
||||
noInventoryGui?: boolean
|
||||
includeOnlyBlocks?: string[]
|
||||
}
|
||||
|
|
@ -115,34 +115,12 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|||
...resources.customModels
|
||||
}
|
||||
|
||||
|
||||
const blocksAssetsParser = new AtlasParser(this.sourceBlocksAtlases, blocksAtlasLatest, blocksAtlasLegacy)
|
||||
const itemsAssetsParser = new AtlasParser(this.sourceItemsAtlases, itemsAtlasLatest, itemsAtlasLegacy)
|
||||
|
||||
const blockTexturesChanges = {} as Record<string, string>
|
||||
const date = new Date()
|
||||
if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) {
|
||||
Object.assign(blockTexturesChanges, christmasPack)
|
||||
}
|
||||
|
||||
const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {})
|
||||
const customItemTextures = Object.keys(resources.customTextures.items?.textures ?? {})
|
||||
|
||||
console.time('createBlocksAtlas')
|
||||
const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(
|
||||
resources.texturesVersion,
|
||||
(textureName) => {
|
||||
if (this.currentConfig!.includeOnlyBlocks && !this.currentConfig!.includeOnlyBlocks.includes(textureName)) return false
|
||||
const texture = resources.customTextures.blocks?.textures[textureName]
|
||||
return blockTexturesChanges[textureName] ?? texture
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
customBlockTextures
|
||||
)
|
||||
console.timeEnd('createBlocksAtlas')
|
||||
await this.recreateBlockAtlas(resources)
|
||||
|
||||
if (abortController.signal.aborted) return
|
||||
|
||||
const itemsAssetsParser = new AtlasParser(this.sourceItemsAtlases, itemsAtlasLatest, itemsAtlasLegacy)
|
||||
const customItemTextures = Object.keys(resources.customTextures.items?.textures ?? {})
|
||||
console.time('createItemsAtlas')
|
||||
const { atlas: itemsAtlas, canvas: itemsCanvas } = await itemsAssetsParser.makeNewAtlas(
|
||||
resources.texturesVersion,
|
||||
|
|
@ -157,9 +135,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|||
)
|
||||
console.timeEnd('createItemsAtlas')
|
||||
|
||||
resources.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL())
|
||||
resources.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL())
|
||||
resources.blocksAtlasImage = await getLoadedImage(blocksCanvas.toDataURL())
|
||||
resources.itemsAtlasImage = await getLoadedImage(itemsCanvas.toDataURL())
|
||||
|
||||
if (resources.version && resources.blockstatesModels && resources.itemsAtlasParser && resources.blocksAtlasParser) {
|
||||
|
|
@ -169,11 +145,6 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|||
resources.itemsAtlasParser,
|
||||
resources.blocksAtlasParser
|
||||
)
|
||||
resources.worldBlockProvider = worldBlockProvider(
|
||||
resources.blockstatesModels,
|
||||
resources.blocksAtlasParser.atlas,
|
||||
STABLE_MODELS_VERSION
|
||||
)
|
||||
}
|
||||
|
||||
if (abortController.signal.aborted) return
|
||||
|
|
@ -196,6 +167,43 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|||
}
|
||||
}
|
||||
|
||||
async recreateBlockAtlas (resources: LoadedResources = this.currentResources!) {
|
||||
const blockTexturesChanges = {} as Record<string, string>
|
||||
const date = new Date()
|
||||
if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) {
|
||||
Object.assign(blockTexturesChanges, christmasPack)
|
||||
}
|
||||
|
||||
const blocksAssetsParser = new AtlasParser(this.sourceBlocksAtlases, blocksAtlasLatest, blocksAtlasLegacy)
|
||||
|
||||
const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {})
|
||||
console.time('createBlocksAtlas')
|
||||
const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(
|
||||
resources.texturesVersion,
|
||||
(textureName) => {
|
||||
if (this.currentConfig!.includeOnlyBlocks && !this.currentConfig!.includeOnlyBlocks.includes(textureName)) return false
|
||||
const texture = resources.customTextures.blocks?.textures[textureName]
|
||||
return blockTexturesChanges[textureName] ?? texture
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
customBlockTextures,
|
||||
{
|
||||
needHorizontalIndexes: !!this.currentConfig!.includeOnlyBlocks,
|
||||
}
|
||||
)
|
||||
console.timeEnd('createBlocksAtlas')
|
||||
|
||||
resources.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL())
|
||||
resources.blocksAtlasImage = await getLoadedImage(blocksCanvas.toDataURL())
|
||||
|
||||
resources.worldBlockProvider = worldBlockProvider(
|
||||
resources.blockstatesModels,
|
||||
resources.blocksAtlasParser.atlas,
|
||||
STABLE_MODELS_VERSION
|
||||
)
|
||||
}
|
||||
|
||||
async generateGuiTextures () {
|
||||
await generateGuiAtlas()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-useless-empty-export
|
||||
export { }
|
||||
export default {}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
const BrowserFS = require('browserfs')
|
||||
|
||||
module.exports = BrowserFS.BFSRequire('fs')
|
||||
globalThis.fs ??= BrowserFS.BFSRequire('fs')
|
||||
globalThis.fs.promises = new Proxy({}, {
|
||||
get(target, p) {
|
||||
return (...args) => {
|
||||
return globalThis.promises[p](...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = globalThis.fs
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
module.exports.performance = window.performance
|
||||
module.exports.performance = globalThis.performance
|
||||
|
|
|
|||
14
src/utils.ts
14
src/utils.ts
|
|
@ -65,6 +65,16 @@ export const pointerLock = {
|
|||
}
|
||||
}
|
||||
|
||||
export const logAction = (category: string, action: string, value?: string, label?: string) => {
|
||||
if (!options.externalLoggingService) return
|
||||
window.loggingServiceChannel?.({
|
||||
category,
|
||||
action,
|
||||
value,
|
||||
label
|
||||
})
|
||||
}
|
||||
|
||||
export const isInRealGameSession = () => {
|
||||
return isGameActive(true) && (!packetsReplayState.isOpen || packetsReplayState.isMinimized) && !gameAdditionalState.viewerConnection
|
||||
}
|
||||
|
|
@ -148,11 +158,11 @@ export const setRenderDistance = () => {
|
|||
localServer!.players[0].view = 0
|
||||
renderDistance = 0
|
||||
}
|
||||
worldView.updateViewDistance(renderDistance)
|
||||
worldView?.updateViewDistance(renderDistance)
|
||||
prevRenderDistance = renderDistance
|
||||
}
|
||||
export const reloadChunks = async () => {
|
||||
if (!worldView) return
|
||||
if (!bot || !worldView) return
|
||||
setRenderDistance()
|
||||
await worldView.updatePosition(bot.entity.position, true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { reloadChunks } from './utils'
|
|||
import { miscUiState } from './globalState'
|
||||
import { isCypress } from './standaloneUtils'
|
||||
|
||||
globalThis.viewer ??= { world: {} }
|
||||
|
||||
subscribeKey(options, 'renderDistance', reloadChunks)
|
||||
subscribeKey(options, 'multiplayerRenderDistance', reloadChunks)
|
||||
|
||||
|
|
@ -85,8 +87,8 @@ export const watchOptionsAfterViewerInit = () => {
|
|||
appViewer.inWorldRenderingConfig.extraBlockRenderers = !o.disableSignsMapsSupport
|
||||
appViewer.inWorldRenderingConfig.fetchPlayerSkins = o.loadPlayerSkins
|
||||
appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor
|
||||
appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererOptions.three._experimentalSmoothChunkLoading
|
||||
appViewer.inWorldRenderingConfig._renderByChunks = o.rendererOptions.three._renderByChunks
|
||||
appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading
|
||||
appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks
|
||||
})
|
||||
|
||||
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ref } from 'valtio'
|
||||
import { watchUnloadForCleanup } from './gameUnload'
|
||||
|
||||
let inWater = false
|
||||
|
|
@ -9,9 +10,10 @@ customEvents.on('gameLoaded', () => {
|
|||
watchUnloadForCleanup(cleanup)
|
||||
|
||||
const updateInWater = () => {
|
||||
const waterBr = Object.keys(bot.entity.effects).find((effect: any) => loadedData.effects[effect.id].name === 'water_breathing')
|
||||
const waterBr = Object.keys(bot.entity.effects).find((effect: any) => loadedData.effects[effect.id]?.name === 'water_breathing')
|
||||
if (inWater) {
|
||||
appViewer.playerState.reactive.inWater = true
|
||||
appViewer.playerState.reactive.waterBreathing = waterBr !== undefined
|
||||
} else {
|
||||
cleanup()
|
||||
}
|
||||
|
|
@ -31,5 +33,5 @@ let sceneBg = { r: 0, g: 0, b: 0 }
|
|||
export const updateBackground = (newSceneBg = sceneBg) => {
|
||||
sceneBg = newSceneBg
|
||||
const color: [number, number, number] = inWater ? [0, 0, 1] : [sceneBg.r, sceneBg.g, sceneBg.b]
|
||||
appViewer.playerState.reactive.backgroundColor = color
|
||||
appViewer.playerState.reactive.backgroundColor = ref(color)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue