enforce style that was used in repo! indent back to 2
This commit is contained in:
parent
5b9c268750
commit
ff2c9e4c0c
12 changed files with 507 additions and 486 deletions
|
|
@ -1,6 +1,27 @@
|
|||
{
|
||||
"extends": "zardoy",
|
||||
"rules": {
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 2,
|
||||
"ignoredNodes": [
|
||||
"TemplateLiteral"
|
||||
]
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
// perf
|
||||
"import/no-deprecated": "off",
|
||||
// ---
|
||||
|
|
|
|||
|
|
@ -2,78 +2,78 @@
|
|||
import type { AppOptions } from '../../src/optionsStorage'
|
||||
|
||||
const cleanVisit = () => {
|
||||
window.localStorage.clear()
|
||||
visit()
|
||||
window.localStorage.clear()
|
||||
visit()
|
||||
}
|
||||
|
||||
const visit = (url = '/') => {
|
||||
window.localStorage.cypress = 'true'
|
||||
cy.visit(url)
|
||||
window.localStorage.cypress = 'true'
|
||||
cy.visit(url)
|
||||
}
|
||||
|
||||
// todo use ssl
|
||||
|
||||
const compareRenderedFlatWorld = () => {
|
||||
// wait for render
|
||||
// cy.wait(6000)
|
||||
// cy.get('body').toMatchImageSnapshot({
|
||||
// name: 'superflat-world',
|
||||
// })
|
||||
// wait for render
|
||||
// cy.wait(6000)
|
||||
// cy.get('body').toMatchImageSnapshot({
|
||||
// name: 'superflat-world',
|
||||
// })
|
||||
}
|
||||
|
||||
const testWorldLoad = () => {
|
||||
cy.document().then({ timeout: 20_000, }, doc => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
doc.addEventListener('cypress-world-ready', resolve)
|
||||
})
|
||||
}).then(() => {
|
||||
compareRenderedFlatWorld()
|
||||
cy.document().then({ timeout: 20_000, }, doc => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
doc.addEventListener('cypress-world-ready', resolve)
|
||||
})
|
||||
}).then(() => {
|
||||
compareRenderedFlatWorld()
|
||||
})
|
||||
}
|
||||
|
||||
const setOptions = (options: Partial<AppOptions>) => {
|
||||
cy.window().then(win => {
|
||||
Object.assign(win['options'], options)
|
||||
})
|
||||
cy.window().then(win => {
|
||||
Object.assign(win['options'], options)
|
||||
})
|
||||
}
|
||||
|
||||
it('Loads & renders singleplayer', () => {
|
||||
cleanVisit()
|
||||
setOptions({
|
||||
localServerOptions: {
|
||||
generation: {
|
||||
name: 'superflat',
|
||||
// eslint-disable-next-line unicorn/numeric-separators-style
|
||||
options: { seed: 250869072 }
|
||||
},
|
||||
},
|
||||
renderDistance: 2
|
||||
})
|
||||
cy.get('#title-screen').find('[data-test-id="singleplayer-button"]', { includeShadowDom: true, }).click()
|
||||
testWorldLoad()
|
||||
cleanVisit()
|
||||
setOptions({
|
||||
localServerOptions: {
|
||||
generation: {
|
||||
name: 'superflat',
|
||||
// eslint-disable-next-line unicorn/numeric-separators-style
|
||||
options: { seed: 250869072 }
|
||||
},
|
||||
},
|
||||
renderDistance: 2
|
||||
})
|
||||
cy.get('#title-screen').find('[data-test-id="singleplayer-button"]', { includeShadowDom: true, }).click()
|
||||
testWorldLoad()
|
||||
})
|
||||
|
||||
it('Joins to server', () => {
|
||||
// visit('/?version=1.16.1')
|
||||
window.localStorage.version = '1.16.1'
|
||||
visit()
|
||||
// todo replace with data-test
|
||||
cy.get('#title-screen').find('[data-test-id="connect-screen-button"]', { includeShadowDom: true, }).click()
|
||||
cy.get('input#serverip', { includeShadowDom: true, }).clear().focus().type('localhost')
|
||||
cy.get('[data-test-id="connect-to-server"]', { includeShadowDom: true, }).click()
|
||||
testWorldLoad()
|
||||
// visit('/?version=1.16.1')
|
||||
window.localStorage.version = '1.16.1'
|
||||
visit()
|
||||
// todo replace with data-test
|
||||
cy.get('#title-screen').find('[data-test-id="connect-screen-button"]', { includeShadowDom: true, }).click()
|
||||
cy.get('input#serverip', { includeShadowDom: true, }).clear().focus().type('localhost')
|
||||
cy.get('[data-test-id="connect-to-server"]', { includeShadowDom: true, }).click()
|
||||
testWorldLoad()
|
||||
})
|
||||
|
||||
it('Loads & renders zip world', () => {
|
||||
cleanVisit()
|
||||
cy.get('#title-screen').find('[data-test-id="select-file-folder"]', { includeShadowDom: true, }).click({ shiftKey: true })
|
||||
cy.get('input[type="file"]').selectFile('cypress/superflat.zip', { force: true })
|
||||
testWorldLoad()
|
||||
cleanVisit()
|
||||
cy.get('#title-screen').find('[data-test-id="select-file-folder"]', { includeShadowDom: true, }).click({ shiftKey: true })
|
||||
cy.get('input[type="file"]').selectFile('cypress/superflat.zip', { force: true })
|
||||
testWorldLoad()
|
||||
})
|
||||
|
||||
it.skip('Performance test', () => {
|
||||
// select that world
|
||||
// from -2 85 24
|
||||
// await bot.loadPlugin(pathfinder.pathfinder)
|
||||
// bot.pathfinder.goto(new pathfinder.goals.GoalXZ(28, -28))
|
||||
// select that world
|
||||
// from -2 85 24
|
||||
// await bot.loadPlugin(pathfinder.pathfinder)
|
||||
// bot.pathfinder.goto(new pathfinder.goals.GoalXZ(28, -28))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { installTexturePack, updateTexturePackInstalledState } from './texturePa
|
|||
browserfs.install(window)
|
||||
// todo migrate to StorageManager API for localsave as localstorage has only 5mb limit, when localstorage is fallback test limit warning on 4mb
|
||||
const deafultMountablePoints = {
|
||||
"/world": { fs: "LocalStorage" },
|
||||
'/world': { fs: 'LocalStorage' },
|
||||
'/userData': { fs: 'IndexedDB' },
|
||||
}
|
||||
browserfs.configure({
|
||||
|
|
@ -154,8 +154,8 @@ export const openWorldDirectory = async (dragndropHandle?: FileSystemDirectoryHa
|
|||
// todo
|
||||
fs: 'MountableFileSystem',
|
||||
options: {
|
||||
"/world": {
|
||||
fs: "FileSystemAccess",
|
||||
'/world': {
|
||||
fs: 'FileSystemAccess',
|
||||
options: {
|
||||
handle: directoryHandle
|
||||
}
|
||||
|
|
@ -215,8 +215,8 @@ const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name'])
|
|||
fs: 'MountableFileSystem',
|
||||
options: {
|
||||
...deafultMountablePoints,
|
||||
"/world": {
|
||||
fs: "ZipFS",
|
||||
'/world': {
|
||||
fs: 'ZipFS',
|
||||
options: {
|
||||
zipData: Buffer.from(file instanceof File ? (await file.arrayBuffer()) : file),
|
||||
name
|
||||
|
|
@ -276,12 +276,12 @@ export async function generateAndDownloadWorldZip() {
|
|||
zip.folder('world')
|
||||
|
||||
// Generate the ZIP archive content
|
||||
const zipContent = await zip.generateAsync({ type: "blob" })
|
||||
const zipContent = await zip.generateAsync({ type: 'blob' })
|
||||
|
||||
// Create a download link and trigger the download
|
||||
const downloadLink = document.createElement("a")
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.href = URL.createObjectURL(zipContent)
|
||||
downloadLink.download = "prismarine-world.zip"
|
||||
downloadLink.download = 'prismarine-world.zip'
|
||||
downloadLink.click()
|
||||
|
||||
// Clean up the URL object after download
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ const exportWorld = async () => {
|
|||
await addFolderToZip(worldFolder, zip, '')
|
||||
|
||||
// Generate the ZIP archive content
|
||||
const zipContent = await zip.generateAsync({ type: "blob" })
|
||||
const zipContent = await zip.generateAsync({ type: 'blob' })
|
||||
|
||||
// Create a download link and trigger the download
|
||||
const downloadLink = document.createElement("a")
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.href = URL.createObjectURL(zipContent)
|
||||
downloadLink.download = "world-exported.zip"
|
||||
downloadLink.download = 'world-exported.zip'
|
||||
downloadLink.click()
|
||||
|
||||
// Clean up the URL object after download
|
||||
|
|
|
|||
|
|
@ -139,22 +139,22 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => {
|
|||
// handle general commands
|
||||
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
||||
switch (command) {
|
||||
case 'general.jump':
|
||||
bot.setControlState('jump', pressed)
|
||||
break
|
||||
case 'general.sneak':
|
||||
gameAdditionalState.isSneaking = pressed
|
||||
bot.setControlState('sneak', pressed)
|
||||
break
|
||||
case 'general.sprint':
|
||||
case 'general.jump':
|
||||
bot.setControlState('jump', pressed)
|
||||
break
|
||||
case 'general.sneak':
|
||||
gameAdditionalState.isSneaking = pressed
|
||||
bot.setControlState('sneak', pressed)
|
||||
break
|
||||
case 'general.sprint':
|
||||
// todo add setting to change behavior
|
||||
if (pressed) {
|
||||
setSprinting(pressed)
|
||||
}
|
||||
break
|
||||
case 'general.attackDestroy':
|
||||
document.dispatchEvent(new MouseEvent(pressed ? 'mousedown' : 'mouseup', { button: 0 }))
|
||||
break
|
||||
if (pressed) {
|
||||
setSprinting(pressed)
|
||||
}
|
||||
break
|
||||
case 'general.attackDestroy':
|
||||
document.dispatchEvent(new MouseEvent(pressed ? 'mousedown' : 'mouseup', { button: 0 }))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,26 +192,26 @@ contro.on('trigger', ({ command }) => {
|
|||
if (stringStartsWith(command, 'general')) {
|
||||
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
||||
switch (command) {
|
||||
case 'general.inventory':
|
||||
document.exitPointerLock?.()
|
||||
showModal({ reactType: 'inventory' })
|
||||
break
|
||||
case 'general.drop':
|
||||
if (bot.heldItem) bot.tossStack(bot.heldItem)
|
||||
break
|
||||
case 'general.chat':
|
||||
document.getElementById('hud').shadowRoot.getElementById('chat').enableChat()
|
||||
break
|
||||
case 'general.command':
|
||||
document.getElementById('hud').shadowRoot.getElementById('chat').enableChat('/')
|
||||
break
|
||||
case 'general.interactPlace':
|
||||
document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
|
||||
setTimeout(() => {
|
||||
case 'general.inventory':
|
||||
document.exitPointerLock?.()
|
||||
showModal({ reactType: 'inventory' })
|
||||
break
|
||||
case 'general.drop':
|
||||
if (bot.heldItem) bot.tossStack(bot.heldItem)
|
||||
break
|
||||
case 'general.chat':
|
||||
document.getElementById('hud').shadowRoot.getElementById('chat').enableChat()
|
||||
break
|
||||
case 'general.command':
|
||||
document.getElementById('hud').shadowRoot.getElementById('chat').enableChat('/')
|
||||
break
|
||||
case 'general.interactPlace':
|
||||
document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
|
||||
setTimeout(() => {
|
||||
// todo cleanup
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
||||
})
|
||||
break
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@ import { openWorldDirectory, openWorldZip } from './browserfs'
|
|||
import { isGameActive } from './globalState'
|
||||
|
||||
const parseNbt = promisify(nbt.parse)
|
||||
window.nbt = nbt;
|
||||
window.nbt = nbt
|
||||
|
||||
// todo display drop zone
|
||||
for (const event of ["drag", "dragstart", "dragend", "dragover", "dragenter", "dragleave", "drop"]) {
|
||||
for (const event of ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop']) {
|
||||
window.addEventListener(event, (e: any) => {
|
||||
if (e.dataTransfer && !e.dataTransfer.types.includes("Files")) {
|
||||
if (e.dataTransfer && !e.dataTransfer.types.includes('Files')) {
|
||||
// e.dataTransfer.effectAllowed = "none"
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
})
|
||||
}
|
||||
window.addEventListener("drop", async e => {
|
||||
window.addEventListener('drop', async e => {
|
||||
if (!e.dataTransfer?.files.length) return
|
||||
const { items } = e.dataTransfer
|
||||
const item = items[0]
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ const getItemSlice = (name) => {
|
|||
|
||||
const getImageSrc = (path) => {
|
||||
switch (path) {
|
||||
case 'gui/container/inventory': return InventoryGui
|
||||
case 'blocks': return globalThis.texturePackDataUrl || `textures/${version}.png`
|
||||
case 'invsprite': return `invsprite.png`
|
||||
case 'gui/container/inventory': return InventoryGui
|
||||
case 'blocks': return globalThis.texturePackDataUrl || `textures/${version}.png`
|
||||
case 'invsprite': return `invsprite.png`
|
||||
}
|
||||
return Dirt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export const loadSave = async (root = '/world') => {
|
|||
|
||||
if (!fsState.isReadonly) {
|
||||
// todo allow also to ctrl+s
|
||||
alert("Note: the world is saved only on /save or disconnect! ENSURE YOU HAVE BACKUP!")
|
||||
alert('Note: the world is saved only on /save or disconnect! ENSURE YOU HAVE BACKUP!')
|
||||
}
|
||||
|
||||
fsState.saveLoaded = true
|
||||
|
|
|
|||
|
|
@ -5,146 +5,146 @@ import { resolveTimeout, setLoadingScreenStatus } from './utils'
|
|||
import { miscUiState } from './globalState'
|
||||
|
||||
class CustomDuplex extends Duplex {
|
||||
constructor(options, public writeAction) {
|
||||
super(options)
|
||||
}
|
||||
constructor(options, public writeAction) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
_read() { }
|
||||
_read() { }
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
this.writeAction(chunk)
|
||||
callback()
|
||||
}
|
||||
_write(chunk, encoding, callback) {
|
||||
this.writeAction(chunk)
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
let peerInstance: Peer | undefined
|
||||
|
||||
export const getJoinLink = () => {
|
||||
if (!peerInstance) return
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set('connectPeer', peerInstance.id)
|
||||
url.searchParams.set('peerVersion', localServer.options.version)
|
||||
return url.toString()
|
||||
if (!peerInstance) return
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set('connectPeer', peerInstance.id)
|
||||
url.searchParams.set('peerVersion', localServer.options.version)
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
const copyJoinLink = async () => {
|
||||
miscUiState.wanOpened = true
|
||||
const joinLink = getJoinLink()
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(joinLink)
|
||||
} else {
|
||||
window.prompt('Copy to clipboard: Ctrl+C, Enter', joinLink)
|
||||
}
|
||||
miscUiState.wanOpened = true
|
||||
const joinLink = getJoinLink()
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(joinLink)
|
||||
} else {
|
||||
window.prompt('Copy to clipboard: Ctrl+C, Enter', joinLink)
|
||||
}
|
||||
}
|
||||
|
||||
export const openToWanAndCopyJoinLink = async (writeText: (text) => void, doCopy = true) => {
|
||||
if (!localServer) return
|
||||
if (peerInstance) {
|
||||
if (doCopy) await copyJoinLink()
|
||||
return 'Already opened to wan. Join link copied'
|
||||
if (!localServer) return
|
||||
if (peerInstance) {
|
||||
if (doCopy) await copyJoinLink()
|
||||
return 'Already opened to wan. Join link copied'
|
||||
}
|
||||
const peer = new Peer({
|
||||
debug: 3,
|
||||
})
|
||||
peerInstance = peer
|
||||
peer.on('connection', (connection) => {
|
||||
console.log('connection')
|
||||
const serverDuplex = new CustomDuplex({}, (data) => connection.send(data))
|
||||
const client = new Client(true, localServer.options.version, undefined)
|
||||
client.setSocket(serverDuplex)
|
||||
localServer._server.emit('connection', client)
|
||||
|
||||
connection.on('data', (data: any) => {
|
||||
serverDuplex.push(Buffer.from(data))
|
||||
})
|
||||
// our side disconnect
|
||||
const endConnection = () => {
|
||||
console.log('connection.close')
|
||||
serverDuplex.end()
|
||||
connection.close()
|
||||
}
|
||||
const peer = new Peer({
|
||||
debug: 3,
|
||||
})
|
||||
peerInstance = peer
|
||||
peer.on('connection', (connection) => {
|
||||
console.log('connection')
|
||||
const serverDuplex = new CustomDuplex({}, (data) => connection.send(data))
|
||||
const client = new Client(true, localServer.options.version, undefined)
|
||||
client.setSocket(serverDuplex)
|
||||
localServer._server.emit('connection', client)
|
||||
serverDuplex.on('end', endConnection)
|
||||
serverDuplex.on('force-close', endConnection)
|
||||
client.on('end', endConnection)
|
||||
|
||||
connection.on('data', (data: any) => {
|
||||
serverDuplex.push(Buffer.from(data))
|
||||
})
|
||||
// our side disconnect
|
||||
const endConnection = () => {
|
||||
console.log('connection.close')
|
||||
serverDuplex.end()
|
||||
connection.close()
|
||||
}
|
||||
serverDuplex.on('end', endConnection)
|
||||
serverDuplex.on('force-close', endConnection)
|
||||
client.on('end', endConnection)
|
||||
|
||||
const disconnected = () => {
|
||||
serverDuplex.end()
|
||||
client.end()
|
||||
}
|
||||
connection.on('iceStateChanged', (state) => {
|
||||
console.log('iceStateChanged', state)
|
||||
if (state === 'disconnected') {
|
||||
disconnected()
|
||||
}
|
||||
})
|
||||
connection.on('close', disconnected)
|
||||
connection.on('error', disconnected)
|
||||
const disconnected = () => {
|
||||
serverDuplex.end()
|
||||
client.end()
|
||||
}
|
||||
connection.on('iceStateChanged', (state) => {
|
||||
console.log('iceStateChanged', state)
|
||||
if (state === 'disconnected') {
|
||||
disconnected()
|
||||
}
|
||||
})
|
||||
peer.on('error', (error) => {
|
||||
console.error(error)
|
||||
writeText(error.message)
|
||||
})
|
||||
return new Promise<string>(resolve => {
|
||||
peer.on('open', async () => {
|
||||
await copyJoinLink()
|
||||
resolve('Copied join link to clipboard')
|
||||
})
|
||||
setTimeout(() => {
|
||||
resolve('Failed to open to wan (timeout)')
|
||||
}, 5000)
|
||||
connection.on('close', disconnected)
|
||||
connection.on('error', disconnected)
|
||||
})
|
||||
peer.on('error', (error) => {
|
||||
console.error(error)
|
||||
writeText(error.message)
|
||||
})
|
||||
return new Promise<string>(resolve => {
|
||||
peer.on('open', async () => {
|
||||
await copyJoinLink()
|
||||
resolve('Copied join link to clipboard')
|
||||
})
|
||||
setTimeout(() => {
|
||||
resolve('Failed to open to wan (timeout)')
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
|
||||
export const closeWan = () => {
|
||||
if (!peerInstance) return
|
||||
peerInstance.destroy()
|
||||
peerInstance = undefined
|
||||
miscUiState.wanOpened = false
|
||||
return 'Closed to wan'
|
||||
if (!peerInstance) return
|
||||
peerInstance.destroy()
|
||||
peerInstance = undefined
|
||||
miscUiState.wanOpened = false
|
||||
return 'Closed to wan'
|
||||
}
|
||||
|
||||
export const connectToPeer = async (peerId: string) => {
|
||||
setLoadingScreenStatus('Connecting to peer server')
|
||||
// todo destroy connection on error
|
||||
const peer = new Peer({
|
||||
debug: 3,
|
||||
setLoadingScreenStatus('Connecting to peer server')
|
||||
// todo destroy connection on error
|
||||
const peer = new Peer({
|
||||
debug: 3,
|
||||
})
|
||||
await resolveTimeout(new Promise(resolve => {
|
||||
peer.once('open', resolve)
|
||||
}))
|
||||
setLoadingScreenStatus('Connecting to the peer')
|
||||
const connection = peer.connect(peerId, {
|
||||
serialization: 'raw',
|
||||
})
|
||||
await resolveTimeout(new Promise<void>((resolve, reject) => {
|
||||
connection.once('error', (error) => {
|
||||
console.log(error.type, error.name)
|
||||
console.log(error)
|
||||
reject(error.message)
|
||||
})
|
||||
await resolveTimeout(new Promise(resolve => {
|
||||
peer.once('open', resolve)
|
||||
}))
|
||||
setLoadingScreenStatus('Connecting to the peer')
|
||||
const connection = peer.connect(peerId, {
|
||||
serialization: 'raw',
|
||||
})
|
||||
await resolveTimeout(new Promise<void>((resolve, reject) => {
|
||||
connection.once('error', (error) => {
|
||||
console.log(error.type, error.name)
|
||||
console.log(error)
|
||||
reject(error.message);
|
||||
})
|
||||
connection.once('open', resolve)
|
||||
}))
|
||||
connection.once('open', resolve)
|
||||
}))
|
||||
|
||||
const clientDuplex = new CustomDuplex({}, (data) => {
|
||||
// todo rm debug
|
||||
console.debug('sending', data.toString())
|
||||
connection.send(data)
|
||||
})
|
||||
connection.on('data', (data: any) => {
|
||||
console.debug('received', Buffer.from(data).toString())
|
||||
clientDuplex.push(Buffer.from(data))
|
||||
})
|
||||
connection.on('close', () => {
|
||||
console.log('connection closed')
|
||||
clientDuplex.end()
|
||||
// bot._client.end()
|
||||
// bot.end()
|
||||
bot.emit('end', 'Disconnected.')
|
||||
})
|
||||
connection.on('error', (error) => {
|
||||
console.error(error)
|
||||
clientDuplex.end()
|
||||
})
|
||||
const clientDuplex = new CustomDuplex({}, (data) => {
|
||||
// todo rm debug
|
||||
console.debug('sending', data.toString())
|
||||
connection.send(data)
|
||||
})
|
||||
connection.on('data', (data: any) => {
|
||||
console.debug('received', Buffer.from(data).toString())
|
||||
clientDuplex.push(Buffer.from(data))
|
||||
})
|
||||
connection.on('close', () => {
|
||||
console.log('connection closed')
|
||||
clientDuplex.end()
|
||||
// bot._client.end()
|
||||
// bot.end()
|
||||
bot.emit('end', 'Disconnected.')
|
||||
})
|
||||
connection.on('error', (error) => {
|
||||
console.error(error)
|
||||
clientDuplex.end()
|
||||
})
|
||||
|
||||
return clientDuplex
|
||||
return clientDuplex
|
||||
}
|
||||
|
|
|
|||
142
src/reactUi.jsx
142
src/reactUi.jsx
|
|
@ -13,49 +13,49 @@ import { options, watchValue } from './optionsStorage'
|
|||
|
||||
// todo
|
||||
useInterfaceState.setState({
|
||||
isFlying: false,
|
||||
uiCustomization: {
|
||||
touchButtonSize: 40,
|
||||
},
|
||||
updateCoord([coord, state]) {
|
||||
const coordToAction = [
|
||||
['z', -1, 'KeyW'],
|
||||
['z', 1, 'KeyS'],
|
||||
['x', -1, 'KeyA'],
|
||||
['x', 1, 'KeyD'],
|
||||
['y', 1, 'Space'], // todo jump
|
||||
['y', -1, 'ShiftLeft'], // todo jump
|
||||
]
|
||||
// todo refactor
|
||||
const actionAndState = state === 0 ? coordToAction.filter(([axis]) => axis === coord) : coordToAction.find(([axis, value]) => axis === coord && value === state)
|
||||
if (!bot) return
|
||||
if (state === 0) {
|
||||
for (const action of actionAndState) {
|
||||
contro.pressedKeyOrButtonChanged({code: action[2],}, false)
|
||||
}
|
||||
} else {
|
||||
//@ts-expect-error
|
||||
contro.pressedKeyOrButtonChanged({code: actionAndState[2],}, true)
|
||||
}
|
||||
isFlying: false,
|
||||
uiCustomization: {
|
||||
touchButtonSize: 40,
|
||||
},
|
||||
updateCoord([coord, state]) {
|
||||
const coordToAction = [
|
||||
['z', -1, 'KeyW'],
|
||||
['z', 1, 'KeyS'],
|
||||
['x', -1, 'KeyA'],
|
||||
['x', 1, 'KeyD'],
|
||||
['y', 1, 'Space'], // todo jump
|
||||
['y', -1, 'ShiftLeft'], // todo jump
|
||||
]
|
||||
// todo refactor
|
||||
const actionAndState = state === 0 ? coordToAction.filter(([axis]) => axis === coord) : coordToAction.find(([axis, value]) => axis === coord && value === state)
|
||||
if (!bot) return
|
||||
if (state === 0) {
|
||||
for (const action of actionAndState) {
|
||||
contro.pressedKeyOrButtonChanged({code: action[2],}, false)
|
||||
}
|
||||
} else {
|
||||
//@ts-expect-error
|
||||
contro.pressedKeyOrButtonChanged({code: actionAndState[2],}, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watchValue(options, (o) => {
|
||||
useInterfaceState.setState({
|
||||
uiCustomization: {
|
||||
touchButtonSize: o.touchButtonsSize,
|
||||
},
|
||||
})
|
||||
useInterfaceState.setState({
|
||||
uiCustomization: {
|
||||
touchButtonSize: o.touchButtonsSize,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const TouchControls = () => {
|
||||
// todo setting
|
||||
const usingTouch = useUsingTouch()
|
||||
// todo setting
|
||||
const usingTouch = useUsingTouch()
|
||||
|
||||
if (!usingTouch) return null
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
if (!usingTouch) return null
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
height: 100%;
|
||||
|
|
@ -69,57 +69,57 @@ const TouchControls = () => {
|
|||
pointer-events: auto;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<LeftTouchArea />
|
||||
<div />
|
||||
<RightTouchArea />
|
||||
</div>
|
||||
)
|
||||
>
|
||||
<LeftTouchArea />
|
||||
<div />
|
||||
<RightTouchArea />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function useIsBotAvailable() {
|
||||
const stack = useSnapshot(activeModalStack)
|
||||
const stack = useSnapshot(activeModalStack)
|
||||
|
||||
return isGameActive(false)
|
||||
return isGameActive(false)
|
||||
}
|
||||
|
||||
const DisplayQr = () => {
|
||||
const { currentDisplayQr } = useSnapshot(miscUiState)
|
||||
const { currentDisplayQr } = useSnapshot(miscUiState)
|
||||
|
||||
if (!currentDisplayQr) return null
|
||||
if (!currentDisplayQr) return null
|
||||
|
||||
return createPortal(<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 15
|
||||
}}
|
||||
onClick={() => {
|
||||
miscUiState.currentDisplayQr = null
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG size={384} value={currentDisplayQr} style={{display: 'block', border: '2px solid black',}} />
|
||||
</div>, document.body)
|
||||
return createPortal(<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 15
|
||||
}}
|
||||
onClick={() => {
|
||||
miscUiState.currentDisplayQr = null
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG size={384} value={currentDisplayQr} style={{display: 'block', border: '2px solid black',}} />
|
||||
</div>, document.body)
|
||||
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const isBotAvailable = useIsBotAvailable()
|
||||
if (!isBotAvailable) return null
|
||||
const isBotAvailable = useIsBotAvailable()
|
||||
if (!isBotAvailable) return null
|
||||
|
||||
return <div>
|
||||
<DisplayQr />
|
||||
<TouchControls />
|
||||
</div>
|
||||
return <div>
|
||||
<DisplayQr />
|
||||
<TouchControls />
|
||||
</div>
|
||||
}
|
||||
|
||||
renderToDom(<App />, {
|
||||
strictMode: false,
|
||||
selector: '#react-root',
|
||||
strictMode: false,
|
||||
selector: '#react-root',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,262 +11,262 @@ import { removeFileRecursiveAsync } from './browserfs'
|
|||
import { setLoadingScreenStatus } from './utils'
|
||||
|
||||
export const resourcePackState = proxy({
|
||||
resourcePackInstalled: false,
|
||||
currentTexturesDataUrl: undefined as string | undefined,
|
||||
currentTexturesBlockStates: undefined as BlockStates | undefined,
|
||||
resourcePackInstalled: false,
|
||||
currentTexturesDataUrl: undefined as string | undefined,
|
||||
currentTexturesBlockStates: undefined as BlockStates | undefined,
|
||||
})
|
||||
|
||||
function nextPowerOfTwo(n) {
|
||||
if (n === 0) return 1
|
||||
n--
|
||||
n |= n >> 1
|
||||
n |= n >> 2
|
||||
n |= n >> 4
|
||||
n |= n >> 8
|
||||
n |= n >> 16
|
||||
return n + 1
|
||||
if (n === 0) return 1
|
||||
n--
|
||||
n |= n >> 1
|
||||
n |= n >> 2
|
||||
n |= n >> 4
|
||||
n |= n >> 8
|
||||
n |= n >> 16
|
||||
return n + 1
|
||||
}
|
||||
|
||||
const mkdirRecursive = async (path) => {
|
||||
const parts = path.split('/')
|
||||
let current = ''
|
||||
for (const part of parts) {
|
||||
current += part + '/'
|
||||
try {
|
||||
await fs.promises.mkdir(current)
|
||||
} catch (err) {
|
||||
}
|
||||
const parts = path.split('/')
|
||||
let current = ''
|
||||
for (const part of parts) {
|
||||
current += part + '/'
|
||||
try {
|
||||
await fs.promises.mkdir(current)
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const texturePackBasePath = '/userData/resourcePacks/default'
|
||||
export const uninstallTexturePack = async () => {
|
||||
await removeFileRecursiveAsync(texturePackBasePath)
|
||||
setCustomTexturePackData(undefined, undefined)
|
||||
await removeFileRecursiveAsync(texturePackBasePath)
|
||||
setCustomTexturePackData(undefined, undefined)
|
||||
}
|
||||
|
||||
export const getResourcePackName = async () => {
|
||||
// temp
|
||||
try {
|
||||
return await fs.promises.readFile(join(texturePackBasePath, 'name.txt'), 'utf8')
|
||||
} catch (err) {
|
||||
return '???'
|
||||
}
|
||||
// temp
|
||||
try {
|
||||
return await fs.promises.readFile(join(texturePackBasePath, 'name.txt'), 'utf8')
|
||||
} catch (err) {
|
||||
return '???'
|
||||
}
|
||||
}
|
||||
|
||||
export const fromTexturePackPath = (path) => {
|
||||
return join(texturePackBasePath, path)
|
||||
return join(texturePackBasePath, path)
|
||||
}
|
||||
|
||||
export const updateTexturePackInstalledState = async () => {
|
||||
try {
|
||||
resourcePackState.resourcePackInstalled = await existsAsync(texturePackBasePath)
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
resourcePackState.resourcePackInstalled = await existsAsync(texturePackBasePath)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
export const installTexturePack = async (file: File | ArrayBuffer) => {
|
||||
try {
|
||||
await uninstallTexturePack()
|
||||
} catch (err) {
|
||||
}
|
||||
const status = 'Installing resource pack: copying all files'
|
||||
setLoadingScreenStatus(status)
|
||||
// extract the zip and write to fs every file in it
|
||||
const zip = new JSZip()
|
||||
const zipFile = await zip.loadAsync(file)
|
||||
if (!zipFile.file('pack.mcmeta')) throw new Error('Not a resource pack: missing pack.mcmeta')
|
||||
await mkdirRecursive(texturePackBasePath)
|
||||
try {
|
||||
await uninstallTexturePack()
|
||||
} catch (err) {
|
||||
}
|
||||
const status = 'Installing resource pack: copying all files'
|
||||
setLoadingScreenStatus(status)
|
||||
// extract the zip and write to fs every file in it
|
||||
const zip = new JSZip()
|
||||
const zipFile = await zip.loadAsync(file)
|
||||
if (!zipFile.file('pack.mcmeta')) throw new Error('Not a resource pack: missing pack.mcmeta')
|
||||
await mkdirRecursive(texturePackBasePath)
|
||||
|
||||
const allFilesArr = Object.entries(zipFile.files)
|
||||
let done = 0
|
||||
const upStatus = () => {
|
||||
setLoadingScreenStatus(`${status} ${Math.round(++done / allFilesArr.length * 100)}%`)
|
||||
}
|
||||
await Promise.all(allFilesArr.map(async ([path, file]) => {
|
||||
const writePath = join(texturePackBasePath, path)
|
||||
if (path.endsWith('/')) return
|
||||
await mkdirRecursive(dirname(writePath))
|
||||
await fs.promises.writeFile(writePath, Buffer.from(await file.async('arraybuffer')))
|
||||
done++
|
||||
upStatus()
|
||||
}))
|
||||
await fs.promises.writeFile(join(texturePackBasePath, 'name.txt'), file['name'] ?? '??', 'utf8')
|
||||
const allFilesArr = Object.entries(zipFile.files)
|
||||
let done = 0
|
||||
const upStatus = () => {
|
||||
setLoadingScreenStatus(`${status} ${Math.round(++done / allFilesArr.length * 100)}%`)
|
||||
}
|
||||
await Promise.all(allFilesArr.map(async ([path, file]) => {
|
||||
const writePath = join(texturePackBasePath, path)
|
||||
if (path.endsWith('/')) return
|
||||
await mkdirRecursive(dirname(writePath))
|
||||
await fs.promises.writeFile(writePath, Buffer.from(await file.async('arraybuffer')))
|
||||
done++
|
||||
upStatus()
|
||||
}))
|
||||
await fs.promises.writeFile(join(texturePackBasePath, 'name.txt'), file['name'] ?? '??', 'utf8')
|
||||
|
||||
if (viewer?.world.active) {
|
||||
await genTexturePackTextures(viewer.version)
|
||||
}
|
||||
setLoadingScreenStatus(undefined)
|
||||
showNotification({
|
||||
message: 'Texturepack installed!',
|
||||
})
|
||||
if (viewer?.world.active) {
|
||||
await genTexturePackTextures(viewer.version)
|
||||
}
|
||||
setLoadingScreenStatus(undefined)
|
||||
showNotification({
|
||||
message: 'Texturepack installed!',
|
||||
})
|
||||
}
|
||||
|
||||
const existsAsync = async (path) => {
|
||||
try {
|
||||
await fs.promises.stat(path)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
await fs.promises.stat(path)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type TextureResolvedData = {
|
||||
blockSize: number
|
||||
// itemsUrlContent: string
|
||||
blockSize: number
|
||||
// itemsUrlContent: string
|
||||
}
|
||||
|
||||
const arrEqual = (a: any[], b: any[]) => a.length === b.length && a.every((x) => b.includes(x))
|
||||
|
||||
const applyTexturePackData = async (version: string, { blockSize }: TextureResolvedData, blocksUrlContent: string) => {
|
||||
const result = await fetch(`blocksStates/${version}.json`)
|
||||
const blockStates: BlockStates = await result.json()
|
||||
const factor = blockSize / 16
|
||||
const result = await fetch(`blocksStates/${version}.json`)
|
||||
const blockStates: BlockStates = await result.json()
|
||||
const factor = blockSize / 16
|
||||
|
||||
// this will be refactored with generateTextures refactor
|
||||
const processObj = (x) => {
|
||||
if (typeof x !== 'object' || !x) return
|
||||
if (Array.isArray(x)) {
|
||||
for (const v of x) {
|
||||
processObj(v)
|
||||
}
|
||||
// this will be refactored with generateTextures refactor
|
||||
const processObj = (x) => {
|
||||
if (typeof x !== 'object' || !x) return
|
||||
if (Array.isArray(x)) {
|
||||
for (const v of x) {
|
||||
processObj(v)
|
||||
}
|
||||
|
||||
} else {
|
||||
const actual = Object.keys(x)
|
||||
const needed = ['u', 'v', 'su', 'sv']
|
||||
} else {
|
||||
const actual = Object.keys(x)
|
||||
const needed = ['u', 'v', 'su', 'sv']
|
||||
|
||||
if (!arrEqual(actual, needed)) {
|
||||
for (const v of Object.values(x)) {
|
||||
processObj(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
for (const k of needed) {
|
||||
x[k] *= factor
|
||||
}
|
||||
if (!arrEqual(actual, needed)) {
|
||||
for (const v of Object.values(x)) {
|
||||
processObj(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
for (const k of needed) {
|
||||
x[k] *= factor
|
||||
}
|
||||
}
|
||||
processObj(blockStates)
|
||||
setCustomTexturePackData(blocksUrlContent, blockStates)
|
||||
}
|
||||
processObj(blockStates)
|
||||
setCustomTexturePackData(blocksUrlContent, blockStates)
|
||||
}
|
||||
|
||||
const setCustomTexturePackData = (blockTextures, blockStates) => {
|
||||
resourcePackState.currentTexturesBlockStates = blockStates && ref(blockStates)
|
||||
resourcePackState.currentTexturesDataUrl = blockTextures
|
||||
resourcePackState.resourcePackInstalled = blockTextures !== undefined
|
||||
resourcePackState.currentTexturesBlockStates = blockStates && ref(blockStates)
|
||||
resourcePackState.currentTexturesDataUrl = blockTextures
|
||||
resourcePackState.resourcePackInstalled = blockTextures !== undefined
|
||||
}
|
||||
|
||||
const getSizeFromImage = async (filePath: string) => {
|
||||
const probeImg = new Image()
|
||||
const file = await fs.promises.readFile(filePath, 'base64')
|
||||
probeImg.src = `data:image/png;base64,${file}`
|
||||
await new Promise((resolve, reject) => {
|
||||
probeImg.addEventListener('load', resolve)
|
||||
})
|
||||
if (probeImg.width !== probeImg.height) throw new Error(`Probe texture ${filePath} is not square`)
|
||||
return probeImg.width
|
||||
const probeImg = new Image()
|
||||
const file = await fs.promises.readFile(filePath, 'base64')
|
||||
probeImg.src = `data:image/png;base64,${file}`
|
||||
await new Promise((resolve, reject) => {
|
||||
probeImg.addEventListener('load', resolve)
|
||||
})
|
||||
if (probeImg.width !== probeImg.height) throw new Error(`Probe texture ${filePath} is not square`)
|
||||
return probeImg.width
|
||||
}
|
||||
|
||||
export const genTexturePackTextures = async (version: string) => {
|
||||
setCustomTexturePackData(undefined, undefined)
|
||||
let blocksBasePath = '/userData/resourcePacks/default/assets/minecraft/textures/block'
|
||||
// todo not clear why this is needed
|
||||
const blocksBasePathAlt = '/userData/resourcePacks/default/assets/minecraft/textures/blocks'
|
||||
const blocksGenereatedPath = `/userData/resourcePacks/default/${version}.png`
|
||||
const genereatedPathData = `/userData/resourcePacks/default/${version}.json`
|
||||
if (!(await existsAsync(blocksBasePath))) {
|
||||
if (await existsAsync(blocksBasePathAlt)) {
|
||||
blocksBasePath = blocksBasePathAlt
|
||||
} else {
|
||||
return
|
||||
setCustomTexturePackData(undefined, undefined)
|
||||
let blocksBasePath = '/userData/resourcePacks/default/assets/minecraft/textures/block'
|
||||
// todo not clear why this is needed
|
||||
const blocksBasePathAlt = '/userData/resourcePacks/default/assets/minecraft/textures/blocks'
|
||||
const blocksGenereatedPath = `/userData/resourcePacks/default/${version}.png`
|
||||
const genereatedPathData = `/userData/resourcePacks/default/${version}.json`
|
||||
if (!(await existsAsync(blocksBasePath))) {
|
||||
if (await existsAsync(blocksBasePathAlt)) {
|
||||
blocksBasePath = blocksBasePathAlt
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (await existsAsync(blocksGenereatedPath)) {
|
||||
applyTexturePackData(version, JSON.parse(await fs.promises.readFile(genereatedPathData, 'utf8')), await fs.promises.readFile(blocksGenereatedPath, 'utf8'))
|
||||
return
|
||||
}
|
||||
|
||||
setLoadingScreenStatus('Generating custom textures')
|
||||
|
||||
const textureFiles = blocksFileNames.indexes[version].map(k => blocksFileNames.blockNames[k])
|
||||
textureFiles.unshift('missing_texture.png')
|
||||
|
||||
const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length)))
|
||||
const originalTileSize = 16
|
||||
|
||||
const firstBlockFile = (await fs.promises.readdir(blocksBasePath)).find(f => f.endsWith('.png'))
|
||||
if (!firstBlockFile) {
|
||||
return
|
||||
}
|
||||
|
||||
// we get the size of image from the first block file, which is not ideal but works in 99% cases
|
||||
const tileSize = await getSizeFromImage(join(blocksBasePath, firstBlockFile))
|
||||
|
||||
const imgSize = texSize * tileSize
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = imgSize
|
||||
canvas.height = imgSize
|
||||
const src = `textures/${version}.png`
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.imageSmoothingEnabled = false
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onerror = reject
|
||||
img.addEventListener('load', resolve)
|
||||
})
|
||||
for (const [i, fileName] of textureFiles.entries()) {
|
||||
const x = (i % texSize) * tileSize
|
||||
const y = Math.floor(i / texSize) * tileSize
|
||||
const xOrig = (i % texSize) * originalTileSize
|
||||
const yOrig = Math.floor(i / texSize) * originalTileSize
|
||||
let imgCustom: HTMLImageElement
|
||||
try {
|
||||
const fileBase64 = await fs.promises.readFile(join(blocksBasePath, fileName), 'base64')
|
||||
const _imgCustom = new Image()
|
||||
await new Promise<void>(resolve => {
|
||||
_imgCustom.addEventListener('load', () => {
|
||||
imgCustom = _imgCustom
|
||||
resolve()
|
||||
})
|
||||
_imgCustom.onerror = () => {
|
||||
console.log('Skipping issued texture', fileName)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
if (await existsAsync(blocksGenereatedPath)) {
|
||||
applyTexturePackData(version, JSON.parse(await fs.promises.readFile(genereatedPathData, 'utf8')), await fs.promises.readFile(blocksGenereatedPath, 'utf8'))
|
||||
return
|
||||
_imgCustom.src = `data:image/png;base64,${fileBase64}`
|
||||
})
|
||||
} catch {
|
||||
console.log('Skipping not found texture', fileName)
|
||||
}
|
||||
|
||||
setLoadingScreenStatus('Generating custom textures')
|
||||
|
||||
const textureFiles = blocksFileNames.indexes[version].map(k => blocksFileNames.blockNames[k])
|
||||
textureFiles.unshift('missing_texture.png')
|
||||
|
||||
const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length)))
|
||||
const originalTileSize = 16
|
||||
|
||||
const firstBlockFile = (await fs.promises.readdir(blocksBasePath)).find(f => f.endsWith('.png'))
|
||||
if (!firstBlockFile) {
|
||||
return
|
||||
if (imgCustom) {
|
||||
ctx.drawImage(imgCustom, x, y, tileSize, tileSize)
|
||||
} else {
|
||||
// todo this involves incorrect mappings for existing textures when the size is different
|
||||
ctx.drawImage(img, xOrig, yOrig, originalTileSize, originalTileSize, x, y, tileSize, tileSize)
|
||||
}
|
||||
}
|
||||
const blockDataUrl = canvas.toDataURL('image/png')
|
||||
const newData: TextureResolvedData = {
|
||||
blockSize: tileSize,
|
||||
}
|
||||
await fs.promises.writeFile(genereatedPathData, JSON.stringify(newData), 'utf8')
|
||||
await fs.promises.writeFile(blocksGenereatedPath, blockDataUrl, 'utf8')
|
||||
await applyTexturePackData(version, newData, blockDataUrl)
|
||||
|
||||
// we get the size of image from the first block file, which is not ideal but works in 99% cases
|
||||
const tileSize = await getSizeFromImage(join(blocksBasePath, firstBlockFile))
|
||||
|
||||
const imgSize = texSize * tileSize
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = imgSize
|
||||
canvas.height = imgSize
|
||||
const src = `textures/${version}.png`
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.imageSmoothingEnabled = false
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onerror = reject
|
||||
img.addEventListener('load', resolve)
|
||||
})
|
||||
for (const [i, fileName] of textureFiles.entries()) {
|
||||
const x = (i % texSize) * tileSize
|
||||
const y = Math.floor(i / texSize) * tileSize
|
||||
const xOrig = (i % texSize) * originalTileSize
|
||||
const yOrig = Math.floor(i / texSize) * originalTileSize
|
||||
let imgCustom: HTMLImageElement
|
||||
try {
|
||||
const fileBase64 = await fs.promises.readFile(join(blocksBasePath, fileName), 'base64')
|
||||
const _imgCustom = new Image()
|
||||
await new Promise<void>(resolve => {
|
||||
_imgCustom.addEventListener('load', () => {
|
||||
imgCustom = _imgCustom
|
||||
resolve()
|
||||
})
|
||||
_imgCustom.onerror = () => {
|
||||
console.log('Skipping issued texture', fileName)
|
||||
resolve()
|
||||
}
|
||||
_imgCustom.src = `data:image/png;base64,${fileBase64}`
|
||||
})
|
||||
} catch {
|
||||
console.log('Skipping not found texture', fileName)
|
||||
}
|
||||
|
||||
if (imgCustom) {
|
||||
ctx.drawImage(imgCustom, x, y, tileSize, tileSize)
|
||||
} else {
|
||||
// todo this involves incorrect mappings for existing textures when the size is different
|
||||
ctx.drawImage(img, xOrig, yOrig, originalTileSize, originalTileSize, x, y, tileSize, tileSize)
|
||||
}
|
||||
}
|
||||
const blockDataUrl = canvas.toDataURL('image/png')
|
||||
const newData: TextureResolvedData = {
|
||||
blockSize: tileSize,
|
||||
}
|
||||
await fs.promises.writeFile(genereatedPathData, JSON.stringify(newData), 'utf8')
|
||||
await fs.promises.writeFile(blocksGenereatedPath, blockDataUrl, 'utf8')
|
||||
await applyTexturePackData(version, newData, blockDataUrl)
|
||||
|
||||
// const a = document.createElement('a')
|
||||
// a.href = dataUrl
|
||||
// a.download = 'pack.png'
|
||||
// a.click()
|
||||
// const a = document.createElement('a')
|
||||
// a.href = dataUrl
|
||||
// a.download = 'pack.png'
|
||||
// a.click()
|
||||
}
|
||||
|
||||
export const watchTexturepackInViewer = (viewer: Viewer) => {
|
||||
subscribeKey(resourcePackState, 'currentTexturesDataUrl', () => {
|
||||
console.log('applying resourcepack world data')
|
||||
viewer.world.texturesDataUrl = resourcePackState.currentTexturesDataUrl
|
||||
viewer.world.blockStatesData = resourcePackState.currentTexturesBlockStates
|
||||
if (!viewer?.world.active) return
|
||||
viewer.world.updateTexturesData()
|
||||
})
|
||||
subscribeKey(resourcePackState, 'currentTexturesDataUrl', () => {
|
||||
console.log('applying resourcepack world data')
|
||||
viewer.world.texturesDataUrl = resourcePackState.currentTexturesDataUrl
|
||||
viewer.world.blockStatesData = resourcePackState.currentTexturesBlockStates
|
||||
if (!viewer?.world.active) return
|
||||
viewer.world.updateTexturesData()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
12
src/utils.ts
12
src/utils.ts
|
|
@ -48,7 +48,7 @@ export const pointerLock = {
|
|||
unadjustedMovement: options.mouseRawInput
|
||||
})
|
||||
promise?.catch((error) => {
|
||||
if (error.name === "NotSupportedError") {
|
||||
if (error.name === 'NotSupportedError') {
|
||||
// Some platforms may not support unadjusted movement, request again a regular pointer lock.
|
||||
document.documentElement.requestPointerLock()
|
||||
} else if (error.name === 'SecurityError') {
|
||||
|
|
@ -106,11 +106,11 @@ export async function getScreenRefreshRate(): Promise<number> {
|
|||
|
||||
export const getGamemodeNumber = (bot) => {
|
||||
switch (bot.game.gameMode) {
|
||||
case 'survival': return 0
|
||||
case 'creative': return 1
|
||||
case 'adventure': return 2
|
||||
case 'spectator': return 3
|
||||
default: return -1
|
||||
case 'survival': return 0
|
||||
case 'creative': return 1
|
||||
case 'adventure': return 2
|
||||
case 'spectator': return 3
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue