This commit is contained in:
Vitaly 2024-05-10 06:05:49 +03:00 committed by GitHub
commit a87f6d3fa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 443 additions and 75 deletions

View file

@ -114,6 +114,9 @@ Press `Y` to set query parameters to url of your current game state.
- `?singleplayer=1` - Create empty world on load. Nothing will be saved
- `?noSave=true` - Disable auto save on unload / disconnect / export. Only manual save with `/save` command will work
- `?map=<map_url>` - Load the map from ZIP. You can use any url, but it must be CORS enabled.
- `?setting=<setting_name>:<setting_value>` - Set the and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4`
### Notable Things that Power this Project
- [Mineflayer](https://github.com/PrismarineJS/mineflayer) - Handles all client-side communications with the server (including the builtin one) - forked

View file

@ -6,6 +6,7 @@
"promoteServers": [
{
"ip": "kaboom.pw",
"version": "1.18.2",
"description": "Chaos and destruction server. Free for everyone."
},
{
@ -15,6 +16,7 @@
},
{
"ip": "play.minemalia.com",
"version": "1.18.2",
"description": "Only login with existing accounts."
}
]

View file

@ -17,6 +17,7 @@ fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replac
const watch = process.argv.includes('--watch') || process.argv.includes('-w')
const prod = process.argv.includes('--prod')
if (prod) process.env.PROD = 'true'
const dev = !prod
const banner = [

View file

@ -62,6 +62,7 @@
"esbuild": "^0.19.3",
"esbuild-plugin-polyfill-node": "^0.3.0",
"express": "^4.18.2",
"filesize": "^10.0.12",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.20",
"fs-extra": "^11.1.1",
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
@ -70,6 +71,8 @@
"lodash-es": "^4.17.21",
"minecraft-assets": "^1.12.2",
"minecraft-data": "3.65.0",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol",
"mojangson": "^2.0.4",
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
"node-gzip": "^1.1.2",
"peerjs": "^1.5.0",
@ -77,6 +80,7 @@
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prosemirror-example-setup": "^1.2.2",
"prosemirror-markdown": "^1.12.0",
"prosemirror-menu": "^1.2.4",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.33.1",
"qrcode.react": "^3.1.0",
@ -84,18 +88,15 @@
"react-dom": "^18.2.0",
"react-transition-group": "^4.4.5",
"remark": "^15.0.1",
"filesize": "^10.0.12",
"sanitize-filename": "^1.6.3",
"skinview3d": "^3.0.1",
"source-map-js": "^1.0.2",
"stats-gl": "^1.0.5",
"stats.js": "^0.17.0",
"use-typed-event-listener": "^4.0.2",
"mojangson": "^2.0.4",
"prosemirror-menu": "^1.2.4",
"tabbable": "^6.2.0",
"title-case": "3.x",
"ua-parser-js": "^1.0.37",
"use-typed-event-listener": "^4.0.2",
"valtio": "^1.11.1",
"vec3": "^0.1.7",
"workbox-build": "^7.0.0"
@ -116,7 +117,7 @@
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"constants-browserify": "^1.0.0",
"contro-max": "^0.1.2",
"contro-max": "^0.1.6",
"crypto-browserify": "^3.12.0",
"cypress": "^10.11.0",
"cypress-esbuild-preprocessor": "^1.0.2",
@ -156,12 +157,15 @@
"prismarine-world": "github:zardoy/prismarine-world#next-era",
"minecraft-data": "3.65.0",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"minecraft-protocol": "github:zardoy/minecraft-protocol#everything",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol",
"react": "^18.2.0",
"prismarine-chunk": "github:zardoy/prismarine-chunk"
},
"updateConfig": {
"ignoreDependencies": []
},
"patchedDependencies": {
"minecraft-protocol@1.47.0": "patches/minecraft-protocol@1.47.0.patch"
}
},
"packageManager": "pnpm@9.0.4"

View file

@ -0,0 +1,130 @@
diff --git a/src/client/autoVersion.js b/src/client/autoVersion.js
index c437ecf3a0e4ab5758a48538c714b7e9651bb5da..d9c9895ae8614550aa09ad60a396ac32ffdf1287 100644
--- a/src/client/autoVersion.js
+++ b/src/client/autoVersion.js
@@ -9,7 +9,7 @@ module.exports = function (client, options) {
client.wait_connect = true // don't let src/client/setProtocol proceed on socket 'connect' until 'connect_allowed'
debug('pinging', options.host)
// TODO: use 0xfe ping instead for better compatibility/performance? https://github.com/deathcap/node-minecraft-ping
- ping(options, function (err, response) {
+ ping(options, async function (err, response) {
if (err) { return client.emit('error', err) }
debug('ping response', response)
// TODO: could also use ping pre-connect to save description, type, max players, etc.
@@ -40,6 +40,7 @@ module.exports = function (client, options) {
// Reinitialize client object with new version TODO: move out of its constructor?
client.version = minecraftVersion
+ await options.versionSelectedHook?.(client)
client.state = states.HANDSHAKING
// Let other plugins such as Forge/FML (modinfo) respond to the ping response
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644
--- a/src/client/encrypt.js
+++ b/src/client/encrypt.js
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
if (packet.serverId !== '-') {
debug('This server appears to be an online server and you are providing no password, the authentication will probably fail')
}
- sendEncryptionKeyResponse()
+ client.end('This server appears to be an online server and you are providing no authentication. Try authenticating first.')
+ // sendEncryptionKeyResponse()
+ // client.once('set_compression', () => {
+ // clearTimeout(loginTimeout)
+ // })
}
function onJoinServerResponse (err) {
diff --git a/src/client.js b/src/client.js
index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590a3ad8b8a 100644
--- a/src/client.js
+++ b/src/client.js
@@ -88,10 +88,12 @@ class Client extends EventEmitter {
parsed.metadata.name = parsed.data.name
parsed.data = parsed.data.params
parsed.metadata.state = state
- debug('read packet ' + state + '.' + parsed.metadata.name)
- if (debug.enabled) {
- const s = JSON.stringify(parsed.data, null, 2)
- debug(s && s.length > 10000 ? parsed.data : s)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) {
+ debug('read packet ' + state + '.' + parsed.metadata.name)
+ if (debug.enabled) {
+ const s = JSON.stringify(parsed.data, null, 2)
+ debug(s && s.length > 10000 ? parsed.data : s)
+ }
}
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
if (this._mcBundle.length) { // End bundle
@@ -109,7 +111,13 @@ class Client extends EventEmitter {
this._hasBundlePacket = false
}
} else {
- emitPacket(parsed)
+ try {
+ emitPacket(parsed)
+ } catch (err) {
+ console.log('Client incorrectly handled packet ' + parsed.metadata.name)
+ console.error(err)
+ // todo investigate why it doesn't close the stream even if unhandled there
+ }
}
})
}
@@ -166,7 +174,10 @@ class Client extends EventEmitter {
}
const onFatalError = (err) => {
- this.emit('error', err)
+ // todo find out what is trying to write after client disconnect
+ if(err.code !== 'ECONNABORTED') {
+ this.emit('error', err)
+ }
endSocket()
}
@@ -195,6 +206,8 @@ class Client extends EventEmitter {
serializer -> framer -> socket -> splitter -> deserializer */
if (this.serializer) {
this.serializer.end()
+ this.socket?.end()
+ this.socket?.emit('end')
} else {
if (this.socket) this.socket.end()
}
@@ -236,8 +249,11 @@ class Client extends EventEmitter {
write (name, params) {
if (!this.serializer.writable) { return }
- debug('writing packet ' + this.state + '.' + name)
- debug(params)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) {
+ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
+ debug(params)
+ }
+ this.emit('writePacket', name, params)
this.serializer.write({ name, params })
}
diff --git a/src/index.d.ts b/src/index.d.ts
index 0a5821c32d735e11205a280aa5a503c13533dc14..94a49f661d922478b940d853169b6087e6ec3df5 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -121,6 +121,7 @@ declare module 'minecraft-protocol' {
sessionServer?: string
keepAlive?: boolean
closeTimeout?: number
+ closeTimeout?: number
noPongTimeout?: number
checkTimeoutInterval?: number
version?: string
@@ -141,6 +142,8 @@ declare module 'minecraft-protocol' {
disableChatSigning?: boolean
/** Pass custom client implementation if needed. */
Client?: Client
+ /** Can be used to prepare mc data on autoVersion (client.version has selected version) */
+ versionSelectedHook?: (client: Client) => Promise<void> | void
}
export class Server extends EventEmitter {

82
pnpm-lock.yaml generated
View file

@ -12,10 +12,15 @@ overrides:
prismarine-world: github:zardoy/prismarine-world#next-era
minecraft-data: 3.65.0
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
minecraft-protocol: github:zardoy/minecraft-protocol#everything
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol
react: ^18.2.0
prismarine-chunk: github:zardoy/prismarine-chunk
patchedDependencies:
minecraft-protocol@1.47.0:
hash: 2uxevyasyasdavsxuehfavgkjq
path: patches/minecraft-protocol@1.47.0.patch
importers:
.:
@ -122,6 +127,9 @@ importers:
minecraft-data:
specifier: 3.65.0
version: 3.65.0
minecraft-protocol:
specifier: github:PrismarineJS/node-minecraft-protocol
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13)
mojangson:
specifier: ^2.0.4
version: 2.0.4
@ -257,8 +265,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
contro-max:
specifier: ^0.1.2
version: 0.1.2(typescript@5.5.0-beta)
specifier: ^0.1.6
version: 0.1.6(typescript@5.5.0-beta)
crypto-browserify:
specifier: ^3.12.0
version: 3.12.0
@ -3800,8 +3808,8 @@ packages:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
contro-max@0.1.2:
resolution: {integrity: sha512-mY9aRQ9on/iyzvyhb4OD/10WRRKulVd92F7cxMFVn3rq5EwI+gZitGpHN2mp9+IzwRgBJrOKr1C051b3YlEktQ==}
contro-max@0.1.6:
resolution: {integrity: sha512-QsoOcAlbtNgkCGBvwKsh+GUVZ2c5zfMgYQCu+v4MplX5VolkWhMwAcEOBRxt8oENbnRXOKUGQr816Ey1G4/jpg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
convert-source-map@1.9.0:
@ -4190,10 +4198,6 @@ packages:
resolution: {integrity: sha512-y5JHnrygHnCndtqVHHDhCr0ZzzWHK5RBTczWRlGSIR5UnGHBXuxpoaE0UB5E82qym8ma2dI799wDSSJN2e4VSg==}
engines: {node: '>=5'}
emittery@0.10.2:
resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==}
engines: {node: '>=12'}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -6038,9 +6042,13 @@ packages:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c}
version: 1.0.1
minecraft-protocol@https://codeload.github.com/zardoy/minecraft-protocol/tar.gz/2c14a686bfe7cbd9a5c87b629b402295ee86219f:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-protocol/tar.gz/2c14a686bfe7cbd9a5c87b629b402295ee86219f}
version: 1.45.0
minecraft-protocol@1.47.0:
resolution: {integrity: sha512-IHL8faXLLIWv1O+2v2NgyKlooilu/OiSL9orI8Kqed/rZvVOrFPzs2PwMAYjpQX9gxLPhiSU19KqZ8CjfNuqhg==}
engines: {node: '>=14'}
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7}
version: 1.47.0
engines: {node: '>=14'}
minecraft-wrap@1.5.1:
@ -6698,6 +6706,9 @@ packages:
resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0}
version: 1.17.1
prismarine-chat@1.10.1:
resolution: {integrity: sha512-XukYcuueuhDxzEXG7r8BZyt6jOObrPPB4JESCgb+/XenB9nExoSHF8eTQWWj8faKPLqm1dRQaYwFJlNBlJZJUw==}
prismarine-chat@1.9.1:
resolution: {integrity: sha512-x7WWa5MNhiLZSO6tw+YyKpzquFZ+DNISVgiV6K3SU0GsishMXe+nto02WhF/4AuFerKdugm9u1d/r4C4zSkJOg==}
@ -11924,7 +11935,7 @@ snapshots:
flatmap: 0.0.3
long: 5.2.3
minecraft-data: 3.65.0
minecraft-protocol: https://codeload.github.com/zardoy/minecraft-protocol/tar.gz/2c14a686bfe7cbd9a5c87b629b402295ee86219f(encoding@0.1.13)
minecraft-protocol: 1.47.0(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13)
mkdirp: 2.1.6
node-gzip: 1.1.2
node-rsa: 1.1.1
@ -12829,10 +12840,11 @@ snapshots:
content-type@1.0.5: {}
contro-max@0.1.2(typescript@5.5.0-beta):
contro-max@0.1.6(typescript@5.5.0-beta):
dependencies:
emittery: 0.10.2
events: 3.3.0
lodash-es: 4.17.21
typed-emitter: 2.1.0
optionalDependencies:
react: 18.2.0
use-typed-event-listener: 4.0.2(react@18.2.0)(typescript@5.5.0-beta)
@ -13306,8 +13318,6 @@ snapshots:
emit-then@2.0.0: {}
emittery@0.10.2: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@ -15670,7 +15680,7 @@ snapshots:
- '@types/react'
- react
minecraft-protocol@https://codeload.github.com/zardoy/minecraft-protocol/tar.gz/2c14a686bfe7cbd9a5c87b629b402295ee86219f(encoding@0.1.13):
minecraft-protocol@1.47.0(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13):
dependencies:
'@types/readable-stream': 4.0.12
aes-js: 3.1.2
@ -15684,6 +15694,32 @@ snapshots:
node-fetch: 2.7.0(encoding@0.1.13)
node-rsa: 0.4.2
prismarine-auth: 2.4.2(encoding@0.1.13)
prismarine-chat: 1.10.1
prismarine-nbt: 2.5.0
prismarine-realms: 1.3.2(encoding@0.1.13)
protodef: 1.15.0
readable-stream: 4.5.2
uuid-1345: 1.0.2
yggdrasil: 1.7.0(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- supports-color
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13):
dependencies:
'@types/readable-stream': 4.0.12
aes-js: 3.1.2
buffer-equal: 1.0.1
debug: 4.3.4(supports-color@8.1.1)
endian-toggle: 0.0.0
lodash.get: 4.4.2
lodash.merge: 4.6.2
minecraft-data: 3.65.0
minecraft-folder-path: 1.2.0
node-fetch: 2.7.0(encoding@0.1.13)
node-rsa: 0.4.2
prismarine-auth: 2.4.2(encoding@0.1.13)
prismarine-chat: 1.10.1
prismarine-nbt: 2.5.0
prismarine-realms: 1.3.2(encoding@0.1.13)
protodef: 1.15.0
@ -15730,7 +15766,7 @@ snapshots:
mineflayer@https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/5a544cf2547a6e0f1f17786962d77a33c661c02f(encoding@0.1.13):
dependencies:
minecraft-data: 3.65.0
minecraft-protocol: https://codeload.github.com/zardoy/minecraft-protocol/tar.gz/2c14a686bfe7cbd9a5c87b629b402295ee86219f(encoding@0.1.13)
minecraft-protocol: 1.47.0(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13)
prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0
prismarine-chat: 1.9.1
@ -16445,6 +16481,12 @@ snapshots:
prismarine-nbt: 2.5.0
prismarine-registry: 1.7.0
prismarine-chat@1.10.1:
dependencies:
mojangson: 2.0.4
prismarine-nbt: 2.5.0
prismarine-registry: 1.7.0
prismarine-chat@1.9.1:
dependencies:
mojangson: 2.0.4
@ -16467,7 +16509,7 @@ snapshots:
prismarine-entity@2.3.1:
dependencies:
prismarine-chat: 1.9.1
prismarine-chat: 1.10.1
prismarine-item: 1.14.0
prismarine-registry: 1.7.0
vec3: 0.1.8

View file

@ -116,7 +116,7 @@ const plugins = [
//@ts-ignore
for (const file of outputFiles) {
let contents = file.text
if (file.path.endsWith('.map') && file.text) {
if (file.path.endsWith('.map') && file.text && !process.env.PROD) {
const map = JSON.parse(file.text)
removeNodeModulesSourcemaps(map)
contents = JSON.stringify(map)

View file

@ -15,6 +15,7 @@ import { fsState } from './loadSave'
import { showOptionsModal } from './react/SelectOption'
import widgets from './react/widgets'
import { getItemFromBlock } from './botUtils'
import { gamepadUiCursorState, moveGamepadCursorByPx } from './react/GamepadUiCursor'
// todo move this to shared file with component
export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}'))
@ -45,7 +46,10 @@ export const contro = new ControMax({
},
ui: {
back: [null/* 'Escape' */, 'B'],
click: [null, 'A'],
leftClick: [null, 'A'],
rightClick: [null, 'Y'],
speedupCursor: [null, 'Left Stick'],
pauseMenu: [null, 'Start']
},
advanced: {
lockUrl: ['KeyY'],
@ -66,7 +70,7 @@ export const contro = new ControMax({
defaultControlOptions: controlOptions,
target: document,
captureEvents () {
return bot && isGameActive(false)
return true
},
storeProvider: {
load: () => customKeymaps,
@ -86,8 +90,18 @@ const setSprinting = (state: boolean) => {
gameAdditionalState.isSprinting = state
}
contro.on('movementUpdate', ({ vector, gamepadIndex }) => {
contro.on('movementUpdate', ({ vector, soleVector, gamepadIndex }) => {
if (gamepadIndex !== undefined && gamepadUiCursorState.display) {
const deadzone = 0.1 // TODO make deadzone configurable
if (Math.abs(soleVector.x) < deadzone && Math.abs(soleVector.z) < deadzone) {
return
}
moveGamepadCursorByPx(soleVector.x, true)
moveGamepadCursorByPx(soleVector.z, false)
emitMousemove()
}
miscUiState.usingGamepadInput = gamepadIndex !== undefined
if (!bot || !isGameActive(false)) return
// gamepadIndex will be used for splitscreen in future
const coordToAction = [
['z', -1, 'forward'],
@ -145,11 +159,71 @@ subscribe(activeModalStack, () => {
}
})
const uiCommand = (command: Command) => {
if (command === 'ui.back') {
hideCurrentModal()
} else if (command === 'ui.click') {
// todo cursor
const emitMousemove = () => {
const { x, y } = gamepadUiCursorState
const xAbs = x / 100 * window.innerWidth
const yAbs = y / 100 * window.innerHeight
const element = document.elementFromPoint(xAbs, yAbs) as HTMLElement | null
if (!element) return
element.dispatchEvent(new MouseEvent('mousemove', {
clientX: xAbs,
clientY: yAbs
}))
}
let lastClickedEl = null as HTMLElement | null
let lastClickedElTimeout: ReturnType<typeof setTimeout> | undefined
const inModalCommand = (command: Command, pressed: boolean) => {
if (pressed && !gamepadUiCursorState.display) return
if (pressed) {
if (command === 'ui.back') {
hideCurrentModal()
}
if (command === 'ui.leftClick' || command === 'ui.rightClick') {
// in percent
const { x, y } = gamepadUiCursorState
const xAbs = x / 100 * window.innerWidth
const yAbs = y / 100 * window.innerHeight
const el = document.elementFromPoint(xAbs, yAbs) as HTMLElement
if (el) {
if (el === lastClickedEl && command === 'ui.leftClick') {
el.dispatchEvent(new MouseEvent('dblclick', {
bubbles: true,
clientX: xAbs,
clientY: yAbs
}))
return
}
el.dispatchEvent(new MouseEvent('mousedown', {
button: command === 'ui.leftClick' ? 0 : 2,
bubbles: true,
clientX: xAbs,
clientY: yAbs
}))
el.dispatchEvent(new MouseEvent(command === 'ui.leftClick' ? 'click' : 'contextmenu', {
bubbles: true,
clientX: xAbs,
clientY: yAbs
}))
el.dispatchEvent(new MouseEvent('mouseup', {
button: command === 'ui.leftClick' ? 0 : 2,
bubbles: true,
clientX: xAbs,
clientY: yAbs
}))
el.focus()
lastClickedEl = el
if (lastClickedElTimeout) clearTimeout(lastClickedElTimeout)
lastClickedElTimeout = setTimeout(() => {
lastClickedEl = null
}, 500)
}
}
}
if (command === 'ui.speedupCursor') {
gamepadUiCursorState.multiply = pressed ? 2 : 1
}
}
@ -160,10 +234,7 @@ const setSneaking = (state: boolean) => {
const onTriggerOrReleased = (command: Command, pressed: boolean) => {
// always allow release!
if (pressed && !isGameActive(true)) {
uiCommand(command)
return
}
if (!bot || !isGameActive(false)) return
if (stringStartsWith(command, 'general')) {
// handle general commands
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
@ -199,7 +270,9 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => {
}
// im still not sure, maybe need to refactor to handle in inventory instead
const alwaysHandledCommand = (command: Command) => {
const alwaysPressedHandledCommand = (command: Command) => {
inModalCommand(command, true)
// triggered even outside of the game
if (command === 'general.inventory') {
if (activeModalStack.at(-1)?.reactType?.startsWith?.('player_win:')) { // todo?
hideCurrentModal()
@ -207,9 +280,14 @@ const alwaysHandledCommand = (command: Command) => {
}
}
function cycleHotbarSlot (dir: 1 | -1) {
const newHotbarSlot = (bot.quickBarSlot + dir + 9) % 9
bot.setQuickBarSlot(newHotbarSlot)
}
contro.on('trigger', ({ command }) => {
const willContinue = !isGameActive(true)
alwaysHandledCommand(command)
alwaysPressedHandledCommand(command)
if (willContinue) return
const secondActionCommand = secondActionCommands[command]
@ -229,8 +307,15 @@ contro.on('trigger', ({ command }) => {
onTriggerOrReleased(command, true)
if (stringStartsWith(command, 'general')) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (command) {
case 'general.jump':
case 'general.sneak':
case 'general.toggleSneakOrDown':
case 'general.sprint':
case 'general.attackDestroy':
case 'general.interactPlace':
// handled in onTriggerOrReleased
break
case 'general.inventory':
document.exitPointerLock?.()
openPlayerInventory()
@ -258,6 +343,12 @@ contro.on('trigger', ({ command }) => {
case 'general.selectItem':
void selectItem()
break
case 'general.nextHotbarSlot':
cycleHotbarSlot(1)
break
case 'general.prevHotbarSlot':
cycleHotbarSlot(-1)
break
}
}
if (command === 'advanced.lockUrl') {
@ -278,9 +369,14 @@ contro.on('trigger', ({ command }) => {
window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`)
// return
}
if (command === 'ui.pauseMenu') {
showModal({ reactType: 'pause-screen' })
}
})
contro.on('release', ({ command }) => {
inModalCommand(command, false)
onTriggerOrReleased(command, false)
})

View file

@ -42,8 +42,10 @@ import { defaultsDeep } from 'lodash-es'
import { initVR } from './vr'
import {
AppConfig,
activeModalStack,
activeModalStacks,
hideModal,
insertActiveModalStack,
isGameActive,
miscUiState,
@ -88,6 +90,7 @@ import { ViewerWrapper } from 'prismarine-viewer/viewer/lib/viewerWrapper'
import './devReload'
import './water'
import { ConnectOptions } from './connect'
import { subscribe } from 'valtio'
window.debug = debug
window.THREE = THREE
@ -832,7 +835,7 @@ document.body.addEventListener('touchstart', (e) => {
void window.fetch('config.json').then(async res => res.json()).then(c => c, (error) => {
console.warn('Failed to load optional app config.json', error)
return {}
}).then((config) => {
}).then((config: AppConfig | {}) => {
miscUiState.appConfig = config
})
@ -850,8 +853,27 @@ downloadAndOpenFile().then((downloadAction) => {
return
}
if (qs.get('ip') || qs.get('proxy')) {
// show server editor for connect or save
showModal({ reactType: 'editServer' })
const waitAppConfigLoad = !qs.get('proxy')
const openServerEditor = () => {
hideModal()
// show server editor for connect or save
showModal({ reactType: 'editServer' })
}
showModal({ reactType: 'empty' })
if (waitAppConfigLoad) {
const unsubscribe = subscribe(miscUiState, checkCanDisplay)
checkCanDisplay()
// eslint-disable-next-line no-inner-declarations
function checkCanDisplay () {
if (miscUiState.appConfig) {
unsubscribe()
openServerEditor()
return true
}
}
} else {
openServerEditor()
}
}
void Promise.resolve().then(() => {

View file

@ -6,8 +6,8 @@ import { subscribeKey } from 'valtio/utils'
import { omitObj } from '@zardoy/utils'
const defaultOptions = {
renderDistance: 2,
multiplayerRenderDistance: 2,
renderDistance: 3,
multiplayerRenderDistance: 3,
closeConfirmation: true,
autoFullScreen: false,
mouseRawInput: false,

View file

@ -4,7 +4,7 @@ import Input from './Input'
import Button from './Button'
import { useIsSmallWidth } from './simpleHooks'
export interface NewServerInfo {
export interface BaseServerInfo {
ip: string
name?: string
versionOverride?: string
@ -15,12 +15,12 @@ export interface NewServerInfo {
interface Props {
onBack: () => void
onConfirm: (info: NewServerInfo) => void
onConfirm: (info: BaseServerInfo) => void
title?: string
initialData?: NewServerInfo
initialData?: BaseServerInfo
parseQs?: boolean
onQsConnect?: (server: NewServerInfo) => void
defaults?: Pick<NewServerInfo, 'proxyOverride' | 'usernameOverride'>
onQsConnect?: (server: BaseServerInfo) => void
defaults?: Pick<BaseServerInfo, 'proxyOverride' | 'usernameOverride'>
}
export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, defaults }: Props) => {
@ -33,7 +33,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
const [serverIp, setServerIp] = React.useState(ipWithoutPort ?? qsParams?.get('ip') ?? '')
const [serverPort, setServerPort] = React.useState(port ?? '')
const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? qsParams?.get('version') ?? '')
const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParams?.get('version') ?? '')
const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParams?.get('proxy') ?? '')
const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParams?.get('username') ?? '')
const [passwordOverride, setPasswordOverride] = React.useState(initialData?.passwordOverride ?? qsParams?.get('password') ?? '')

View file

@ -8,4 +8,5 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
image-rendering: pixelated;
}

View file

@ -0,0 +1,11 @@
.crosshair {
width: 16px;
height: 16px;
background: var(--gui-icons);
background-size: calc(256px * var(--crosshair-scale));
position: fixed;
z-index: 100;
transform: translate(-50%, -50%);
pointer-events: none;
image-rendering: pixelated;
}

View file

@ -0,0 +1,40 @@
import { proxy, useSnapshot } from 'valtio'
import { useEffect } from 'react'
import { activeModalStack, miscUiState } from '../globalState'
import SharedHudVars from './SharedHudVars'
import styles from './GamepadUiCursor.module.css'
export const gamepadUiCursorState = proxy({
x: 50,
y: 50,
multiply: 1,
display: false
})
export const moveGamepadCursorByPx = (value: number, isX: boolean) => {
value *= gamepadUiCursorState.multiply * 3
const valueToPercentage = value / (isX ? window.innerWidth : window.innerHeight) * 100
gamepadUiCursorState[isX ? 'x' : 'y'] += valueToPercentage
}
export default () => {
const hasModals = useSnapshot(activeModalStack).length > 0
const { x, y } = useSnapshot(gamepadUiCursorState)
const { usingGamepadInput, gameLoaded } = useSnapshot(miscUiState)
const doDisplay = usingGamepadInput && (hasModals || !gameLoaded)
useEffect(() => {
document.body.style.cursor = gameLoaded && !hasModals && usingGamepadInput ? 'none' : 'auto'
}, [usingGamepadInput, hasModals, gameLoaded])
useEffect(() => {
gamepadUiCursorState.display = doDisplay
}, [doDisplay])
if (!doDisplay) return null
return <SharedHudVars>
<div className={styles.crosshair} style={{ left: `${x}%`, top: `${y}%` }} />
</SharedHudVars>
}

View file

@ -11,7 +11,7 @@ interface Props extends React.ComponentProps<typeof Singleplayer> {
username?: string
password?: string
proxy?: string
version?: string
versionOverride?: string
shouldSave?: boolean
}) => void
initialProxies: SavedProxiesLocalStorage

View file

@ -1,22 +1,16 @@
import { useEffect, useMemo, useState } from 'react'
import { proxy } from 'valtio'
import { proxy, useSnapshot } from 'valtio'
import { qsOptions } from '../optionsStorage'
import { ConnectOptions } from '../connect'
import { hideCurrentModal, miscUiState, showModal } from '../globalState'
import ServersList from './ServersList'
import AddServerOrConnect from './AddServerOrConnect'
import AddServerOrConnect, { BaseServerInfo } from './AddServerOrConnect'
import { useDidUpdateEffect } from './utils'
import { useIsModalActive } from './utilsApp'
interface StoreServerItem {
ip: string,
name?: string
version?: string
interface StoreServerItem extends BaseServerInfo {
lastJoined?: number
description?: string
proxyOverride?: string
usernameOverride?: string
passwordOverride?: string
optionsOverride?: Record<string, any>
autoLogin?: Record<string, string>
}
@ -69,7 +63,7 @@ const getInitialServersList = () => {
const legacyLastJoinedServer: StoreServerItem = {
ip: localStorage['server'],
passwordOverride: localStorage['password'],
version: localStorage['version'],
versionOverride: localStorage['version'],
lastJoined: Date.now()
}
servers.push(legacyLastJoinedServer)
@ -80,7 +74,7 @@ const getInitialServersList = () => {
servers.push({
ip: server.ip,
description: server.description,
version: server.version,
versionOverride: server.version,
})
}
}
@ -206,7 +200,8 @@ const Inner = () => {
setServersList(old => [...old, server])
} else {
const index = serversList.indexOf(serverEditScreen)
serversList[index] = info
const { lastJoined } = serversList[index]
serversList[index] = { ...info, lastJoined }
setServersList([...serversList])
}
setServerEditScreen(null)
@ -248,7 +243,7 @@ const Inner = () => {
username,
server: ip,
proxy: overrides.proxy || selectedProxy,
botVersion: overrides.version,
botVersion: overrides.versionOverride ?? /* legacy */ overrides['version'],
password: overrides.password,
ignoreQs: true,
autoLoginPassword: server?.autoLogin?.[username],
@ -312,7 +307,7 @@ const Inner = () => {
return {
name: server.index.toString(),
title: server.name || server.ip,
detail: (server.version ?? '') + ' ' + (server.usernameOverride ?? ''),
detail: (server.versionOverride ?? '') + ' ' + (server.usernameOverride ?? ''),
// lastPlayed: server.lastJoined,
formattedTextOverride: additional?.formattedText,
worldNameRight: additional?.textNameRight ?? '',

View file

@ -2,7 +2,7 @@ import { CSSProperties, useEffect } from 'react'
import icons from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/icons.png'
import widgets from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png'
export default ({ children }) => {
export default ({ children }): React.ReactElement => {
useEffect(() => {
if (document.getElementById('hud-vars-style')) return
// 1. Don't inline long data URLs for better DX in elements tab

View file

@ -40,12 +40,14 @@ export const handleMovementStickDelta = (e?: { clientX, clientY }) => {
}
joystickPointer.joystickInner!.style.transform = `translate(${x}px, ${y}px)`
const vector = {
x: x / max,
y: 0,
z: y / max,
}
void contro.emit('movementUpdate', {
vector: {
x: x / max,
y: 0,
z: y / max,
},
vector,
soleVector: vector
})
}

View file

@ -8,7 +8,7 @@
}
.input {
position: relative;
position: absolute;
outline: none;
border: none;
background: none;

View file

@ -35,6 +35,7 @@ import HotbarRenderApp from './react/HotbarRenderApp'
import Crosshair from './react/Crosshair'
import ButtonAppProvider from './react/ButtonAppProvider'
import ServersListProvider from './react/ServersListProvider'
import GamepadUiCursor from './react/GamepadUiCursor'
const RobustPortal = ({ children, to }) => {
return createPortal(<PerComponentErrorBoundary>{children}</PerComponentErrorBoundary>, to)
@ -149,6 +150,12 @@ const App = () => {
{/* <GameHud>
</GameHud> */}
</RobustPortal>
<RobustPortal to={document.body}>
<div className='overlay-top-scaled'>
<GamepadUiCursor />
</div>
<div></div>
</RobustPortal>
</ButtonAppProvider>
</div>
}

View file

@ -134,6 +134,18 @@ body {
-ms-interpolation-mode: nearest-neighbor;
}
.overlay-top-scaled {
position: fixed;
inset: 0;
transform-origin: top left;
transform: scale(var(--guiScale));
width: calc(100% / var(--guiScale));
height: calc(100% / var(--guiScale));
z-index: 80;
image-rendering: pixelated;
pointer-events: none;
}
#viewer-canvas {
position: fixed;
top: 0;