Compare commits
5 commits
next
...
visual-cop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9236d5612b | ||
|
|
079f9404f6 | ||
|
|
488240f32a | ||
|
|
6c157245bb | ||
|
|
753821b01a |
9 changed files with 263 additions and 37 deletions
|
|
@ -282,6 +282,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
|
||||||
|
|
||||||
const aos: number[] = []
|
const aos: number[] = []
|
||||||
const neighborPos = position.plus(new Vec3(...dir))
|
const neighborPos = position.plus(new Vec3(...dir))
|
||||||
|
// 10%
|
||||||
const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
|
const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
|
||||||
for (const pos of corners) {
|
for (const pos of corners) {
|
||||||
let vertex = [
|
let vertex = [
|
||||||
|
|
@ -290,7 +291,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
|
||||||
(pos[2] ? maxz : minz)
|
(pos[2] ? maxz : minz)
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!needTiles) {
|
if (!needTiles) { // 10%
|
||||||
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
|
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
|
||||||
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
||||||
vertex = vertex.map(v => v / 16)
|
vertex = vertex.map(v => v / 16)
|
||||||
|
|
@ -411,7 +412,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||||
// todo this can be removed here
|
// todo this can be removed here
|
||||||
signs: {},
|
signs: {},
|
||||||
// isFull: true,
|
// isFull: true,
|
||||||
highestBlocks: {},
|
highestBlocks: {}, // todo migrate to map for 2% boost perf
|
||||||
hadErrors: false
|
hadErrors: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,7 +450,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||||
}
|
}
|
||||||
const biome = block.biome.name
|
const biome = block.biome.name
|
||||||
|
|
||||||
if (world.preflat) {
|
if (world.preflat) { // 10% perf
|
||||||
const patchProperties = preflatBlockCalculation(block, world, cursor)
|
const patchProperties = preflatBlockCalculation(block, world, cursor)
|
||||||
if (patchProperties) {
|
if (patchProperties) {
|
||||||
block._originalProperties ??= block._properties
|
block._originalProperties ??= block._properties
|
||||||
|
|
@ -505,6 +506,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||||
const model = modelVars[useVariant] ?? modelVars[0]
|
const model = modelVars[useVariant] ?? modelVars[0]
|
||||||
if (!model) continue
|
if (!model) continue
|
||||||
|
|
||||||
|
// #region 10%
|
||||||
let globalMatrix = null as any
|
let globalMatrix = null as any
|
||||||
let globalShift = null as any
|
let globalShift = null as any
|
||||||
for (const axis of ['x', 'y', 'z'] as const) {
|
for (const axis of ['x', 'y', 'z'] as const) {
|
||||||
|
|
@ -518,6 +520,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||||
globalShift = [8, 8, 8]
|
globalShift = [8, 8, 8]
|
||||||
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
|
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
for (const element of model.elements ?? []) {
|
for (const element of model.elements ?? []) {
|
||||||
const ao = model.ao ?? true
|
const ao = model.ao ?? true
|
||||||
|
|
@ -527,6 +530,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||||
renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome)
|
renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// 60%
|
||||||
renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome)
|
renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,18 @@ import { useEffect, useState } from 'react'
|
||||||
import styles from './appStatus.module.css'
|
import styles from './appStatus.module.css'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import Screen from './Screen'
|
import Screen from './Screen'
|
||||||
|
import LoadingChunks from './LoadingChunks'
|
||||||
|
|
||||||
export default ({ status, isError, hideDots = false, lastStatus = '', backAction = undefined as undefined | (() => void), description = '', actionsSlot = null as React.ReactNode | null }) => {
|
export default ({
|
||||||
|
status,
|
||||||
|
isError,
|
||||||
|
hideDots = false,
|
||||||
|
lastStatus = '',
|
||||||
|
backAction = undefined as undefined | (() => void),
|
||||||
|
description = '',
|
||||||
|
actionsSlot = null as React.ReactNode | null,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
const [loadingDots, setLoadingDots] = useState('')
|
const [loadingDots, setLoadingDots] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -52,6 +62,7 @@ export default ({ status, isError, hideDots = false, lastStatus = '', backAction
|
||||||
<Button onClick={() => window.location.reload()} label="Reset App (recommended)" />
|
<Button onClick={() => window.location.reload()} label="Reset App (recommended)" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{children}
|
||||||
</Screen>
|
</Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { proxy, useSnapshot } from 'valtio'
|
import { proxy, useSnapshot } from 'valtio'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { activeModalStack, activeModalStacks, hideModal, insertActiveModalStack, miscUiState } from '../globalState'
|
import { activeModalStack, activeModalStacks, hideModal, insertActiveModalStack, miscUiState, showModal } from '../globalState'
|
||||||
import { resetLocalStorageWorld } from '../browserfs'
|
import { resetLocalStorageWorld } from '../browserfs'
|
||||||
import { fsState } from '../loadSave'
|
import { fsState } from '../loadSave'
|
||||||
import { guessProblem } from '../errorLoadingScreenHelpers'
|
import { guessProblem } from '../errorLoadingScreenHelpers'
|
||||||
|
|
@ -14,6 +14,7 @@ import { useIsModalActive } from './utilsApp'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import { AuthenticatedAccount, updateAuthenticatedAccountData, updateLoadedServerData } from './ServersListProvider'
|
import { AuthenticatedAccount, updateAuthenticatedAccountData, updateLoadedServerData } from './ServersListProvider'
|
||||||
import { showOptionsModal } from './SelectOption'
|
import { showOptionsModal } from './SelectOption'
|
||||||
|
import LoadingChunks from './LoadingChunks'
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
status: '',
|
status: '',
|
||||||
|
|
@ -22,9 +23,12 @@ const initialState = {
|
||||||
descriptionHint: '',
|
descriptionHint: '',
|
||||||
isError: false,
|
isError: false,
|
||||||
hideDots: false,
|
hideDots: false,
|
||||||
|
loadingChunksData: null as null | Record<string, string>,
|
||||||
|
loadingChunksDataPlayerChunk: null as null | { x: number, z: number },
|
||||||
|
isDisplaying: false
|
||||||
}
|
}
|
||||||
export const appStatusState = proxy(initialState)
|
export const appStatusState = proxy(initialState)
|
||||||
const resetState = () => {
|
export const resetAppStatusState = () => {
|
||||||
Object.assign(appStatusState, initialState)
|
Object.assign(appStatusState, initialState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +37,7 @@ export const lastConnectOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint } = useSnapshot(appStatusState)
|
const { isError, lastStatus, maybeRecoverable, status, hideDots, descriptionHint, loadingChunksData, loadingChunksDataPlayerChunk } = useSnapshot(appStatusState)
|
||||||
const { active: replayActive } = useSnapshot(packetsReplaceSessionState)
|
const { active: replayActive } = useSnapshot(packetsReplaceSessionState)
|
||||||
|
|
||||||
const isOpen = useIsModalActive('app-status')
|
const isOpen = useIsModalActive('app-status')
|
||||||
|
|
@ -52,7 +56,7 @@ export default () => {
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
const reconnect = () => {
|
const reconnect = () => {
|
||||||
resetState()
|
resetAppStatusState()
|
||||||
window.dispatchEvent(new window.CustomEvent('connect', {
|
window.dispatchEvent(new window.CustomEvent('connect', {
|
||||||
detail: lastConnectOptions.value
|
detail: lastConnectOptions.value
|
||||||
}))
|
}))
|
||||||
|
|
@ -93,7 +97,7 @@ export default () => {
|
||||||
lastStatus={lastStatus}
|
lastStatus={lastStatus}
|
||||||
description={displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint}
|
description={displayAuthButton ? '' : (isError ? guessProblem(status) : '') || descriptionHint}
|
||||||
backAction={maybeRecoverable ? () => {
|
backAction={maybeRecoverable ? () => {
|
||||||
resetState()
|
resetAppStatusState()
|
||||||
miscUiState.gameLoaded = false
|
miscUiState.gameLoaded = false
|
||||||
miscUiState.loadedDataVersion = null
|
miscUiState.loadedDataVersion = null
|
||||||
window.loadedData = undefined
|
window.loadedData = undefined
|
||||||
|
|
@ -113,10 +117,25 @@ export default () => {
|
||||||
{replayActive && <Button label='Download Packets Replay' onClick={downloadPacketsReplay} />}
|
{replayActive && <Button label='Download Packets Replay' onClick={downloadPacketsReplay} />}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
{loadingChunksData && <LoadingChunks regionFiles={Object.keys(loadingChunksData)} stateMap={loadingChunksData} playerChunk={loadingChunksDataPlayerChunk} />}
|
||||||
|
{isOpen && <DisplayingIndicator />}
|
||||||
|
</AppStatus>
|
||||||
</DiveTransition>
|
</DiveTransition>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DisplayingIndicator = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
appStatusState.isDisplaying = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
|
||||||
const PossiblyVpnBypassProxyButton = ({ reconnect }: { reconnect: () => void }) => {
|
const PossiblyVpnBypassProxyButton = ({ reconnect }: { reconnect: () => void }) => {
|
||||||
const [vpnBypassProxy, setVpnBypassProxy] = useState('')
|
const [vpnBypassProxy, setVpnBypassProxy] = useState('')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const endExitStyle = { opacity: 0, transform: 'translateZ(150px)' }
|
||||||
const endStyle = { opacity: 1, transform: 'translateZ(0)' }
|
const endStyle = { opacity: 1, transform: 'translateZ(0)' }
|
||||||
|
|
||||||
const stateStyles = {
|
const stateStyles = {
|
||||||
entering: startStyle,
|
entering: endStyle,
|
||||||
entered: endStyle,
|
entered: endStyle,
|
||||||
exiting: endExitStyle,
|
exiting: endExitStyle,
|
||||||
exited: endExitStyle,
|
exited: endExitStyle,
|
||||||
|
|
@ -26,6 +26,15 @@ export default ({ children, open }) => {
|
||||||
if (!mounted && open) {
|
if (!mounted && open) {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
}
|
}
|
||||||
|
let timeout
|
||||||
|
if (mounted && !open) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setMounted(false)
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (timeout) clearTimeout(timeout)
|
||||||
|
}
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
if (!mounted) return null
|
if (!mounted) return null
|
||||||
|
|
@ -43,5 +52,4 @@ export default ({ children, open }) => {
|
||||||
</div>
|
</div>
|
||||||
}}
|
}}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/react/LoadingChunks.css
Normal file
12
src/react/LoadingChunks.css
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
@keyframes loading-chunks-loading-animation {
|
||||||
|
/* blink */
|
||||||
|
0% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/react/LoadingChunks.stories.tsx
Normal file
43
src/react/LoadingChunks.stories.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import LoadingChunks from './LoadingChunks'
|
||||||
|
|
||||||
|
const meta: Meta<typeof LoadingChunks> = {
|
||||||
|
component: LoadingChunks,
|
||||||
|
render (args) {
|
||||||
|
const [stateMap, setStateMap] = useState(Object.fromEntries(args.regionFiles!.map(x => x.split('.').slice(1, 3).map(Number).map(y => y.toString()).join(',')).map(x => [x, 'loading'])))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
// pick random and set to done
|
||||||
|
const random = Math.floor(Math.random() * args.regionFiles!.length)
|
||||||
|
const [x, z] = args.regionFiles![random].split('.').slice(1, 3).map(Number)
|
||||||
|
setStateMap(prev => ({ ...prev, [`${x},${z}`]: 'done' }))
|
||||||
|
}, 1000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <LoadingChunks stateMap={stateMap} {...args} />
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof LoadingChunks>
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
regionFiles: [
|
||||||
|
'r.-1.-1.mca',
|
||||||
|
'r.-1.0.mca',
|
||||||
|
'r.0.-1.mca',
|
||||||
|
'r.0.0.mca',
|
||||||
|
'r.0.1.mca',
|
||||||
|
],
|
||||||
|
playerChunk: {
|
||||||
|
x: -1,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
displayText: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
60
src/react/LoadingChunks.tsx
Normal file
60
src/react/LoadingChunks.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import './LoadingChunks.css'
|
||||||
|
|
||||||
|
export default ({ regionFiles = [] as string[], stateMap = {} as Record<string, string>, displayText = false, playerChunk = null as null | { x: number, z: number } }) => {
|
||||||
|
// visualize downloading chunks
|
||||||
|
const regionNumbers = regionFiles.map(x => x.split('.').slice(1, 3).map(Number))
|
||||||
|
const minX = Math.min(...regionNumbers.map(([x]) => x))
|
||||||
|
const maxX = Math.max(...regionNumbers.map(([x]) => x))
|
||||||
|
const minZ = Math.min(...regionNumbers.map(([, z]) => z))
|
||||||
|
const maxZ = Math.max(...regionNumbers.map(([, z]) => z))
|
||||||
|
const xChunks = maxX - minX + 1
|
||||||
|
const zChunks = maxZ - minZ + 1
|
||||||
|
|
||||||
|
return <div style={{
|
||||||
|
// maxWidth: '80%',
|
||||||
|
// maxHeight: '80%',
|
||||||
|
// aspectRatio: '1',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: `repeat(${xChunks}, 1fr)`,
|
||||||
|
gridTemplateRows: `repeat(${zChunks}, 1fr)`,
|
||||||
|
gap: 1,
|
||||||
|
width: '110px',
|
||||||
|
height: '110px',
|
||||||
|
}}>
|
||||||
|
{Array.from({ length: xChunks * zChunks }).map((_, i) => {
|
||||||
|
const x = minX + i % xChunks
|
||||||
|
const z = minZ + Math.floor(i / xChunks)
|
||||||
|
const file = `r.${x}.${z}.mca`
|
||||||
|
const state = stateMap[file]
|
||||||
|
if (!regionFiles.includes(file)) return <div key={i} style={{ background: 'gray' }} />
|
||||||
|
return <Chunk key={i} x={x} z={z} state={state} displayText={displayText} currentPlayer={playerChunk?.x === x && playerChunk?.z === z} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chunk = ({ x, z, state, displayText, currentPlayer }) => {
|
||||||
|
const text = displayText ? `${x},${z}` : undefined
|
||||||
|
|
||||||
|
return <div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
background: state === 'errored' ? 'red' : state === 'loading' ? 'white' : 'limegreen',
|
||||||
|
animation: state === 'loading' ? `loading-chunks-loading-animation 4s infinite cubic-bezier(0.4, 0, 0.2, 1)` : undefined,
|
||||||
|
transition: 'background 1s',
|
||||||
|
color: state === 'loading' ? 'black' : 'white',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
}}>
|
||||||
|
{/* green dot */}
|
||||||
|
{currentPlayer && <div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
background: 'reed',
|
||||||
|
borderRadius: '50%',
|
||||||
|
width: '5px',
|
||||||
|
height: '5px',
|
||||||
|
zIndex: -1,
|
||||||
|
}} />}
|
||||||
|
{text}</div>
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useSnapshot } from 'valtio'
|
import { subscribe, useSnapshot } from 'valtio'
|
||||||
import { usedServerPathsV1 } from 'flying-squid/dist/lib/modules/world'
|
import { usedServerPathsV1 } from 'flying-squid/dist/lib/modules/world'
|
||||||
import { openURL } from 'prismarine-viewer/viewer/lib/simpleUtils'
|
import { openURL } from 'prismarine-viewer/viewer/lib/simpleUtils'
|
||||||
|
import { Vec3 } from 'vec3'
|
||||||
|
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
||||||
|
import { subscribeKey } from 'valtio/utils'
|
||||||
import {
|
import {
|
||||||
activeModalStack,
|
activeModalStack,
|
||||||
showModal,
|
showModal,
|
||||||
|
|
@ -23,14 +26,29 @@ import Screen from './Screen'
|
||||||
import styles from './PauseScreen.module.css'
|
import styles from './PauseScreen.module.css'
|
||||||
import { DiscordButton } from './DiscordButton'
|
import { DiscordButton } from './DiscordButton'
|
||||||
import { showNotification } from './NotificationProvider'
|
import { showNotification } from './NotificationProvider'
|
||||||
|
import { appStatusState } from './AppStatusProvider'
|
||||||
|
|
||||||
|
const waitForPotentialRender = async () => {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(resolve as any))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const saveToBrowserMemory = async () => {
|
export const saveToBrowserMemory = async () => {
|
||||||
setLoadingScreenStatus('Saving world')
|
setLoadingScreenStatus('Saving world')
|
||||||
try {
|
try {
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
subscribeKey(appStatusState, 'isDisplaying', () => {
|
||||||
|
if (appStatusState.isDisplaying) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
const { worldFolder } = localServer.options
|
const { worldFolder } = localServer.options
|
||||||
const saveRootPath = await uniqueFileNameFromWorldName(worldFolder.split('/').pop(), `/data/worlds`)
|
const saveRootPath = await uniqueFileNameFromWorldName(worldFolder.split('/').pop(), `/data/worlds`)
|
||||||
await mkdirRecursive(saveRootPath)
|
await mkdirRecursive(saveRootPath)
|
||||||
|
console.log('made world folder', saveRootPath)
|
||||||
const allRootPaths = [...usedServerPathsV1]
|
const allRootPaths = [...usedServerPathsV1]
|
||||||
const allFilesToCopy = [] as string[]
|
const allFilesToCopy = [] as string[]
|
||||||
for (const dirBase of allRootPaths) {
|
for (const dirBase of allRootPaths) {
|
||||||
|
|
@ -46,39 +64,87 @@ export const saveToBrowserMemory = async () => {
|
||||||
}
|
}
|
||||||
allFilesToCopy.push(...res.map(x => join(dirBase, x)))
|
allFilesToCopy.push(...res.map(x => join(dirBase, x)))
|
||||||
}
|
}
|
||||||
const pathsSplit = allFilesToCopy.reduce((acc, cur, i) => {
|
console.log('paths collected')
|
||||||
if (i % 15 === 0) {
|
const pathsSplitBasic = allFilesToCopy.filter(path => {
|
||||||
acc.push([])
|
if (!path.startsWith('region/')) return true
|
||||||
}
|
const [x, z] = path.split('/').at(-1)!.split('.').slice(1, 3).map(Number)
|
||||||
acc.at(-1)!.push(cur)
|
return Math.abs(x) > 50 || Math.abs(z) > 50 // HACK: otherwise it's too big and we can't handle it in visual display
|
||||||
return acc
|
})
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter
|
|
||||||
}, [] as string[][])
|
|
||||||
let copied = 0
|
let copied = 0
|
||||||
const upProgress = () => {
|
let isRegionFiles = false
|
||||||
|
const upProgress = (totalSize: number) => {
|
||||||
copied++
|
copied++
|
||||||
const action = fsState.remoteBackend ? 'Downloading & copying' : 'Copying'
|
let action = fsState.remoteBackend ? 'Downloading & copying' : 'Copying'
|
||||||
setLoadingScreenStatus(`${action} files (${copied}/${allFilesToCopy.length})`)
|
action += isRegionFiles ? ' region files (world chunks)' : ' basic save files'
|
||||||
|
setLoadingScreenStatus(`${action} files (${copied}/${totalSize})`)
|
||||||
}
|
}
|
||||||
for (const copyPaths of pathsSplit) {
|
const copyFiles = async (copyPaths: string[][]) => {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
const totalSIze = copyPaths.flat().length
|
||||||
await Promise.all(copyPaths.map(async (copyPath) => {
|
for (const copyFileGroup of copyPaths) {
|
||||||
const srcPath = join(worldFolder, copyPath)
|
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-loop-func
|
||||||
const savePath = join(saveRootPath, copyPath)
|
await Promise.all(copyFileGroup.map(async (copyPath) => {
|
||||||
await mkdirRecursive(savePath)
|
const srcPath = join(worldFolder, copyPath)
|
||||||
await fs.promises.writeFile(savePath, await fs.promises.readFile(srcPath))
|
const savePath = join(saveRootPath, copyPath)
|
||||||
upProgress()
|
await mkdirRecursive(savePath)
|
||||||
}))
|
await fs.promises.writeFile(savePath, await fs.promises.readFile(srcPath))
|
||||||
|
upProgress(totalSIze)
|
||||||
|
if (isRegionFiles) {
|
||||||
|
const regionFile = copyPath.split('/').at(-1)!
|
||||||
|
appStatusState.loadingChunksData![regionFile] = 'done'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await waitForPotentialRender()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// basic save files
|
||||||
|
await copyFiles(splitByCopySize(pathsSplitBasic))
|
||||||
|
setLoadingScreenStatus('Preparing world chunks copying')
|
||||||
|
await waitForPotentialRender()
|
||||||
|
|
||||||
|
// region files
|
||||||
|
isRegionFiles = true
|
||||||
|
copied = 0
|
||||||
|
const regionFiles = allFilesToCopy.filter(x => !pathsSplitBasic.includes(x))
|
||||||
|
const regionFilesNumbers = regionFiles.map(x => x.split('/').at(-1)!.split('.').slice(1, 3).map(Number))
|
||||||
|
const xMin = Math.min(...regionFilesNumbers.flatMap(x => x[0]))
|
||||||
|
const zMin = Math.min(...regionFilesNumbers.flatMap(x => x[1]))
|
||||||
|
const xMax = Math.max(...regionFilesNumbers.flatMap(x => x[0]))
|
||||||
|
const zMax = Math.max(...regionFilesNumbers.flatMap(x => x[1]))
|
||||||
|
const playerPosRegion = bot.entity.position.divide(new Vec3(32 * 16, 32 * 16, 32 * 16)).floored()
|
||||||
|
const maxDistantRegion = Math.max(
|
||||||
|
Math.abs(playerPosRegion.x - xMin),
|
||||||
|
Math.abs(playerPosRegion.z - zMin),
|
||||||
|
Math.abs(playerPosRegion.x - xMax),
|
||||||
|
Math.abs(playerPosRegion.z - zMax)
|
||||||
|
)
|
||||||
|
const spiral = generateSpiralMatrix(maxDistantRegion)
|
||||||
|
const filesWithSpiral = spiral.filter(x => allFilesToCopy.includes(`region/r.${x[0]}.${x[1]}.mca`)).map(x => `region/r.${x[0]}.${x[1]}.mca`)
|
||||||
|
if (filesWithSpiral.length !== regionFiles.length) throw new Error('Something went wrong with region files')
|
||||||
|
|
||||||
|
appStatusState.loadingChunksData = Object.fromEntries(regionFiles.map(x => [x.split('/').at(-1)!, 'loading']))
|
||||||
|
appStatusState.loadingChunksDataPlayerChunk = { x: playerPosRegion.x, z: playerPosRegion.z }
|
||||||
|
await copyFiles(splitByCopySize(filesWithSpiral, 10))
|
||||||
|
|
||||||
return saveRootPath
|
return saveRootPath
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
void showOptionsModal(`Error while saving the world: ${err.message}`, [])
|
void showOptionsModal(`Error while saving the world: ${err.message}`, [])
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingScreenStatus(undefined)
|
setLoadingScreenStatus(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const splitByCopySize = (files: string[], copySize = 15) => {
|
||||||
|
return files.reduce<string[][]>((acc, cur, i) => {
|
||||||
|
if (i % copySize === 0) {
|
||||||
|
acc.push([])
|
||||||
|
}
|
||||||
|
acc.at(-1)!.push(cur)
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const qsParams = new URLSearchParams(window.location.search)
|
const qsParams = new URLSearchParams(window.location.search)
|
||||||
const lockConnect = qsParams?.get('lockConnect') === 'true'
|
const lockConnect = qsParams?.get('lockConnect') === 'true'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { hideModal, isGameActive, miscUiState, showModal } from './globalState'
|
import { activeModalStack, hideModal, isGameActive, miscUiState, showModal } from './globalState'
|
||||||
import { options } from './optionsStorage'
|
import { options } from './optionsStorage'
|
||||||
import { appStatusState } from './react/AppStatusProvider'
|
import { appStatusState, resetAppStatusState } from './react/AppStatusProvider'
|
||||||
import { notificationProxy, showNotification } from './react/NotificationProvider'
|
import { notificationProxy, showNotification } from './react/NotificationProvider'
|
||||||
|
|
||||||
export const goFullscreen = async (doToggle = false) => {
|
export const goFullscreen = async (doToggle = false) => {
|
||||||
|
|
@ -139,7 +139,10 @@ export const setLoadingScreenStatus = function (status: string | undefined | nul
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo update in component instead
|
if (!activeModalStack.some(x => x.reactType === 'app-status')) {
|
||||||
|
// just showing app status
|
||||||
|
resetAppStatusState()
|
||||||
|
}
|
||||||
showModal({ reactType: 'app-status' })
|
showModal({ reactType: 'app-status' })
|
||||||
if (appStatusState.isError) {
|
if (appStatusState.isError) {
|
||||||
miscUiState.gameLoaded = false
|
miscUiState.gameLoaded = false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue