Compare commits

...
Sign in to create a new pull request.

77 commits

Author SHA1 Message Date
Vitaly Turovsky
64fa0a9b8a fix index 2025-03-26 00:09:30 +03:00
Vitaly Turovsky
849a6d3c60 Merge branch 'nextgen-physics' into worker-rewrite 2025-03-26 00:09:18 +03:00
Vitaly Turovsky
9057d3acb5 revert stuff 2025-03-26 00:05:55 +03:00
Vitaly Turovsky
547658f489 Merge remote-tracking branch 'origin/next' into nextgen-physics 2025-03-26 00:03:28 +03:00
Vitaly Turovsky
bf9c47dd26 fix test 2025-03-26 00:01:28 +03:00
Vitaly Turovsky
9cede6dbbc final fixes 2025-03-26 00:01:28 +03:00
Vitaly Turovsky
6a5ac4f8d2 make initial resource pack load optimised, rework it 2025-03-26 00:01:28 +03:00
Vitaly Turovsky
2630a57d35 renderers cleanup 2025-03-26 00:01:28 +03:00
Vitaly Turovsky
20569747ca fix mesher config pass 2025-03-26 00:01:27 +03:00
Vitaly Turovsky
9888bd55c1 fix: allow to go back when not crashed game 2025-03-26 00:01:27 +03:00
Vitaly
b501893ab2 ip pkgs 2025-03-23 03:13:52 +00:00
Vitaly Turovsky
e2b78333a1 Merge remote-tracking branch 'origin/renderer-rewrite' into nextgen-physics 2025-03-21 16:33:42 +03:00
Vitaly Turovsky
e917764b76 fix lint 2025-03-21 13:58:53 +03:00
Vitaly Turovsky
ed041972c4 disable in other way 2025-03-21 13:50:11 +03:00
Vitaly Turovsky
b579ee1767 Revert "disable playground"
This reverts commit 5eedb3c456.
2025-03-21 13:48:39 +03:00
Vitaly Turovsky
d450a31547 fix typo 2025-03-21 11:27:12 +03:00
Vitaly Turovsky
853e0e1d84 up readme 2025-03-21 11:05:19 +03:00
Vitaly Turovsky
8ddac97414 dont use bot 2025-03-21 11:02:30 +03:00
Vitaly Turovsky
5eedb3c456 disable playground 2025-03-21 10:59:46 +03:00
Vitaly Turovsky
11abbfcbb1 disable check 2025-03-21 07:07:56 +03:00
Vitaly Turovsky
8ee4dc37e7 disable displaying unknown non interactible entitites 2025-03-21 07:07:26 +03:00
Vitaly Turovsky
dc2ad7ccce disable signs, disable playground since its not used by anyone 2025-03-21 06:36:15 +03:00
Vitaly Turovsky
b483923009 rm unused three imports 2025-03-21 06:22:30 +03:00
Vitaly Turovsky
82d0638eb8 Merge remote-tracking branch 'origin/next' into renderer-rewrite 2025-03-21 06:20:58 +03:00
Vitaly Turovsky
cae2b612ba fix all remaining reactive state linking 2025-03-21 06:19:36 +03:00
Vitaly Turovsky
f88e9c8b61 Refactor renderer state management and move vr 2025-03-21 06:11:31 +03:00
Vitaly Turovsky
136b051695 smooth camera movement! 2025-03-21 05:16:30 +03:00
Vitaly Turovsky
9fedafe776 move threejs entities & cursor block to renderer 2025-03-21 05:07:27 +03:00
Vitaly Turovsky
ccb00043cf fix reload hand 2025-03-20 22:38:23 +03:00
Vitaly Turovsky
c1a7765fcb final code cleanup i think 2025-03-20 22:10:14 +03:00
Vitaly Turovsky
de3eddad89 a lot of imports update, data cleanup, add thousands errors 2025-03-20 05:36:22 +03:00
Vitaly Turovsky
e851f4fac2 Merge remote-tracking branch 'origin/next' into nextgen-physics 2025-03-20 05:15:31 +03:00
Vitaly Turovsky
f2f1c2538e 10x inventory performance 2025-03-20 04:53:52 +03:00
Vitaly Turovsky
766d7950f7 Merge remote-tracking branch 'origin/next' into worker-rewrite 2025-03-20 04:37:55 +03:00
Vitaly Turovsky
5364085030 fix possible crash on non existing server data update 2025-03-20 04:37:02 +03:00
Vitaly Turovsky
400f5982be update server data for index 0 2025-03-20 04:34:55 +03:00
Vitaly Turovsky
dc073cd559 Merge remote-tracking branch 'origin/next' into renderer-rewrite 2025-03-19 01:26:02 +03:00
Vitaly Turovsky
6eb50cde24 last dont crash 2025-03-19 01:25:03 +03:00
Vitaly Turovsky
67d90a56fb Merge remote-tracking branch 'origin/next' into renderer-rewrite 2025-03-19 01:24:34 +03:00
Vitaly Turovsky
f2307632a2 Merge remote-tracking branch 'origin/next' into nextgen-physics 2025-03-18 20:51:34 +03:00
Vitaly Turovsky
ddd58399da precise control packet 2025-03-17 19:56:29 +03:00
Vitaly Turovsky
d74d860726 Merge remote-tracking branch 'origin/next' into renderer-rewrite 2025-03-15 05:36:54 +03:00
Vitaly Turovsky
4d4637f710 Merge remote-tracking branch 'origin/next' into nextgen-physics 2025-03-15 04:33:12 +03:00
Vitaly Turovsky
c4c76d46aa add timings 2025-03-15 02:28:26 +03:00
Vitaly Turovsky
333db054ea Merge remote-tracking branch 'origin/next' into worker-rewrite 2025-03-15 02:26:48 +03:00
Vitaly Turovsky
847314d50f hide hand in spectator 2025-03-15 02:18:51 +03:00
Vitaly Turovsky
8a3c84745d test flying! 2025-03-15 02:18:44 +03:00
Vitaly Turovsky
3a9e2aa384 fix 2025-03-15 00:46:21 +03:00
Vitaly Turovsky
7cc562bd02 Merge remote-tracking branch 'origin/next' into nextgen-physics 2025-03-15 00:45:55 +03:00
Vitaly Turovsky
f1a5b7cfa6 fix 2025-03-12 18:58:30 +03:00
Vitaly Turovsky
cecc6fbd81 restore 2025-03-12 18:26:30 +03:00
Vitaly Turovsky
c228b91d2d Merge remote-tracking branch 'origin/next' into worker-rewrite 2025-03-12 18:26:20 +03:00
Vitaly Turovsky
75129e48d1 better logging 2025-03-10 22:21:31 +03:00
Vitaly Turovsky
bba548e118 almost done
WIP: refactor support for all events by skipoping only packet ones to support chat ones
TEST hypixel
2025-03-10 16:01:37 +03:00
Vitaly Turovsky
37b871a8da it works! now migrating auth 2025-03-10 00:25:45 +03:00
Vitaly Turovsky
ad862e557b something works 2025-03-09 22:33:14 +03:00
Vitaly Turovsky
0597a3dad2 make library versions less annoying 2025-03-09 04:57:48 +03:00
Vitaly
cefdf5362f Merge branch 'next' into nextgen-physics 2025-03-07 15:59:22 +00:00
Vitaly Turovsky
d197859d47 big resourcemanager refactor 2025-03-05 15:30:04 +03:00
Vitaly Turovsky
1861edf567 delayLoadUntilFocus 2025-03-04 14:17:44 +03:00
Vitaly Turovsky
d8294d565b even more appViewer usage 2025-03-04 13:53:28 +03:00
Vitaly Turovsky
4381ef4f75 removing bobbing & panorama 2025-03-04 12:57:04 +03:00
Vitaly Turovsky
c65db9a8cb a working refactor 2025-03-04 12:12:42 +03:00
Vitaly Turovsky
34972e4e71 Merge branch 'next' into nextgen-physics 2025-02-27 05:16:21 +03:00
Vitaly
638dd6711e
Merge branch 'next' into nextgen-physics 2025-02-15 05:31:40 +03:00
Vitaly
9356daaefc
Merge branch 'next' into nextgen-physics 2025-02-13 23:27:44 +03:00
Vitaly Turovsky
fb10179691 set 1.19.4 for now! 2025-02-03 10:26:24 +03:00
Vitaly Turovsky
7e74633c14 Merge branch 'next' into nextgen-physics 2025-02-03 10:25:47 +03:00
Vitaly Turovsky
2d7ec12a75 Merge branch 'next' into nextgen-physics 2025-02-03 10:25:08 +03:00
Vitaly Turovsky
c626d105ff use actually useful server 2025-01-31 18:44:26 +03:00
Vitaly Turovsky
8db6b5bb51 fix library versions 2025-01-31 06:00:50 +03:00
Vitaly Turovsky
2848ab63d3 disable silly creative fly patching 2025-01-31 05:30:59 +03:00
Vitaly Turovsky
537658476d use gen mineflayer 2025-01-30 21:38:35 +03:00
Vitaly Turovsky
044153c2dc up ver 2025-01-30 21:36:23 +03:00
Vitaly Turovsky
380c21486b make it finally pass grim checks! 2025-01-30 21:33:30 +03:00
Vitaly Turovsky
aed5b40516 use local 2025-01-29 03:31:16 +03:00
Vitaly Turovsky
3051cc35f5 up pkg 2025-01-29 03:30:25 +03:00
21 changed files with 5253 additions and 5408 deletions

View file

@ -158,6 +158,7 @@
"unicorn/prefer-ternary": "off",
"unicorn/switch-case-braces": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"unicorn/relative-url-style": "off",
"unicorn/explicit-length-check": "off",
"unicorn/prefer-dom-node-append": "off",
"typescript-eslint/no-confusing-void-expression": "off",

View file

@ -10,16 +10,15 @@
"ip": "wss://mcraft.ryzyn.xyz",
"version": "1.19.4"
},
{
"ip": "grim.mcraft.fun",
"version": "1.19.4"
},
{
"ip": "play.hypixel.net"
},
{
"ip": "wss://play.mcraft.fun"
},
{
"ip": "wss://play2.mcraft.fun"
},
{
"ip": "kaboom.pw",
"version": "1.20.3",
"description": "Very nice a polite server. Must try for everyone!"
}
],
"pauseLinks": [

View file

@ -118,11 +118,11 @@
"workbox-build": "^7.0.0"
},
"devDependencies": {
"@rsbuild/core": "^1.0.1-beta.9",
"@rsbuild/plugin-node-polyfill": "^1.0.3",
"@rsbuild/plugin-react": "^1.0.1-beta.9",
"@rsbuild/plugin-type-check": "^1.0.1-beta.9",
"@rsbuild/plugin-typed-css-modules": "^1.0.1",
"@rsbuild/core": "1.0.1-beta.9",
"@rsbuild/plugin-node-polyfill": "1.0.3",
"@rsbuild/plugin-react": "1.0.1-beta.9",
"@rsbuild/plugin-type-check": "1.0.1-beta.9",
"@rsbuild/plugin-typed-css-modules": "1.0.1",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
@ -153,7 +153,7 @@
"mc-assets": "^0.2.45",
"mineflayer-mouse": "^0.1.7",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",
"mineflayer": "github:GenerelSchwerz/mineflayer",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
@ -194,9 +194,9 @@
},
"pnpm": {
"overrides": {
"@nxg-org/mineflayer-physics-util": "latest",
"buffer": "^6.0.3",
"vec3": "0.1.10",
"@nxg-org/mineflayer-physics-util": "1.5.8",
"three": "0.154.0",
"diamond-square": "github:zardoy/diamond-square",
"prismarine-block": "github:zardoy/prismarine-block#next-era",

10024
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,6 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// eslint-disable-next-line import/no-named-as-default
import GUI from 'lil-gui'
import _ from 'lodash'
import { toMajorVersion } from '../../src/utils'
import { WorldDataEmitter } from '../viewer'
import { Viewer } from '../viewer/lib/viewer'
import { BlockNames } from '../../src/mcDataTypes'

View file

@ -33,3 +33,9 @@ export function sectionPos (pos: { x: number, y: number, z: number }) {
const z = Math.floor(pos.z / 16)
return [x, y, z]
}
// doesn't support snapshots
export const toMajorVersion = version => {
const [a, b] = (String(version)).split('.')
return `${a}.${b}`
}

View file

@ -8,13 +8,12 @@ import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer'
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
import { toMajorVersion } from '../../../src/utils'
import { ResourcesManager } from '../../../src/resourcesManager'
import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer'
import { SoundSystem } from '../three/threeJsSound'
import { buildCleanupDecorator } from './cleanupDecorator'
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
import { chunkPos } from './simpleUtils'
import { chunkPos, toMajorVersion } from './simpleUtils'
import { removeStat, updateStatText } from './ui/newStats'
import { WorldDataEmitter } from './worldDataEmitter'
import { IPlayerState } from './basePlayerState'

View file

@ -712,142 +712,32 @@ document.addEventListener('visibilitychange', (e) => {
}
})
// #region creative fly
// these controls are more like for gamemode 3
const makeInterval = (fn, interval) => {
const intervalId = setInterval(fn, interval)
const cleanup = () => {
clearInterval(intervalId)
cleanup.active = false
}
cleanup.active = true
return cleanup
}
const isFlying = () => bot.physics.gravity === 0
let endFlyLoop: ReturnType<typeof makeInterval> | undefined
const currentFlyVector = new Vec3(0, 0, 0)
window.currentFlyVector = currentFlyVector
// todo cleanup
const flyingPressedKeys = {
down: false,
up: false
}
const startFlyLoop = () => {
if (!isFlying()) return
endFlyLoop?.()
endFlyLoop = makeInterval(() => {
if (!bot) {
endFlyLoop?.()
return
}
bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0)))
}, 50)
}
// todo we will get rid of patching it when refactor controls
let originalSetControlState
const patchedSetControlState = (action, state) => {
if (!isFlying()) {
return originalSetControlState(action, state)
}
const actionPerFlyVector = {
jump: new Vec3(0, 1, 0),
sneak: new Vec3(0, -1, 0),
}
const changeVec = actionPerFlyVector[action]
if (!changeVec) {
return originalSetControlState(action, state)
}
if (flyingPressedKeys[state === 'jump' ? 'up' : 'down'] === state) return
const toAddVec = changeVec.scaled(state ? 1 : -1)
for (const coord of ['x', 'y', 'z']) {
if (toAddVec[coord] === 0) continue
if (currentFlyVector[coord] === toAddVec[coord]) return
}
currentFlyVector.add(toAddVec)
flyingPressedKeys[state === 'jump' ? 'up' : 'down'] = state
}
const isFlying = () => (bot.entity as any).flying
const startFlying = (sendAbilities = true) => {
bot.entity['creativeFly'] = true
if (sendAbilities) {
bot._client.write('abilities', {
flags: 2,
})
}
// window.flyingSpeed will be removed
bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities
bot.entity.velocity = new Vec3(0, 0, 0)
bot.creative.startFlying()
startFlyLoop()
(bot.entity as any).flying = true
}
const endFlying = (sendAbilities = true) => {
bot.entity['creativeFly'] = false
if (bot.physics.gravity !== 0) return
if (!isFlying()) return
if (sendAbilities) {
bot._client.write('abilities', {
flags: 0,
})
}
Object.assign(flyingPressedKeys, {
up: false,
down: false
})
currentFlyVector.set(0, 0, 0)
bot.physics['airborneAcceleration'] = standardAirborneAcceleration
bot.creative.stopFlying()
endFlyLoop?.()
(bot.entity as any).flying = false
}
let allowFlying = false
export const onBotCreate = () => {
let wasSpectatorFlying = false
bot._client.on('abilities', ({ flags }) => {
allowFlying = !!(flags & 4)
if (flags & 2) { // flying
toggleFly(true, false)
} else {
toggleFly(false, false)
}
})
const gamemodeCheck = () => {
if (bot.game.gameMode === 'spectator') {
allowFlying = true
toggleFly(true, false)
wasSpectatorFlying = true
} else if (wasSpectatorFlying) {
toggleFly(false, false)
wasSpectatorFlying = false
}
}
bot.on('game', () => {
gamemodeCheck()
})
bot.on('login', () => {
gamemodeCheck()
})
}
const standardAirborneAcceleration = 0.02
const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
// if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return
if (!allowFlying) return
if (bot.setControlState !== patchedSetControlState) {
originalSetControlState = bot.setControlState
bot.setControlState = patchedSetControlState
}
if (!bot.entity.canFly) return
if (newState) {
startFlying(sendAbilities)
@ -856,7 +746,6 @@ const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
}
gameAdditionalState.isFlying = isFlying()
}
// #endregion
const selectItem = async () => {
const block = bot.blockAtCursor(5)

View file

@ -17,8 +17,6 @@ import './mineflayer/timers'
import { getServerInfo } from './mineflayer/mc-protocol'
import { onGameLoad } from './inventoryWindows'
import initCollisionShapes from './getCollisionInteractionShapes'
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
import microsoftAuthflow from './microsoftAuthflow'
import { Duplex } from 'stream'
import './scaleInterface'
@ -75,7 +73,7 @@ import { saveToBrowserMemory } from './react/PauseScreen'
import './devReload'
import './water'
import { ConnectOptions, loadMinecraftData, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect'
import { ref, subscribe } from 'valtio'
import { subscribe } from 'valtio'
import { signInMessageState } from './react/SignInMessageProvider'
import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
import packetsPatcher from './mineflayer/plugins/packetsPatcher'
@ -97,6 +95,7 @@ import { createConsoleLogProgressReporter, createFullScreenProgressReporter, Pro
import { appViewer } from './appViewer'
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
import { subscribeKey } from 'valtio/utils'
import { getProtocolClientGetter } from './protocolWorker/protocolMain'
window.debug = debug
window.beforeRenderFrame = []
@ -190,8 +189,8 @@ export async function connect (connectOptions: ConnectOptions) {
const { renderDistance: renderDistanceSingleplayer, multiplayerRenderDistance } = options
const parsedServer = parseServerAddress(connectOptions.server)
const server = { host: parsedServer.host, port: parsedServer.port }
const serverParsed = parseServerAddress(connectOptions.server)
const server = { host: serverParsed.host, port: serverParsed.port }
if (connectOptions.proxy?.startsWith(':')) {
connectOptions.proxy = `${location.protocol}//${location.hostname}${connectOptions.proxy}`
}
@ -199,12 +198,12 @@ export async function connect (connectOptions: ConnectOptions) {
const https = connectOptions.proxy.startsWith('https://') || location.protocol === 'https:'
connectOptions.proxy = `${connectOptions.proxy}:${https ? 443 : 80}`
}
const parsedProxy = parseServerAddress(connectOptions.proxy, false)
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
const proxyParsed = parseServerAddress(connectOptions.proxy, false)
const proxy = { host: proxyParsed.host, port: proxyParsed.port }
let { username } = connectOptions
if (connectOptions.server) {
console.log(`connecting to ${server.host}:${server.port ?? 25_565}`)
console.log(`connecting to ${serverParsed.serverIpFull}`)
}
console.log('using player username', username)
@ -244,7 +243,7 @@ export async function connect (connectOptions: ConnectOptions) {
})
}
}
let lastPacket = undefined as string | undefined
const lastPacket = undefined as string | undefined
const onPossibleErrorDisconnect = () => {
if (lastPacket && bot?._client && bot._client.state !== states.PLAY) {
appStatusState.descriptionHint = `Last Server Packet: ${lastPacket}`
@ -285,13 +284,12 @@ export async function connect (connectOptions: ConnectOptions) {
let clientDataStream: Duplex | undefined
if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) {
if (connectOptions.server && !connectOptions.viewerWsConnect && !serverParsed.isWebSocket) {
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
net['setProxy']({ hostname: proxy.host, port: proxy.port })
}
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
let updateDataAfterJoin = () => { }
let localServer
let localReplaySession: ReturnType<typeof startLocalReplayServer> | undefined
try {
@ -411,23 +409,11 @@ export async function connect (connectOptions: ConnectOptions) {
}
setLoadingScreenStatus(initialLoadingText)
if (parsedServer.isWebSocket) {
if (serverParsed.isWebSocket) {
clientDataStream = (await getWebsocketStream(server.host)).mineflayerStream
}
let newTokensCacheResult = null as any
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
const authData = connectOptions.authenticatedAccount ? await microsoftAuthflow({
tokenCaches: cachedTokens,
proxyBaseUrl: connectOptions.proxy,
setProgressText (text) {
setLoadingScreenStatus(text)
},
setCacheResult (result) {
newTokensCacheResult = result
},
connectingServer: server.host
}) : undefined
if (p2pMultiplayer) {
clientDataStream = await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions)
@ -458,9 +444,13 @@ export async function connect (connectOptions: ConnectOptions) {
await downloadMcData(finalVersion)
}
const brand = clientDataStream ? 'minecraft-web-client' : undefined
const createClient = await getProtocolClientGetter(proxy, connectOptions, serverParsed.serverIpFull)
bot = mineflayer.createBot({
host: server.host,
port: server.port ? +server.port : undefined,
brand,
version: finalVersion || false,
...clientDataStream ? {
stream: clientDataStream as any,
@ -477,53 +467,13 @@ export async function connect (connectOptions: ConnectOptions) {
connect () { },
Client: CustomChannelClient as any,
} : {},
onMsaCode (data) {
signInMessageState.code = data.user_code
signInMessageState.link = data.verification_uri
signInMessageState.expiresOn = Date.now() + data.expires_in * 1000
},
sessionServer: authData?.sessionEndpoint?.toString(),
auth: connectOptions.authenticatedAccount ? async (client, options) => {
authData!.setOnMsaCodeCallback(options.onMsaCode)
authData?.setConnectingVersion(client.version)
//@ts-expect-error
client.authflow = authData!.authFlow
try {
signInMessageState.abortController = ref(new AbortController())
await Promise.race([
protocolMicrosoftAuth.authenticate(client, options),
new Promise((_r, reject) => {
signInMessageState.abortController.signal.addEventListener('abort', () => {
reject(new UserError('Aborted by user'))
})
})
])
if (signInMessageState.shouldSaveToken) {
updateAuthenticatedAccountData(accounts => {
const existingAccount = accounts.find(a => a.username === client.username)
if (existingAccount) {
existingAccount.cachedTokens = { ...existingAccount.cachedTokens, ...newTokensCacheResult }
} else {
accounts.push({
username: client.username,
cachedTokens: { ...cachedTokens, ...newTokensCacheResult }
})
}
return accounts
})
updateDataAfterJoin = () => {
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: client.username }), connectOptions.serverIndex)
}
} else {
updateDataAfterJoin = () => {
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: undefined }), connectOptions.serverIndex)
}
}
setLoadingScreenStatus('Authentication successful. Logging in to server')
} finally {
signInMessageState.code = ''
get client () {
if (clientDataStream || singleplayer || p2pMultiplayer || localReplaySession || connectOptions.viewerWsConnect) {
return undefined
}
} : undefined,
return createClient.call(this)
},
// auth: connectOptions.authenticatedAccount ? : undefined,
username,
viewDistance: renderDistance,
checkTimeoutInterval: 240 * 1000,
@ -579,16 +529,16 @@ export async function connect (connectOptions: ConnectOptions) {
})
}
// socket setup actually can be delayed because of dns lookup
if (bot._client.socket) {
setupConnectHandlers()
} else {
const originalSetSocket = bot._client.setSocket.bind(bot._client)
bot._client.setSocket = (socket) => {
if (!bot) return
originalSetSocket(socket)
setupConnectHandlers()
}
}
// if (bot._client.socket) {
// setupConnectHandlers()
// } else {
// const originalSetSocket = bot._client.setSocket.bind(bot._client)
// bot._client.setSocket = (socket) => {
// if (!bot) return
// originalSetSocket(socket)
// setupConnectHandlers()
// }
// }
}
} catch (err) {
@ -623,7 +573,7 @@ export async function connect (connectOptions: ConnectOptions) {
// bot.emit('kicked', '{"translate":"disconnect.genericReason","with":["Internal Exception: io.netty.handler.codec.EncoderException: com.viaversion.viaversion.exception.InformativeException: Please report this on the Via support Discord or open an issue on the relevant GitHub repository\\nPacket Type: SYSTEM_CHAT, Index: 1, Type: TagType, Data: [], Packet ID: 103, Source 0: com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2$$Lambda/0x00007f9930f63080"]}', false)
const packetBeforePlay = (_, __, ___, fullBuffer) => {
lastPacket = fullBuffer.toString()
// lastPacket = fullBuffer.toString()
}
bot._client.on('packet', packetBeforePlay as any)
const playStateSwitch = (newState) => {
@ -675,6 +625,10 @@ export async function connect (connectOptions: ConnectOptions) {
document.dispatchEvent(new Event('cypress-world-ready'))
})
if (!connectOptions.worldStateFileContents || connectOptions.worldStateFileContents.length < 3 * 1024 * 1024) {
localStorage.lastConnectOptions = JSON.stringify(connectOptions)
}
const spawnEarlier = !singleplayer && !p2pMultiplayer
// don't use spawn event, player can be dead
bot.once(spawnEarlier ? 'forcedMove' : 'health', async () => {
@ -703,7 +657,6 @@ export async function connect (connectOptions: ConnectOptions) {
localStorage.removeItem('lastConnectOptions')
}
connectOptions.onSuccessfulPlay?.()
updateDataAfterJoin()
if (connectOptions.autoLoginPassword) {
bot.chat(`/login ${connectOptions.autoLoginPassword}`)
}

View file

@ -1,5 +1,9 @@
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
customEvents.on('hurtAnimation', () => {
cameraShake.shakeFromDamage()
})
customEvents.on('mineflayerBotCreated', () => {
customEvents.on('hurtAnimation', (yaw) => {
getThreeJsRendererMethods()?.shakeFromDamage()

View file

@ -7,16 +7,10 @@ import { getWebsocketStream } from './websocket-core'
let lastPacketTime = 0
customEvents.on('mineflayerBotCreated', () => {
// todo move more code here
if (!appQueryParams.noPacketsValidation) {
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
validatePacket(packetMeta.name, data, fullBuffer, true)
lastPacketTime = performance.now()
});
(bot._client as unknown as Client).on('writePacket', (name, params) => {
validatePacket(name, params, Buffer.alloc(0), false)
})
}
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
lastPacketTime = performance.now()
})
})
setInterval(() => {

View file

@ -1,3 +1,10 @@
import { ref } from 'valtio'
import { signInMessageState } from '../react/SignInMessageProvider'
import { updateAuthenticatedAccountData, updateLoadedServerData } from '../react/serversStorage'
import { setLoadingScreenStatus } from '../appStatus'
import { ConnectOptions } from '../connect'
import { showNotification } from '../react/NotificationProvider'
export const getProxyDetails = async (proxyBaseUrl: string) => {
if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}`
const url = `${proxyBaseUrl}/api/vm/net/connect`
@ -10,13 +17,14 @@ export const getProxyDetails = async (proxyBaseUrl: string) => {
return result
}
export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, setCacheResult, connectingServer }) => {
export const getAuthData = async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, connectingServer }) => {
let onMsaCodeCallback
let connectingVersion = ''
// const authEndpoint = 'http://localhost:3000/'
// const sessionEndpoint = 'http://localhost:3000/session'
let authEndpoint: URL | undefined
let sessionEndpoint: URL | undefined
let newTokensCacheResult = null as any
const result = await getProxyDetails(proxyBaseUrl)
try {
@ -32,7 +40,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
async getMinecraftJavaToken () {
setProgressText('Authenticating with Microsoft account')
if (!window.crypto && !isPageSecure()) throw new Error('Crypto API is available only in secure contexts. Be sure to use https!')
let result = null
let result = null as any
await fetch(authEndpoint, {
method: 'POST',
headers: {
@ -73,7 +81,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
}
if (json.error) throw new Error(json.error)
if (json.token) result = json
if (json.newCache) setCacheResult(json.newCache)
if (json.newCache) newTokensCacheResult = json.newCache
}
const strings = decoder.decode(value)
@ -86,11 +94,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
}
return reader.read().then(processText)
})
const restoredData = await restoreData(result)
if (restoredData?.certificates?.profileKeys?.privatePEM) {
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
}
return restoredData
return result
}
}
return {
@ -101,77 +105,68 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
},
setConnectingVersion (version) {
connectingVersion = version
},
get newTokensCacheResult () {
return newTokensCacheResult
}
}
}
export const authFlowMainThread = async (worker: Worker, authData: Awaited<ReturnType<typeof getAuthData>>, connectOptions: ConnectOptions, setActionAfterJoin: (action: () => void) => void) => {
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
signInMessageState.abortController = ref(new AbortController())
await new Promise<void>(resolve => {
worker.addEventListener('message', ({ data }) => {
if (data.type === 'authFlow') {
authData.setConnectingVersion(data.version)
resolve()
}
})
})
authData.setOnMsaCodeCallback((codeData) => {
signInMessageState.code = codeData.user_code
signInMessageState.link = codeData.verification_uri
signInMessageState.expiresOn = Date.now() + codeData.expires_in * 1000
})
const data = await authData.authFlow.getMinecraftJavaToken()
signInMessageState.code = ''
if (!data) return
const username = data.profile.name
if (signInMessageState.shouldSaveToken) {
updateAuthenticatedAccountData(accounts => {
const existingAccount = accounts.find(a => a.username === username)
if (existingAccount) {
existingAccount.cachedTokens = { ...existingAccount.cachedTokens, ...authData.newTokensCacheResult }
} else {
accounts.push({
username,
cachedTokens: { ...cachedTokens, ...authData.newTokensCacheResult }
})
}
showNotification(`Account ${username} saved`)
return accounts
})
setActionAfterJoin(() => {
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: username }), connectOptions.serverIndex)
})
} else {
setActionAfterJoin(() => {
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: undefined }), connectOptions.serverIndex)
})
}
worker.postMessage({
type: 'authflowResult',
data
})
setLoadingScreenStatus('Authentication successful. Logging in to server')
}
function isPageSecure (url = window.location.href) {
return !url.startsWith('http:')
}
// restore dates from strings
const restoreData = async (json) => {
const promises = [] as Array<Promise<void>>
if (typeof json === 'object' && json) {
for (const [key, value] of Object.entries(json)) {
if (typeof value === 'string') {
promises.push(tryRestorePublicKey(value, key, json))
if (value.endsWith('Z')) {
const date = new Date(value)
if (!isNaN(date.getTime())) {
json[key] = date
}
}
}
if (typeof value === 'object') {
// eslint-disable-next-line no-await-in-loop
await restoreData(value)
}
}
}
await Promise.all(promises)
return json
}
const tryRestorePublicKey = async (value: string, name: string, parent: { [x: string]: any }) => {
value = value.trim()
if (!name.endsWith('PEM') || !value.startsWith('-----BEGIN RSA PUBLIC KEY-----') || !value.endsWith('-----END RSA PUBLIC KEY-----')) return
const der = pemToArrayBuffer(value)
const key = await window.crypto.subtle.importKey(
'spki', // Specify that the data is in SPKI format
der,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-256' }
},
true,
['encrypt'] // Specify key usages
)
const originalName = name.replace('PEM', '')
const exported = await window.crypto.subtle.exportKey('spki', key)
const exportedBuffer = new Uint8Array(exported)
parent[originalName] = {
export () {
return exportedBuffer
}
}
}
function pemToArrayBuffer (pem) {
// Fetch the part of the PEM string between header and footer
const pemHeader = '-----BEGIN RSA PUBLIC KEY-----'
const pemFooter = '-----END RSA PUBLIC KEY-----'
const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim()
const binaryDerString = atob(pemContents.replaceAll(/\s/g, ''))
const binaryDer = new Uint8Array(binaryDerString.length)
for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.codePointAt(i)!
}
return binaryDer.buffer
}
const urlWithBase = (url: string, base: string) => {
const defaultBase = isPageSecure() ? 'https' : 'http'
if (!base.startsWith('http')) base = `${defaultBase}://${base}`

View file

@ -0,0 +1,186 @@
import EventEmitter from 'events'
import { ClientOptions } from 'minecraft-protocol'
import { appQueryParams } from '../appParams'
import { ConnectOptions } from '../connect'
import { setLoadingScreenStatus } from '../appStatus'
import { authFlowMainThread, getAuthData } from './microsoftAuthflow'
const debug = require('debug')('minecraft-protocol')
const copyPrimitiveValues = (obj: any, deep = false, ignoreKeys: string[] = []) => {
const copy = {} as Record<string, any>
for (const key in obj) {
if (ignoreKeys.includes(key)) continue
if (typeof obj[key] === 'object' && obj[key] !== null && deep) {
copy[key] = copyPrimitiveValues(obj[key])
} else if (typeof obj[key] === 'number' || typeof obj[key] === 'string' || typeof obj[key] === 'boolean') {
copy[key] = obj[key]
}
}
return copy
}
export const getProtocolClientGetter = async (proxy: { host: string, port?: string }, connectOptions: ConnectOptions, serverIp: string) => {
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
const authData = connectOptions.authenticatedAccount ?
await getAuthData({
tokenCaches: cachedTokens,
proxyBaseUrl: connectOptions.proxy,
setProgressText (text) {
setLoadingScreenStatus(text)
},
connectingServer: serverIp.replace(/:25565$/, '')
})
: undefined
function createMinecraftProtocolClient (this: any) {
if (!this.brand) return // brand is not resolved yet
if (bot?._client) return bot._client
const createClientOptions = copyPrimitiveValues(this, false, ['client']) as ClientOptions
createClientOptions.sessionServer = authData?.sessionEndpoint.toString()
const worker = new Worker(new URL('./protocolWorker.ts', import.meta.url))
setTimeout(() => {
if (bot) {
bot.on('end', () => {
worker.terminate()
})
} else {
worker.terminate()
}
})
worker.postMessage({
type: 'setProxy',
hostname: proxy.host,
port: proxy.port
})
worker.postMessage({
type: 'init',
options: createClientOptions,
noPacketsValidation: appQueryParams.noPacketsValidation,
useAuthFlow: !!authData
})
const eventEmitter = new EventEmitter() as any
eventEmitter.version = this.version
worker.addEventListener('message', ({ data }) => {
if (data.type === 'event') {
eventEmitter.emit(data.event, ...data.args)
if (data.event === 'packet') {
let [packetData, packetMeta] = data.args
if (window.stopPacketsProcessing === true || (Array.isArray(window.stopPacketsProcessing) && window.stopPacketsProcessing.includes(packetMeta.name))) {
if (window.skipPackets && !window.skipPackets.includes(packetMeta.name)) {
return
}
}
// Start timing the packet processing
const startTime = performance.now()
// restore transferred data
if (packetData instanceof Uint8Array) {
packetData = Buffer.from(packetData)
} else if (typeof packetData === 'object' && packetData !== null) {
// Deep patch any Uint8Array values in the packet data object
const patchUint8Arrays = (obj: any) => {
for (const key in obj) {
if (obj[key] instanceof Uint8Array) {
obj[key] = Buffer.from(obj[key])
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
patchUint8Arrays(obj[key])
}
}
}
patchUint8Arrays(packetData)
}
eventEmitter.state = packetMeta.state
debug(`RECV ${eventEmitter.state}:${packetMeta.name}`, packetData)
// Initialize packet timing tracking if not exists
if (!window.packetTimings) {
window.packetTimings = {}
}
if (!window.packetTimings[packetMeta.name]) {
window.packetTimings[packetMeta.name] = {
total: 0,
count: 0,
avg: 0
}
}
eventEmitter.emit(packetMeta.name, packetData, packetMeta)
// Calculate processing time
const processingTime = performance.now() - startTime
window.packetTimings[packetMeta.name].total += processingTime
window.packetTimings[packetMeta.name].count++
window.packetTimings[packetMeta.name].avg =
window.packetTimings[packetMeta.name].total / window.packetTimings[packetMeta.name].count
// Update packetsThreadBlocking every second
if (!window.lastStatsUpdate) {
window.lastStatsUpdate = Date.now()
setInterval(() => {
// Sort by total processing time
window.packetsThreadBlocking = Object.entries(window.packetTimings)
.sort(([, a], [, b]) => b.total - a.total)
.reduce((acc, [key, value]) => {
acc[key] = value
return acc
}, {})
// Reset timings for next interval
window.packetTimings = {}
window.lastStatsUpdate = Date.now()
}, 1000)
}
}
}
})
eventEmitter.on('writePacket', (...args: any[]) => {
debug(`SEND ${eventEmitter.state}:${args[0]}`, ...args.slice(1))
})
const redirectMethodsToWorker = (names: string[]) => {
for (const name of names) {
eventEmitter[name] = async (...args: any[]) => {
worker.postMessage({
type: 'call',
name,
args: JSON.parse(JSON.stringify(args))
})
if (name === 'write') {
eventEmitter.emit('writePacket', ...args)
}
}
}
}
redirectMethodsToWorker(['write', 'registerChannel', 'writeChannel'])
if (authData) {
void authFlowMainThread(worker, authData, connectOptions, (onJoin) => {
eventEmitter.on('login', onJoin)
})
}
return eventEmitter
// return new Proxy(eventEmitter, {
// get (target, prop) {
// if (!(prop in target)) {
// // console.warn(`Accessing non-existent property "${String(prop)}" on event emitter`)
// }
// const value = target[prop]
// return typeof value === 'function' ? value.bind(target) : value
// }
// })
}
return createMinecraftProtocolClient
}

View file

@ -0,0 +1,177 @@
/* eslint-disable no-restricted-globals */
import './protocolWorkerGlobals'
import * as net from 'net'
import { Client, createClient } from 'minecraft-protocol'
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
import { validatePacket } from '../mineflayer/minecraft-protocol-extra'
// This is a Web Worker for handling protocol-related tasks
// Respond to messages from the main thread
self.onmessage = (e) => {
const handler = handlers[e.data.type]
if (handler) {
handler(e.data)
}
}
const REDIRECT_EVENTS = ['connection', 'listening', 'playerJoin', 'end']
const ENABLE_TRANSFER = false
const emitEvent = (event: string, ...args: any[]) => {
const transfer = ENABLE_TRANSFER ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas || arg instanceof ImageData) : []
self.postMessage({ type: 'event', event, args }, transfer as any)
}
let client: Client
const registeredChannels = [] as string[]
let skipWriteLog = false
const handlers = {
setProxy (data: { hostname: string, port: number }) {
console.log('[protocolWorker] using proxy', data)
net['setProxy']({ hostname: data.hostname, port: data.port })
},
async init ({ options, noPacketsValidation, useAuthFlow }: { options: any, noPacketsValidation: boolean, useAuthFlow: boolean }) {
if (client) throw new Error('Client already initialized')
await globalThis._LOAD_MC_DATA()
if (useAuthFlow) {
options.auth = authFlowWorkerThread
}
client = createClient(options)
for (const event of REDIRECT_EVENTS) {
client.on(event, () => {
emitEvent(event)
})
}
const oldWrite = client.write
client.write = (...args) => {
if (!skipWriteLog) {
emitEvent('writePacket', ...args)
}
return oldWrite.apply(client, args)
}
client.on('packet', (data, packetMeta, buffer, fullBuffer) => {
if (window.stopPacketsProcessing) return
if (!noPacketsValidation) {
validatePacket(packetMeta.name, data, fullBuffer, true)
}
emitEvent('packet', data, packetMeta, {}, { byteLength: fullBuffer.byteLength })
})
},
call (data: { name: string, args: any[] }) {
if (data.name === 'write') {
skipWriteLog = true
}
client[data.name].bind(client)(...data.args)
if (data.name === 'registerChannel' && !registeredChannels.includes(data.args[0])) {
client.on(data.args[0], (...args: any[]) => {
emitEvent(data.args[0], ...args)
})
registeredChannels.push(data.args[0])
}
}
}
const authFlowWorkerThread = async (client, options) => {
self.postMessage({
type: 'authFlow',
version: client.version,
username: client.username
})
options.onMsaCode = (data) => {
self.postMessage({
type: 'msaCode',
data
})
}
client.authflow = {
async getMinecraftJavaToken () {
return new Promise(resolve => {
self.addEventListener('message', async (e) => {
if (e.data.type === 'authflowResult') {
const restoredData = await restoreData(e.data.data)
if (restoredData?.certificates?.profileKeys?.privatePEM) {
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
}
resolve(restoredData)
}
})
})
}
}
await Promise.race([
protocolMicrosoftAuth.authenticate(client, options),
// new Promise((_r, reject) => {
// signInMessageState.abortController.signal.addEventListener('abort', () => {
// reject(new UserError('Aborted by user'))
// })
// })
])
}
// restore dates from strings
const restoreData = async (json) => {
const promises = [] as Array<Promise<void>>
if (typeof json === 'object' && json) {
for (const [key, value] of Object.entries(json)) {
if (typeof value === 'string') {
promises.push(tryRestorePublicKey(value, key, json))
if (value.endsWith('Z')) {
const date = new Date(value)
if (!isNaN(date.getTime())) {
json[key] = date
}
}
}
if (typeof value === 'object') {
// eslint-disable-next-line no-await-in-loop
await restoreData(value)
}
}
}
await Promise.all(promises)
return json
}
const tryRestorePublicKey = async (value: string, name: string, parent: { [x: string]: any }) => {
value = value.trim()
if (!name.endsWith('PEM') || !value.startsWith('-----BEGIN RSA PUBLIC KEY-----') || !value.endsWith('-----END RSA PUBLIC KEY-----')) return
const der = pemToArrayBuffer(value)
const key = await window.crypto.subtle.importKey(
'spki', // Specify that the data is in SPKI format
der,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-256' }
},
true,
['encrypt'] // Specify key usages
)
const originalName = name.replace('PEM', '')
const exported = await window.crypto.subtle.exportKey('spki', key)
const exportedBuffer = new Uint8Array(exported)
parent[originalName] = {
export () {
return exportedBuffer
}
}
}
function pemToArrayBuffer (pem) {
// Fetch the part of the PEM string between header and footer
const pemHeader = '-----BEGIN RSA PUBLIC KEY-----'
const pemFooter = '-----END RSA PUBLIC KEY-----'
const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim()
const binaryDerString = atob(pemContents.replaceAll(/\s/g, ''))
const binaryDer = new Uint8Array(binaryDerString.length)
for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.codePointAt(i)!
}
return binaryDer.buffer
}

View file

@ -0,0 +1,4 @@
// eslint-disable-next-line no-restricted-globals
globalThis.window = self
//@ts-expect-error
process.versions = { node: '' }

View file

@ -4,7 +4,7 @@ import { activeModalStack, activeModalStacks, hideModal, insertActiveModalStack,
import { guessProblem } from '../errorLoadingScreenHelpers'
import type { ConnectOptions } from '../connect'
import { downloadPacketsReplay, packetsRecordingState, replayLogger } from '../packetsReplay/packetsReplayLegacy'
import { getProxyDetails } from '../microsoftAuthflow'
import { getProxyDetails } from '../protocolWorker/microsoftAuthflow'
import { downloadAutoCapturedPackets, getLastAutoCapturedPackets } from '../mineflayer/plugins/packetsRecording'
import { appQueryParams } from '../appParams'
import AppStatus from './AppStatus'

View file

@ -322,7 +322,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
lockedEditing={!!customServersList}
setQuickConnectIp={setQuickConnectIp}
onProfileClick={async () => {
const username = await showOptionsModal('Select authenticated account to remove', authenticatedAccounts.map(a => a.username))
const username = await showOptionsModal('Select authenticated account to remove', authenticatedAccounts.map(a => a.username ?? '??'))
if (!username) return
appStorage.authenticatedAccounts = authenticatedAccounts.filter(a => a.username !== username)
}}

View file

@ -0,0 +1,45 @@
import React from 'react'
import physicsUtilPkg from '@nxg-org/mineflayer-physics-util/package.json'
import mineflayerPkg from 'mineflayer/package.json'
import mcProtocolPkg from 'minecraft-protocol/package.json'
import { useSnapshot } from 'valtio'
import packageJson from '../../../package.json'
import { miscUiState } from '../../globalState'
const LibraryVersions: React.FC = () => {
const versions = {
'@nxg-org/mineflayer-physics-util': physicsUtilPkg.version,
'mineflayer': packageJson.devDependencies['mineflayer'],
'minecraft-protocol': mcProtocolPkg.version
}
const { gameLoaded } = useSnapshot(miscUiState)
if (!gameLoaded) return null
return (
<div
style={{
pointerEvents: 'none',
position: 'fixed',
left: 0,
top: '300px',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: 'white',
padding: '10px',
borderRadius: '0 5px 5px 0',
fontSize: '8px',
zIndex: 1000
}}
>
<div>Library Versions:</div>
{Object.entries(versions).map(([lib, version]) => (
<div key={lib} style={{ marginTop: '5px' }}>
{lib}: {version}
</div>
))}
</div>
)
}
export default LibraryVersions

View file

@ -46,6 +46,7 @@ import BookProvider from './react/BookProvider'
import { options } from './optionsStorage'
import BossBarOverlayProvider from './react/BossBarOverlayProvider'
import DebugEdges from './react/DebugEdges'
import LibraryVersions from './react/components/LibraryVersions'
import GameInteractionOverlay from './react/GameInteractionOverlay'
import MineflayerPluginHud from './react/MineflayerPluginHud'
import MineflayerPluginConsole from './react/MineflayerPluginConsole'
@ -212,6 +213,7 @@ const App = () => {
</RobustPortal>
<EnterFullscreenButton />
<InGameUi />
<LibraryVersions />
<RobustPortal to={document.querySelector('#ui-root')}>
<AllWidgets />
<SingleplayerProvider />

View file

@ -1,8 +1,8 @@
import { versionToNumber } from 'renderer/viewer/common/utils'
import { toMajorVersion } from 'renderer/viewer/lib/simpleUtils'
import { versionToNumber } from 'mc-assets/dist/utils'
import { restoreMinecraftData } from '../optimizeJson'
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
import { toMajorVersion } from '../utils'
import { importLargeData } from '../../generated/large-data-aliases'
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
const customResolver = () => {
const resolver = Promise.withResolvers()

View file

@ -126,12 +126,6 @@ export const isMajorVersionGreater = (ver1: string, ver2: string) => {
return +a1 > +a2 || (+a1 === +a2 && +b1 > +b2)
}
// doesn't support snapshots
export const toMajorVersion = version => {
const [a, b] = (String(version)).split('.')
return `${a}.${b}`
}
let prevRenderDistance = options.renderDistance
export const setRenderDistance = () => {
assertDefined(worldView)