many usability fixes (#24)
This commit is contained in:
commit
1935c9d173
57 changed files with 1582 additions and 3684 deletions
|
|
@ -1,15 +1,33 @@
|
|||
{
|
||||
"extends": "zardoy",
|
||||
"ignorePatterns": [
|
||||
"!*.js"
|
||||
],
|
||||
"rules": {
|
||||
"no-multi-spaces": "error",
|
||||
"space-in-parens": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"comma-spacing": "error",
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
// todo maybe "always-multiline"?
|
||||
"only-multiline"
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 2,
|
||||
"SwitchCase": 1,
|
||||
"ignoredNodes": [
|
||||
"TemplateLiteral"
|
||||
]
|
||||
|
|
@ -65,9 +83,13 @@
|
|||
"@typescript-eslint/no-require-imports": "off",
|
||||
"unicorn/prefer-number-properties": "off",
|
||||
"@typescript-eslint/no-confusing-void-expression": "off",
|
||||
"unicorn/no-empty-file": "off",
|
||||
"unicorn/prefer-event-target": "off",
|
||||
// needs to be fixed actually
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"no-async-promise-executor": "off",
|
||||
"no-bitwise": "off"
|
||||
"no-bitwise": "off",
|
||||
"unicorn/filename-case": "off",
|
||||
"max-depth": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -10,6 +10,7 @@ jobs:
|
|||
uses: actions/checkout@master
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
# todo this needs investigating fixing
|
||||
- run: pnpm install
|
||||
- run: pnpm lint
|
||||
- run: pnpm check-build
|
||||
|
|
|
|||
1
.github/workflows/preview.yml
vendored
1
.github/workflows/preview.yml
vendored
|
|
@ -20,7 +20,6 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||
- name: Install Global Dependencies
|
||||
run: npm install --global vercel pnpm
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const compareRenderedFlatWorld = () => {
|
|||
}
|
||||
|
||||
const testWorldLoad = () => {
|
||||
cy.document().then({ timeout: 20_000, }, doc => {
|
||||
cy.document().then({ timeout: 20_000 }, doc => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
doc.addEventListener('cypress-world-ready', resolve)
|
||||
})
|
||||
|
|
@ -49,7 +49,7 @@ it('Loads & renders singleplayer', () => {
|
|||
},
|
||||
renderDistance: 2
|
||||
})
|
||||
cy.get('#title-screen').find('[data-test-id="singleplayer-button"]', { includeShadowDom: true, }).click()
|
||||
cy.get('#title-screen').find('[data-test-id="singleplayer-button"]', { includeShadowDom: true }).click()
|
||||
testWorldLoad()
|
||||
})
|
||||
|
||||
|
|
@ -58,15 +58,15 @@ it('Joins to server', () => {
|
|||
window.localStorage.version = ''
|
||||
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()
|
||||
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('#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()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@ const { initPlugin } = require('cypress-plugin-snapshots/plugin')
|
|||
const polyfill = require('esbuild-plugin-polyfill-node')
|
||||
|
||||
module.exports = (on, config) => {
|
||||
initPlugin(on, config)
|
||||
on('file:preprocessor', cypressEsbuildPreprocessor({
|
||||
esbuildOptions: {
|
||||
plugins: [
|
||||
polyfill.polyfillNode({
|
||||
polyfills: {
|
||||
crypto: true,
|
||||
},
|
||||
})
|
||||
],
|
||||
},
|
||||
}))
|
||||
on('task', {
|
||||
log (message) {
|
||||
console.log(message)
|
||||
return null
|
||||
},
|
||||
})
|
||||
return config
|
||||
initPlugin(on, config)
|
||||
on('file:preprocessor', cypressEsbuildPreprocessor({
|
||||
esbuildOptions: {
|
||||
plugins: [
|
||||
polyfill.polyfillNode({
|
||||
polyfills: {
|
||||
crypto: true,
|
||||
},
|
||||
})
|
||||
],
|
||||
},
|
||||
}))
|
||||
on('task', {
|
||||
log (message) {
|
||||
console.log(message)
|
||||
return null
|
||||
},
|
||||
})
|
||||
return config
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const banner = [
|
|||
// report reload time
|
||||
dev && 'if (sessionStorage.lastReload) { const [rebuild, reloadStart] = sessionStorage.lastReload.split(","); const now = Date.now(); console.log(`rebuild + reload:`, +rebuild, "+", now - reloadStart, "=", ((+rebuild + (now - reloadStart)) / 1000).toFixed(1) + "s");sessionStorage.lastReload = ""; }',
|
||||
// auto-reload
|
||||
dev && ';(() => new EventSource("/esbuild").onmessage = ({ data: _data }) => { if (!_data) return; const data = JSON.parse(_data); if (!data.update) return; sessionStorage.lastReload = `${data.update.time},${Date.now()}`; location.reload() })();'
|
||||
dev && 'window.noAutoReload ??= false;(() => new EventSource("/esbuild").onmessage = ({ data: _data }) => { if (!_data) return; const data = JSON.parse(_data); if (!data.update) return;console.log("[esbuild] Page is outdated");document.title = `[O] ${document.title}`;if (window.noAutoReload || localStorage.noAutoReload) return; if (localStorage.autoReloadVisible && document.visibilityState !== "visible") return; sessionStorage.lastReload = `${data.update.time},${Date.now()}`; location.reload() })();'
|
||||
].filter(Boolean)
|
||||
|
||||
const buildingVersion = new Date().toISOString().split(':')[0]
|
||||
|
|
@ -35,14 +35,15 @@ const ctx = await esbuild.context({
|
|||
// logLevel: 'debug',
|
||||
logLevel: 'info',
|
||||
platform: 'browser',
|
||||
sourcemap: true,
|
||||
sourcemap: prod ? true : 'inline',
|
||||
outdir: 'dist',
|
||||
mainFields: [
|
||||
'browser', 'module', 'main'
|
||||
],
|
||||
keepNames: true,
|
||||
banner: {
|
||||
js: banner.join('\n'),
|
||||
// using \n breaks sourcemaps!
|
||||
js: banner.join(';'),
|
||||
},
|
||||
alias: {
|
||||
events: 'events', // make explicit
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -36,12 +36,14 @@
|
|||
"esbuild": "^0.19.3",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"flying-squid": "github:zardoy/space-squid#everything",
|
||||
"fs-extra": "^11.1.1",
|
||||
"iconify-icon": "^1.0.8",
|
||||
"jszip": "^3.10.1",
|
||||
"lit": "^2.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minecraft-assets": "^1.9.1",
|
||||
"minecraft-data": "^3.0.0",
|
||||
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
|
||||
"peerjs": "^1.5.0",
|
||||
|
|
@ -49,11 +51,14 @@
|
|||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"stats-gl": "^1.0.5",
|
||||
"stats.js": "^0.17.0",
|
||||
"valtio": "^1.11.1",
|
||||
"workbox-build": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/stats.js": "^0.17.1",
|
||||
"@types/three": "0.128.0",
|
||||
"assert": "^2.0.0",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
|
|
@ -63,6 +68,7 @@
|
|||
"crypto-browserify": "^3.12.0",
|
||||
"cypress": "^9.5.4",
|
||||
"cypress-esbuild-preprocessor": "^1.0.2",
|
||||
"path-exists-cli": "^2.0.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-config-zardoy": "^0.2.17",
|
||||
"events": "^3.3.0",
|
||||
|
|
@ -76,7 +82,7 @@
|
|||
"npm-run-all": "^4.1.5",
|
||||
"os-browserify": "^0.3.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prismarine-viewer": "./prismarine-viewer",
|
||||
"prismarine-viewer": "link:prismarine-viewer",
|
||||
"process": "github:PrismarineJS/node-process",
|
||||
"rimraf": "^5.0.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
|
|
|
|||
3530
pnpm-lock.yaml
generated
3530
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
3
prismarine-viewer/.npmrc
Normal file
3
prismarine-viewer/.npmrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
public-hoist-pattern=*
|
||||
shell-emulator=true
|
||||
package-lock=false
|
||||
|
|
@ -19,6 +19,7 @@ const buildOptions = {
|
|||
entryPoints: [path.join(__dirname, './viewer/lib/worker.js')],
|
||||
outfile: path.join(__dirname, './public/worker.js'),
|
||||
minify: true,
|
||||
logLevel: 'info',
|
||||
drop: [
|
||||
'debugger'
|
||||
],
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))
|
|||
/** @type {import('esbuild').BuildOptions} */
|
||||
const buildOptions = {
|
||||
bundle: true,
|
||||
// entryPoints: [join(__dirname, 'lib/index.js')],
|
||||
entryPoints: [join(__dirname, './examples/schematic.js')],
|
||||
entryPoints: [join(__dirname, './examples/playground.js')],
|
||||
// target: ['es2020'],
|
||||
// logLevel: 'debug',
|
||||
logLevel: 'info',
|
||||
platform: 'browser',
|
||||
sourcemap: dev,
|
||||
sourcemap: dev ? 'inline' : false,
|
||||
outfile: join(__dirname, 'public/index.js'),
|
||||
mainFields: [
|
||||
'browser', 'module', 'main'
|
||||
|
|
@ -66,11 +65,10 @@ const buildOptions = {
|
|||
|
||||
const allowOnlyList = process.env.ONLY_MC_DATA?.split(',') ?? []
|
||||
|
||||
// skip data for 0.30c, snapshots and pre-releases
|
||||
const includeVersions = ['1.16.4']
|
||||
const includeVersions = ['1.20.1', '1.18.1']
|
||||
|
||||
const includedVersions = []
|
||||
let contents = 'module.exports =\n{\n'
|
||||
let contents = `module.exports =\n{\n`
|
||||
for (const platform of Object.keys(dataPaths)) {
|
||||
contents += ` '${platform}': {\n`
|
||||
for (const version of Object.keys(dataPaths[platform])) {
|
||||
|
|
@ -87,7 +85,7 @@ const buildOptions = {
|
|||
}
|
||||
contents += ' },\n'
|
||||
}
|
||||
contents += '}\n'
|
||||
contents += `}\n;globalThis.includedVersions = ${JSON.stringify(includedVersions)};`
|
||||
|
||||
return {
|
||||
contents,
|
||||
|
|
|
|||
203
prismarine-viewer/examples/playground.js
Normal file
203
prismarine-viewer/examples/playground.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
//@ts-check
|
||||
/* global THREE, fetch */
|
||||
const { WorldView, Viewer, MapControls } = require('prismarine-viewer/viewer')
|
||||
const { Vec3 } = require('vec3')
|
||||
const { Schematic } = require('prismarine-schematic')
|
||||
const BlockLoader = require('prismarine-block')
|
||||
/** @type {import('prismarine-chunk')['default']} */
|
||||
//@ts-ignore
|
||||
const ChunkLoader = require('prismarine-chunk')
|
||||
/** @type {import('prismarine-world')['default']} */
|
||||
//@ts-ignore
|
||||
const WorldLoader = require('prismarine-world');
|
||||
const THREE = require('three')
|
||||
const {GUI} = require('lil-gui')
|
||||
global.THREE = THREE
|
||||
|
||||
const gui = new GUI()
|
||||
|
||||
// initial values
|
||||
const params = {
|
||||
skip: '',
|
||||
version: globalThis.includedVersions[0],
|
||||
block: '',
|
||||
metadata: 0,
|
||||
supportBlock: false,
|
||||
entity: '',
|
||||
removeEntity () {
|
||||
this.entity = ''
|
||||
},
|
||||
entityRotate: false,
|
||||
}
|
||||
|
||||
const qs = new URLSearchParams(window.location.search)
|
||||
qs.forEach((value, key) => {
|
||||
const parsed = value.match(/^-?\d+$/) ? parseInt(value) : value === 'true' ? true : value === 'false' ? false : value
|
||||
params[key] = parsed
|
||||
})
|
||||
const setQs = () => {
|
||||
const newQs = new URLSearchParams()
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (!value || typeof value === 'function' || params.skip.includes(key)) continue
|
||||
//@ts-ignore
|
||||
newQs.set(key, value)
|
||||
}
|
||||
window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`)
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const { version } = params
|
||||
const mcData = require('minecraft-data')(version)
|
||||
window['mcData'] = mcData
|
||||
|
||||
gui.add(params, 'version', globalThis.includedVersions)
|
||||
gui.add(params, 'block', mcData.blocksArray.map(b => b.name))
|
||||
const metadataGui = gui.add(params, 'metadata')
|
||||
gui.add(params, 'supportBlock')
|
||||
gui.add(params, 'entity', mcData.entitiesArray.map(b => b.name)).listen()
|
||||
gui.add(params, 'removeEntity')
|
||||
gui.add(params, 'entityRotate')
|
||||
gui.add(params, 'skip')
|
||||
gui.open(false)
|
||||
let folder = gui.addFolder('metadata')
|
||||
|
||||
const Chunk = ChunkLoader(version)
|
||||
const Block = BlockLoader(version)
|
||||
// const data = await fetch('smallhouse1.schem').then(r => r.arrayBuffer())
|
||||
// const schem = await Schematic.read(Buffer.from(data), version)
|
||||
|
||||
const viewDistance = 1
|
||||
const center = new Vec3(0, 90, 0)
|
||||
|
||||
const World = WorldLoader(version)
|
||||
|
||||
// const diamondSquare = require('diamond-square')({ version, seed: Math.floor(Math.random() * Math.pow(2, 31)) })
|
||||
const targetBlockPos = center
|
||||
const world = new World((chunkX, chunkZ) => {
|
||||
//@ts-ignore
|
||||
return new Chunk()
|
||||
})
|
||||
|
||||
// await schem.paste(world, new Vec3(0, 60, 0))
|
||||
|
||||
const worldView = new WorldView(world, viewDistance, center)
|
||||
|
||||
// Create three.js context, add to page
|
||||
const renderer = new THREE.WebGLRenderer()
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
// Create viewer
|
||||
const viewer = new Viewer(renderer)
|
||||
viewer.setVersion(version)
|
||||
viewer.listen(worldView)
|
||||
// Initialize viewer, load chunks
|
||||
worldView.init(center)
|
||||
window['worldView'] = worldView
|
||||
window['viewer'] = viewer
|
||||
|
||||
|
||||
// const controls = new MapControls(viewer.camera, renderer.domElement)
|
||||
// controls.update()
|
||||
|
||||
const cameraPos = center.offset(2, 2, 2)
|
||||
const pitch = THREE.MathUtils.degToRad(-45)
|
||||
const yaw = THREE.MathUtils.degToRad(45)
|
||||
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
|
||||
viewer.camera.position.set(cameraPos.x + 0.5, cameraPos.y + 0.5, cameraPos.z + 0.5)
|
||||
|
||||
// Browser animation loop
|
||||
const animate = () => {
|
||||
// if (controls) controls.update()
|
||||
// worldView.updatePosition(controls.target)
|
||||
viewer.update()
|
||||
renderer.render(viewer.scene, viewer.camera)
|
||||
window.requestAnimationFrame(animate)
|
||||
}
|
||||
// to fix: stairs, signs, chests, skulls, conduit
|
||||
// hay, log, mushroom, terract
|
||||
animate()
|
||||
|
||||
let blockProps = {}
|
||||
const getBlock = () => {
|
||||
return mcData.blocksByName[params.block || 'air']
|
||||
}
|
||||
const onUpdate = {
|
||||
block() {
|
||||
const {states} = mcData.blocksByStateId[getBlock()?.minStateId] ?? {}
|
||||
folder.destroy()
|
||||
if (!states) {
|
||||
return
|
||||
}
|
||||
folder = gui.addFolder('metadata')
|
||||
for (const state of states) {
|
||||
let defaultValue
|
||||
switch (state.type) {
|
||||
case 'enum':
|
||||
defaultValue = state.values[0]
|
||||
break;
|
||||
case 'bool':
|
||||
defaultValue = false
|
||||
break;
|
||||
case 'int':
|
||||
defaultValue = 0
|
||||
break;
|
||||
case 'direction':
|
||||
defaultValue = 'north'
|
||||
break;
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
blockProps[state.name] = defaultValue
|
||||
if (state.type === 'enum') {
|
||||
folder.add(blockProps, state.name, state.values)
|
||||
} else {
|
||||
folder.add(blockProps, state.name)
|
||||
}
|
||||
}
|
||||
folder.open()
|
||||
},
|
||||
entity () {
|
||||
viewer.entities.clear()
|
||||
if (!params.entity) return
|
||||
worldView.emit('entity', {
|
||||
id: 'id', name: params.entity, pos: targetBlockPos.offset(0, 1, 0), width: 1, height: 1, username: 'username'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const applyChanges = () => {
|
||||
//@ts-ignore
|
||||
const block = Block.fromProperties(getBlock()?.id ?? -1, blockProps, 0)
|
||||
viewer.setBlockStateId(targetBlockPos, block.stateId)
|
||||
console.log('up', block.stateId)
|
||||
params.metadata = block.metadata
|
||||
metadataGui.updateDisplay()
|
||||
viewer.setBlockStateId(targetBlockPos.offset(0, -1, 0), params.supportBlock ? 1 : 0)
|
||||
setQs()
|
||||
}
|
||||
gui.onChange(({property}) => {
|
||||
onUpdate[property]?.()
|
||||
applyChanges()
|
||||
})
|
||||
viewer.waitForChunksToRender().then(async () => {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 0)
|
||||
})
|
||||
for (const update of Object.values(onUpdate)) {
|
||||
update()
|
||||
}
|
||||
applyChanges()
|
||||
gui.openAnimated()
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
// worldView.emit('entity', {
|
||||
// id: 'id', name: 'player', pos: center.offset(1, -2, 0), width: 1, height: 1, username: 'username'
|
||||
// })
|
||||
}, 1500)
|
||||
}
|
||||
main()
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
"pretest": "npm run lint",
|
||||
"lint": "standard",
|
||||
"fix": "standard --fix",
|
||||
"prepare": "node viewer/generateTextures.js && node buildWorker.mjs"
|
||||
"postinstall": "node viewer/generateTextures.js && node buildWorker.mjs"
|
||||
},
|
||||
"author": "PrismarineJS",
|
||||
"license": "MIT",
|
||||
|
|
@ -22,37 +22,22 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tweenjs/tween.js": "^20.0.3",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.17.1",
|
||||
"minecraft-data": "^3.0.0",
|
||||
"minecrafthawkeye": "^1.3.6",
|
||||
"prismarine-block": "^1.7.3",
|
||||
"prismarine-chunk": "^1.22.0",
|
||||
"prismarine-world": "^3.3.1",
|
||||
"socket.io": "^4.0.0",
|
||||
"socket.io-client": "^4.0.0",
|
||||
"three": "0.128.0",
|
||||
"three.meshline": "^1.3.0",
|
||||
"vec3": "^0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vec3": "^0.1.7",
|
||||
"assert": "^2.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"canvas": "^2.11.2",
|
||||
"filesize": "^10.0.12",
|
||||
"fs-extra": "^11.0.0",
|
||||
"jest": "^27.0.4",
|
||||
"jest-puppeteer": "^6.0.0",
|
||||
"minecraft-assets": "^1.9.0",
|
||||
"lil-gui": "^0.18.2",
|
||||
"minecraft-wrap": "^1.3.0",
|
||||
"minecrafthawkeye": "^1.2.5",
|
||||
"mineflayer": "^4.0.0",
|
||||
"mineflayer-pathfinder": "^2.0.0",
|
||||
"prismarine-schematic": "^1.2.0",
|
||||
"prismarine-viewer": "file:./",
|
||||
"process": "^0.11.10",
|
||||
"puppeteer": "^16.0.0",
|
||||
"standard": "^17.0.0",
|
||||
"webpack": "^5.10.2",
|
||||
"webpack-cli": "^5.1.1"
|
||||
"prismarine-viewer": "link:./",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function minor (version) {
|
|||
|
||||
function getVersion (version) {
|
||||
if (supportedVersions.indexOf(version) !== -1) return version
|
||||
return lastOfMajor[toMajor(version)]
|
||||
return lastOfMajor[toMajor(version)] ?? Object.values(lastOfMajor).at(-1)
|
||||
}
|
||||
|
||||
module.exports = { getVersion }
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ class Viewer {
|
|||
this.primitives.clear()
|
||||
}
|
||||
|
||||
setVersion (version) {
|
||||
version = getVersion(version)
|
||||
console.log('Using version: ' + version)
|
||||
this.version = version
|
||||
this.world.setVersion(version)
|
||||
setVersion (userVersion) {
|
||||
const texturesVersion = getVersion(userVersion)
|
||||
console.log('Using version:', userVersion, 'textures:', texturesVersion)
|
||||
this.version = userVersion
|
||||
this.world.setVersion(userVersion, texturesVersion)
|
||||
this.entities.clear()
|
||||
this.primitives.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@ const { getSectionGeometry } = require('./models')
|
|||
|
||||
let blocksStates = null
|
||||
let world = null
|
||||
let dirtySections = {}
|
||||
|
||||
function sectionKey (x, y, z) {
|
||||
return `${x},${y},${z}`
|
||||
}
|
||||
|
||||
const dirtySections = {}
|
||||
|
||||
function setSectionDirty (pos, value = true) {
|
||||
const x = Math.floor(pos.x / 16) * 16
|
||||
const y = Math.floor(pos.y / 16) * 16
|
||||
|
|
@ -57,6 +56,7 @@ self.onmessage = ({ data }) => {
|
|||
} else if (data.type === 'reset') {
|
||||
world = null
|
||||
blocksStates = null
|
||||
dirtySections = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class WorldRenderer {
|
|||
|
||||
const worker = new Worker(src)
|
||||
worker.onmessage = ({ data }) => {
|
||||
if (!this.active) return
|
||||
if (data.type === 'geometry') {
|
||||
let mesh = this.sectionMeshs[data.key]
|
||||
if (mesh) {
|
||||
|
|
@ -70,13 +71,16 @@ class WorldRenderer {
|
|||
this.scene.remove(mesh)
|
||||
}
|
||||
this.sectionMeshs = {}
|
||||
this.loadedChunks = {}
|
||||
this.sectionsOutstanding = new Set()
|
||||
for (const worker of this.workers) {
|
||||
worker.postMessage({ type: 'reset' })
|
||||
}
|
||||
}
|
||||
|
||||
setVersion (version) {
|
||||
setVersion (version, texturesVersion = version) {
|
||||
this.version = version
|
||||
this.texturesVersion = texturesVersion
|
||||
this.resetWorld()
|
||||
this.active = true
|
||||
for (const worker of this.workers) {
|
||||
|
|
@ -87,7 +91,7 @@ class WorldRenderer {
|
|||
}
|
||||
|
||||
updateTexturesData () {
|
||||
loadTexture(this.texturesDataUrl || `textures/${this.version}.png`, texture => {
|
||||
loadTexture(this.texturesDataUrl || `textures/${this.texturesVersion}.png`, texture => {
|
||||
texture.magFilter = THREE.NearestFilter
|
||||
texture.minFilter = THREE.NearestFilter
|
||||
texture.flipY = false
|
||||
|
|
@ -97,7 +101,7 @@ class WorldRenderer {
|
|||
const loadBlockStates = () => {
|
||||
return new Promise(resolve => {
|
||||
if (this.blockStatesData) return resolve(this.blockStatesData)
|
||||
return loadJSON(`blocksStates/${this.version}.json`, resolve)
|
||||
return loadJSON(`blocksStates/${this.texturesVersion}.json`, resolve)
|
||||
})
|
||||
}
|
||||
loadBlockStates().then((blockStates) => {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ function getViewDirection (pitch, yaw) {
|
|||
|
||||
class BlockInteraction {
|
||||
static instance = null
|
||||
/** @type {null | {blockPos,mesh}} */
|
||||
interactionLines = null
|
||||
|
||||
init () {
|
||||
bot.on('physicsTick', () => { if (this.lastBlockPlaced < 4) this.lastBlockPlaced++ })
|
||||
|
|
@ -103,8 +105,6 @@ class BlockInteraction {
|
|||
})
|
||||
}
|
||||
|
||||
/** @type {null | {blockPos,mesh}} */
|
||||
interactionLines = null
|
||||
updateBlockInteractionLines (/** @type {Vec3 | null} */blockPos, /** @type {{position, width, height, depth}[]} */shapePositions = undefined) {
|
||||
if (this.interactionLines !== null) {
|
||||
viewer.scene.remove(this.interactionLines.mesh)
|
||||
|
|
@ -127,6 +127,7 @@ class BlockInteraction {
|
|||
}
|
||||
|
||||
// todo this shouldnt be done in the render loop, migrate the code to dom events to avoid delays on lags
|
||||
// eslint-disable-next-line complexity
|
||||
update () {
|
||||
const cursorBlock = bot.blockAtCursor(5)
|
||||
let cursorBlockDiggable = cursorBlock
|
||||
|
|
@ -137,14 +138,19 @@ class BlockInteraction {
|
|||
cursorChanged = !cursorBlock.position.equals(this.cursorBlock.position)
|
||||
}
|
||||
|
||||
// Place
|
||||
if (cursorBlock && this.buttons[2] && (!this.lastButtons[2] || cursorChanged) && this.lastBlockPlaced >= 4) {
|
||||
// Place / interact
|
||||
if (cursorBlock && this.buttons[2] && this.lastBlockPlaced >= 4) {
|
||||
const vecArray = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)]
|
||||
//@ts-ignore
|
||||
//@ts-expect-error
|
||||
const delta = cursorBlock.intersect.minus(cursorBlock.position)
|
||||
// check instead?
|
||||
//@ts-ignore
|
||||
|
||||
// workaround so blocks can be activated with empty hand
|
||||
const oldHeldItem = bot.heldItem
|
||||
//@ts-expect-error
|
||||
bot.heldItem = true
|
||||
//@ts-expect-error
|
||||
bot._placeBlockWithOptions(cursorBlock, vecArray[cursorBlock.face], { delta, forceLook: 'ignore' }).catch(console.warn)
|
||||
bot.heldItem = oldHeldItem
|
||||
this.lastBlockPlaced = 0
|
||||
}
|
||||
|
||||
|
|
@ -167,9 +173,7 @@ class BlockInteraction {
|
|||
}
|
||||
|
||||
// Show cursor
|
||||
if (!cursorBlock) {
|
||||
this.updateBlockInteractionLines(null)
|
||||
} else {
|
||||
if (cursorBlock) {
|
||||
const allShapes = [...cursorBlock.shapes, ...cursorBlock['interactionShapes'] ?? []]
|
||||
this.updateBlockInteractionLines(cursorBlock.position, allShapes.map(shape => {
|
||||
return getDataFromShape(shape)
|
||||
|
|
@ -191,6 +195,8 @@ class BlockInteraction {
|
|||
position.add(cursorBlock.position)
|
||||
this.blockBreakMesh.position.set(position.x, position.y, position.z)
|
||||
}
|
||||
} else {
|
||||
this.updateBlockInteractionLines(null)
|
||||
}
|
||||
|
||||
// Show break animation
|
||||
|
|
|
|||
92
src/botUtils.ts
Normal file
92
src/botUtils.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// this should actually be moved to mineflayer / prismarine-viewer
|
||||
|
||||
export type MessageFormatPart = {
|
||||
text: string
|
||||
color?: string
|
||||
bold?: boolean
|
||||
italic?: boolean
|
||||
underlined?: boolean
|
||||
strikethrough?: boolean
|
||||
obfuscated?: boolean
|
||||
}
|
||||
|
||||
// dont edit these typings manually
|
||||
type MessageInput = {
|
||||
text?: string
|
||||
translate?: string
|
||||
with?: MessageInput[]
|
||||
color?: string
|
||||
bold?: boolean
|
||||
italic?: boolean
|
||||
underlined?: boolean
|
||||
strikethrough?: boolean
|
||||
obfuscated?: boolean
|
||||
extra?: MessageInput[]
|
||||
}
|
||||
|
||||
export const formatMessage = (message: MessageInput) => {
|
||||
const msglist: MessageFormatPart[] = []
|
||||
|
||||
const readMsg = (msg) => {
|
||||
const styles = {
|
||||
color: msg.color,
|
||||
bold: !!msg.bold,
|
||||
italic: !!msg.italic,
|
||||
underlined: !!msg.underlined,
|
||||
strikethrough: !!msg.strikethrough,
|
||||
obfuscated: !!msg.obfuscated
|
||||
}
|
||||
|
||||
if (msg.text) {
|
||||
msglist.push({
|
||||
...msg,
|
||||
text: msg.text,
|
||||
...styles
|
||||
})
|
||||
} else if (msg.translate) {
|
||||
const tText = window.loadedData.language[msg.translate] ?? msg.translate
|
||||
|
||||
if (msg.with) {
|
||||
const splitted = tText.split(/%s|%\d+\$s/g)
|
||||
|
||||
let i = 0
|
||||
for (const [j, part] of splitted.entries()) {
|
||||
msglist.push({ text: part, ...styles })
|
||||
|
||||
if (j + 1 < splitted.length) {
|
||||
if (msg.with[i]) {
|
||||
if (typeof msg.with[i] === 'string') {
|
||||
readMsg({
|
||||
...styles,
|
||||
text: msg.with[i]
|
||||
})
|
||||
} else {
|
||||
readMsg({
|
||||
...styles,
|
||||
...msg.with[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msglist.push({
|
||||
...msg,
|
||||
text: tText,
|
||||
...styles
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.extra) {
|
||||
for (const ex of msg.extra) {
|
||||
readMsg({ ...styles, ...ex })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readMsg(message)
|
||||
|
||||
return msglist
|
||||
}
|
||||
|
|
@ -270,20 +270,12 @@ export const openWorldZip = async (...args: Parameters<typeof openWorldZipInner>
|
|||
}
|
||||
}
|
||||
|
||||
export async function generateAndDownloadWorldZip() {
|
||||
const zip = new JSZip()
|
||||
|
||||
zip.folder('world')
|
||||
|
||||
// Generate the ZIP archive content
|
||||
const zipContent = await zip.generateAsync({ type: 'blob' })
|
||||
|
||||
// Create a download link and trigger the download
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.href = URL.createObjectURL(zipContent)
|
||||
downloadLink.download = 'prismarine-world.zip'
|
||||
downloadLink.click()
|
||||
|
||||
// Clean up the URL object after download
|
||||
URL.revokeObjectURL(downloadLink.href)
|
||||
export const resetLocalStorageWorld = () => {
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (/^[\da-fA-F]{8}(?:\b-[\da-fA-F]{4}){3}\b-[\da-fA-F]{12}$/g.test(key) || key === '/') {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.resetLocalStorageWorld = resetLocalStorageWorld
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { join } from 'path'
|
|||
import JSZip from 'jszip'
|
||||
import { fsState } from './loadSave'
|
||||
import { closeWan, openToWanAndCopyJoinLink } from './localServerMultiplayer'
|
||||
import { resetLocalStorageWorld } from './browserfs'
|
||||
|
||||
const notImplemented = () => {
|
||||
return 'Not implemented yet'
|
||||
|
|
@ -32,7 +33,7 @@ async function addFolderToZip(folderPath, zip, relativePath) {
|
|||
const exportWorld = async () => {
|
||||
// todo issue into chat warning if fs is writable!
|
||||
const zip = new JSZip()
|
||||
let {worldFolder} = localServer.options
|
||||
let { worldFolder } = localServer.options
|
||||
if (!worldFolder.startsWith('/')) worldFolder = `/${worldFolder}`
|
||||
await addFolderToZip(worldFolder, zip, '')
|
||||
|
||||
|
|
@ -42,7 +43,8 @@ const exportWorld = async () => {
|
|||
// Create a download link and trigger the download
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.href = URL.createObjectURL(zipContent)
|
||||
downloadLink.download = 'world-exported.zip'
|
||||
// todo use loaded zip/folder name
|
||||
downloadLink.download = 'world-prismarine-exported.zip'
|
||||
downloadLink.click()
|
||||
|
||||
// Clean up the URL object after download
|
||||
|
|
@ -81,11 +83,10 @@ const commands = [
|
|||
async invoke() {
|
||||
if (fsState.inMemorySave) return
|
||||
// todo for testing purposes
|
||||
sessionStorage.oldData = localStorage
|
||||
sessionStorage.oldWorldData = localStorage
|
||||
console.log('World removed. Old data saved to sessionStorage.oldData')
|
||||
localServer.quit()
|
||||
// todo browserfs bug
|
||||
fs.rmdirSync(localServer.options.worldFolder, { recursive: true })
|
||||
resetLocalStorageWorld()
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
226
src/chat.js
226
src/chat.js
|
|
@ -1,51 +1,19 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { isMobile } = require('./menus/components/common')
|
||||
const { activeModalStack, hideCurrentModal, showModal, miscUiState } = require('./globalState')
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { classMap } from 'lit/directives/class-map.js'
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { isCypress } from './utils'
|
||||
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from './builtinCommands'
|
||||
import { notification } from './menus/notification'
|
||||
import { options } from './optionsStorage'
|
||||
import { activeModalStack, hideCurrentModal, showModal, miscUiState } from './globalState'
|
||||
import { formatMessage } from './botUtils'
|
||||
import { getColorShadow, messageFormatStylesMap } from './react/MessageFormatted'
|
||||
|
||||
const styles = {
|
||||
black: 'color:#000000',
|
||||
dark_blue: 'color:#0000AA',
|
||||
dark_green: 'color:#00AA00',
|
||||
dark_aqua: 'color:#00AAAA',
|
||||
dark_red: 'color:#AA0000',
|
||||
dark_purple: 'color:#AA00AA',
|
||||
gold: 'color:#FFAA00',
|
||||
gray: 'color:#AAAAAA',
|
||||
dark_gray: 'color:#555555',
|
||||
blue: 'color:#5555FF',
|
||||
green: 'color:#55FF55',
|
||||
aqua: 'color:#55FFFF',
|
||||
red: 'color:#FF5555',
|
||||
light_purple: 'color:#FF55FF',
|
||||
yellow: 'color:#FFFF55',
|
||||
white: 'color:#FFFFFF',
|
||||
bold: 'font-weight:900',
|
||||
strikethrough: 'text-decoration:line-through',
|
||||
underlined: 'text-decoration:underline',
|
||||
italic: 'font-style:italic'
|
||||
}
|
||||
|
||||
function colorShadow (hex, dim = 0.25) {
|
||||
const color = parseInt(hex.replace('#', ''), 16)
|
||||
|
||||
const r = (color >> 16 & 0xFF) * dim | 0
|
||||
const g = (color >> 8 & 0xFF) * dim | 0
|
||||
const b = (color & 0xFF) * dim | 0
|
||||
|
||||
const f = (c) => ('00' + c.toString(16)).substr(-2)
|
||||
return `#${f(r)}${f(g)}${f(b)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{text;color?;italic?;underlined?;strikethrough?;bold?}} MessagePart
|
||||
* @typedef {{parts: MessagePart[], id, fading?, faded}} Message
|
||||
* @typedef {{parts: import('./botUtils').MessageFormatPart[], id, fading?, faded}} Message
|
||||
*/
|
||||
|
||||
class ChatBox extends LitElement {
|
||||
|
|
@ -109,7 +77,10 @@ class ChatBox extends LitElement {
|
|||
padding: 2px;
|
||||
max-height: 100px;
|
||||
overflow: auto;
|
||||
/* hide ugly scrollbars in firefox */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
/* unsupported by firefox */
|
||||
.chat-completions-items::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
background-color: rgb(24, 24, 24);
|
||||
|
|
@ -259,13 +230,11 @@ class ChatBox extends LitElement {
|
|||
notification.show = false
|
||||
const chat = this.shadowRoot.getElementById('chat-messages')
|
||||
/** @type {HTMLInputElement} */
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const chatInput = this.shadowRoot.getElementById('chatinput')
|
||||
|
||||
showModal(this)
|
||||
|
||||
// Exit the pointer lock
|
||||
document.exitPointerLock?.()
|
||||
// Show extended chat history
|
||||
chat.style.maxHeight = 'var(--chatHeight)'
|
||||
chat.scrollTop = chat.scrollHeight // Stay bottom of the list
|
||||
|
|
@ -281,7 +250,7 @@ class ChatBox extends LitElement {
|
|||
}
|
||||
|
||||
get inChat () {
|
||||
return activeModalStack.find(m => m.elem === this) !== undefined
|
||||
return activeModalStack.some(m => m.elem === this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -290,7 +259,7 @@ class ChatBox extends LitElement {
|
|||
init (client) {
|
||||
const chat = this.shadowRoot.getElementById('chat-messages')
|
||||
/** @type {HTMLInputElement} */
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const chatInput = this.shadowRoot.getElementById('chatinput')
|
||||
this.chatInput = chatInput
|
||||
|
||||
|
|
@ -300,7 +269,7 @@ class ChatBox extends LitElement {
|
|||
let savedCurrentValue
|
||||
// Chat events
|
||||
document.addEventListener('keydown', e => {
|
||||
if (activeModalStack.slice(-1)[0]?.elem !== this) return
|
||||
if (activeModalStack.at(-1)?.elem !== this) return
|
||||
if (e.code === 'ArrowUp') {
|
||||
if (this.chatHistoryPos === 0) return
|
||||
if (this.chatHistoryPos === this.chatHistory.length) {
|
||||
|
|
@ -329,7 +298,7 @@ class ChatBox extends LitElement {
|
|||
window.sessionStorage.chatHistory = JSON.stringify(this.chatHistory)
|
||||
const builtinHandled = tryHandleBuiltinCommand(message)
|
||||
if (!builtinHandled) {
|
||||
client.write('chat', { message })
|
||||
bot.chat(message)
|
||||
}
|
||||
}
|
||||
hideCurrentModal()
|
||||
|
|
@ -350,76 +319,33 @@ class ChatBox extends LitElement {
|
|||
}
|
||||
this.hide()
|
||||
|
||||
// loadedData.protocol.play.toClient.types
|
||||
const handleClientEvents = (packets) => {
|
||||
for (const [packet, handler] of Object.entries(packets)) {
|
||||
bot._client.on(packet, handler)
|
||||
}
|
||||
}
|
||||
handleClientEvents({
|
||||
playerChat ({ formattedMessage, plainMessage, senderName }) {
|
||||
client.emit('chat', {
|
||||
message: formattedMessage || JSON.stringify({ text: `<${JSON.parse(senderName || '{}').text}> ${plainMessage}` })
|
||||
})
|
||||
},
|
||||
systemChat ({ formattedMessage }) {
|
||||
client.emit('chat', {
|
||||
message: formattedMessage
|
||||
})
|
||||
},
|
||||
})
|
||||
client.on('chat', (packet) => {
|
||||
// Handle new message
|
||||
const fullmessage = JSON.parse(packet.message.toString())
|
||||
/** @type {MessagePart[]} */
|
||||
const msglist = []
|
||||
|
||||
const readMsg = (msg) => {
|
||||
const styles = {
|
||||
color: msg.color,
|
||||
bold: !!msg.bold,
|
||||
italic: !!msg.italic,
|
||||
underlined: !!msg.underlined,
|
||||
strikethrough: !!msg.strikethrough,
|
||||
obfuscated: !!msg.obfuscated
|
||||
}
|
||||
|
||||
if (msg.text) {
|
||||
msglist.push({
|
||||
...msg,
|
||||
text: msg.text,
|
||||
...styles
|
||||
})
|
||||
} else if (msg.translate) {
|
||||
const tText = window.loadedData.language[msg.translate] ?? msg.translate
|
||||
|
||||
if (msg.with) {
|
||||
const splitted = tText.split(/%s|%\d+\$s/g)
|
||||
|
||||
let i = 0
|
||||
splitted.forEach((part, j) => {
|
||||
msglist.push({ text: part, ...styles })
|
||||
|
||||
if (j + 1 < splitted.length) {
|
||||
if (msg.with[i]) {
|
||||
if (typeof msg.with[i] === 'string') {
|
||||
readMsg({
|
||||
...styles,
|
||||
text: msg.with[i]
|
||||
})
|
||||
} else {
|
||||
readMsg({
|
||||
...styles,
|
||||
...msg.with[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
} else {
|
||||
msglist.push({
|
||||
...msg,
|
||||
text: tText,
|
||||
...styles
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.extra) {
|
||||
msg.extra.forEach(ex => {
|
||||
readMsg({ ...styles, ...ex })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
readMsg(fullmessage)
|
||||
const parts = formatMessage(fullmessage)
|
||||
|
||||
const lastId = this.messages.at(-1)?.id ?? 0
|
||||
this.messages = [...this.messages.slice(-this.messagesLimit), {
|
||||
parts: msglist,
|
||||
parts,
|
||||
id: lastId + 1,
|
||||
fading: false,
|
||||
faded: false
|
||||
|
|
@ -441,9 +367,9 @@ class ChatBox extends LitElement {
|
|||
// todo remove
|
||||
window.dummyMessage = () => {
|
||||
client.emit('chat', {
|
||||
message: "{\"color\":\"yellow\",\"translate\":\"multiplayer.player.joined\",\"with\":[{\"insertion\":\"pviewer672\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"/tell pviewer672 \"},\"hoverEvent\":{\"action\":\"show_entity\",\"contents\":{\"type\":\"minecraft:player\",\"id\":\"ecd0eeb1-625e-3fea-b16e-cb449dcfa434\",\"name\":{\"text\":\"pviewer672\"}}},\"text\":\"pviewer672\"}]}",
|
||||
message: '{"color":"yellow","translate":"multiplayer.player.joined","with":[{"insertion":"pviewer672","clickEvent":{"action":"suggest_command","value":"/tell pviewer672 "},"hoverEvent":{"action":"show_entity","contents":{"type":"minecraft:player","id":"ecd0eeb1-625e-3fea-b16e-cb449dcfa434","name":{"text":"pviewer672"}}},"text":"pviewer672"}]}',
|
||||
position: 1,
|
||||
sender: "00000000-0000-0000-0000-000000000000",
|
||||
sender: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
}
|
||||
// window.dummyMessage()
|
||||
|
|
@ -493,35 +419,44 @@ class ChatBox extends LitElement {
|
|||
this.completeRequestValue = value
|
||||
let items = await bot.tabComplete(value, true, true)
|
||||
if (typeof items[0] === 'object') {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
if (items[0].match) items = items.map(i => i.match)
|
||||
}
|
||||
if (value !== this.completeRequestValue) return
|
||||
if (this.completeRequestValue === '/') items = [...items, ...getBuiltinCommandsList()]
|
||||
if (this.completeRequestValue === '/') {
|
||||
if (!items[0].startsWith('/')) {
|
||||
// normalize
|
||||
items = items.map(item => `/${item}`)
|
||||
}
|
||||
if (localServer) {
|
||||
items = [...items, ...getBuiltinCommandsList()]
|
||||
}
|
||||
}
|
||||
this.completionItems = items
|
||||
this.completionItemsSource = items
|
||||
}
|
||||
|
||||
renderMessagePart (/** @type {MessagePart} */{ bold, color, italic, strikethrough, text, underlined }) {
|
||||
renderMessagePart (/** @type {import('./botUtils').MessageFormatPart} */{ bold, color, italic, strikethrough, text, underlined }) {
|
||||
const colorF = (color) => {
|
||||
return color.trim().startsWith('#') ? `color:${color}` : styles[color] ?? undefined
|
||||
return color.trim().startsWith('#') ? `color:${color}` : messageFormatStylesMap[color] ?? undefined
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const applyStyles = [
|
||||
color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : styles.white,
|
||||
italic && styles.italic,
|
||||
bold && styles.bold,
|
||||
italic && styles.italic,
|
||||
underlined && styles.underlined,
|
||||
strikethrough && styles.strikethrough
|
||||
color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${getColorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : messageFormatStylesMap.white,
|
||||
italic && messageFormatStylesMap.italic,
|
||||
bold && messageFormatStylesMap.bold,
|
||||
italic && messageFormatStylesMap.italic,
|
||||
underlined && messageFormatStylesMap.underlined,
|
||||
strikethrough && messageFormatStylesMap.strikethrough
|
||||
].filter(Boolean)
|
||||
|
||||
return html`
|
||||
<span
|
||||
class="chat-message-part"
|
||||
style="${applyStyles.join(';')}"
|
||||
>${text}</span>`
|
||||
<span
|
||||
class="chat-message-part"
|
||||
style="${applyStyles.join(';')}"
|
||||
>${text}</span>
|
||||
`
|
||||
}
|
||||
|
||||
renderMessage (/** @type {Message} */message) {
|
||||
|
|
@ -560,34 +495,37 @@ class ChatBox extends LitElement {
|
|||
// // trigger next tab complete
|
||||
// this.chatInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' }))
|
||||
this.chatInput.focus()
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
return html`
|
||||
<div class="chat-wrapper chat-messages-wrapper ${miscUiState.currentTouch ? 'display-mobile' : ''}">
|
||||
<div class="chat ${this.inChat ? 'opened' : ''}" id="chat-messages">
|
||||
<!-- its to hide player joined at random timings, todo add chat tests as well -->
|
||||
${repeat(isCypress() ? [] : this.messages, (m) => m.id, (m) => this.renderMessage(m))}
|
||||
<div class="chat-wrapper chat-messages-wrapper ${miscUiState.currentTouch ? 'display-mobile' : ''}">
|
||||
<div class="chat ${this.inChat ? 'opened' : ''}" id="chat-messages">
|
||||
<!-- its to hide player joined at random timings, todo add chat tests as well -->
|
||||
${repeat(isCypress() ? [] : this.messages, (m) => m.id, (m) => this.renderMessage(m))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-wrapper chat-input-wrapper ${miscUiState.currentTouch ? 'input-mobile' : ''}" style="display: ${this.inChat ? 'block' : 'none'}">
|
||||
<div class="chat-input">
|
||||
${this.completionItems.length ? html`<div class="chat-completions">
|
||||
<div class="chat-completions-pad-text">${this.completePadText}</div>
|
||||
<div class="chat-completions-items">
|
||||
${repeat(this.completionItems, (i) => i, (i) => html`<div @click=${() => this.acceptComplete(i)}>${i}</div>`)}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
<input type="text" class="chat-mobile-hidden" id="chatinput-next-command" spellcheck="false" autocomplete="off" @focus=${() => {
|
||||
this.auxInputFocus('ArrowUp')
|
||||
}}></input>
|
||||
<input type="text" class="chat-input" id="chatinput" spellcheck="false" autocomplete="off" aria-autocomplete="both"></input>
|
||||
<input type="text" class="chat-mobile-hidden" id="chatinput-prev-command" spellcheck="false" autocomplete="off" @focus=${() => {
|
||||
this.auxInputFocus('ArrowDown')
|
||||
}}></input>
|
||||
<div class="chat-wrapper chat-input-wrapper ${miscUiState.currentTouch ? 'input-mobile' : ''}" style="display: ${this.inChat ? 'block' : 'none'}">
|
||||
<div class="chat-input">
|
||||
${this.completionItems.length ? html`
|
||||
<div class="chat-completions">
|
||||
<div class="chat-completions-pad-text">${this.completePadText}</div>
|
||||
<div class="chat-completions-items">
|
||||
${repeat(this.completionItems, (i) => i, (i) => html`<div @click=${() => this.acceptComplete(i)}>${i}</div>`)}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
<input type="text" class="chat-mobile-hidden" id="chatinput-next-command" spellcheck="false" autocomplete="off" @focus=${() => {
|
||||
this.auxInputFocus('ArrowUp')
|
||||
}}></input>
|
||||
<input type="text" class="chat-input" id="chatinput" spellcheck="false" autocomplete="off" aria-autocomplete="both"></input>
|
||||
<input type="text" class="chat-mobile-hidden" id="chatinput-prev-command" spellcheck="false" autocomplete="off" @focus=${() => {
|
||||
this.auxInputFocus('ArrowDown')
|
||||
}}></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ contro.on('movementUpdate', ({ vector, gamepadIndex }) => {
|
|||
|
||||
let lastCommandTrigger = null as { command: string, time: number } | null
|
||||
|
||||
const secondActionActivationTimeout = 600
|
||||
const secondActionActivationTimeout = 300
|
||||
const secondActionCommands = {
|
||||
'general.jump'() {
|
||||
toggleFly()
|
||||
|
|
@ -139,22 +139,25 @@ 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
|
||||
case 'general.interactPlace':
|
||||
document.dispatchEvent(new MouseEvent(pressed ? 'mousedown' : 'mouseup', { button: 2 }))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,26 +195,19 @@ 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(() => {
|
||||
// todo cleanup
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
||||
})
|
||||
break
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -319,16 +315,16 @@ const toggleFly = () => {
|
|||
gameAdditionalState.isFlying = isFlying()
|
||||
}
|
||||
// #endregion
|
||||
addEventListener('mousedown', (e) => {
|
||||
addEventListener('mousedown', async (e) => {
|
||||
if (!bot) return
|
||||
// wheel click
|
||||
// todo support ctrl+wheel (+nbt)
|
||||
if (e.button === 1) {
|
||||
const block = bot.blockAtCursor(/* 6 */5)
|
||||
const block = bot.blockAtCursor(5)
|
||||
if (!block) return
|
||||
const Item = require('prismarine-item')(bot.version)
|
||||
const item = new Item(block.type, 1, 0)
|
||||
bot.creative.setInventorySlot(bot.inventory.hotbarStart + bot.quickBarSlot, item)
|
||||
await bot.creative.setInventorySlot(bot.inventory.hotbarStart + bot.quickBarSlot, item)
|
||||
bot.updateHeldItem()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { options } from './optionsStorage'
|
||||
|
||||
//@ts-check
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const { EventEmitter } = require('events')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
const states = require('minecraft-protocol/src/states')
|
||||
|
||||
window.serverDataChannel ??= {}
|
||||
export const customCommunication = {
|
||||
sendData (data) {
|
||||
//@ts-ignore
|
||||
setTimeout(() => {
|
||||
window.serverDataChannel[this.isServer ? 'emitClient' : 'emitServer'](data)
|
||||
})
|
||||
},
|
||||
receiverSetup (processData) {
|
||||
//@ts-ignore
|
||||
window.serverDataChannel[this.isServer ? 'emitServer' : 'emitClient'] = (data) => {
|
||||
processData(data)
|
||||
}
|
||||
|
|
@ -33,12 +33,15 @@ class CustomChannelClient extends EventEmitter {
|
|||
|
||||
setSerializer (state) {
|
||||
customCommunication.receiverSetup.call(this, (/** @type {{name, params, state?}} */parsed) => {
|
||||
debug(`receive in ${this.isServer ? 'server' : 'client'}: ${parsed.name}`)
|
||||
if (!options.excludeCommunicationDebugEvents.includes(parsed.name)) {
|
||||
debug(`receive in ${this.isServer ? 'server' : 'client'}: ${parsed.name}`)
|
||||
}
|
||||
this.emit(parsed.name, parsed.params, parsed)
|
||||
this.emit('packet_name', parsed.name, parsed.params, parsed)
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures, grouped-accessor-pairs
|
||||
set state (newProperty) {
|
||||
const oldProperty = this.protocolState
|
||||
this.protocolState = newProperty
|
||||
|
|
@ -53,8 +56,10 @@ class CustomChannelClient extends EventEmitter {
|
|||
}
|
||||
|
||||
write (name, params) {
|
||||
debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
|
||||
debug(params)
|
||||
if(!options.excludeCommunicationDebugEvents.includes(name)) {
|
||||
debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
|
||||
debug(params)
|
||||
}
|
||||
|
||||
customCommunication.sendData.call(this, { name, params, state: this.state })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
'motd': 'A Minecraft Server \nRunning flying-squid',
|
||||
// host: '',
|
||||
// eslint-disable-next-line unicorn/numeric-separators-style
|
||||
'port': 25565,
|
||||
'max-players': 10,
|
||||
'online-mode': false,
|
||||
|
|
@ -21,7 +22,7 @@ module.exports = {
|
|||
// 'worldHeight': 80
|
||||
// }
|
||||
},
|
||||
'kickTimeout': 10000,
|
||||
'kickTimeout': 10_000,
|
||||
'plugins': {},
|
||||
'modpe': false,
|
||||
'view-distance': 2,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module.exports.resolveSrv = function (hostname, callback) {
|
|||
Http.send()
|
||||
|
||||
Http.onload = function () {
|
||||
const response = Http.response
|
||||
const { response } = Http
|
||||
if (response.Status === 3) {
|
||||
const err = new Error('querySrv ENOTFOUND')
|
||||
err.code = 'ENOTFOUND'
|
||||
|
|
@ -24,7 +24,7 @@ module.exports.resolveSrv = function (hostname, callback) {
|
|||
return
|
||||
}
|
||||
const willreturn = []
|
||||
response.Answer.forEach(function (object) {
|
||||
for (const object of response.Answer) {
|
||||
const data = object.data.split(' ')
|
||||
willreturn.push({
|
||||
priority: data[0],
|
||||
|
|
@ -32,7 +32,7 @@ module.exports.resolveSrv = function (hostname, callback) {
|
|||
port: data[2],
|
||||
name: data[3]
|
||||
})
|
||||
})
|
||||
}
|
||||
console.log(willreturn)
|
||||
callback(null, willreturn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ window.addEventListener('drop', async e => {
|
|||
alert('Exit current world first, before loading a new one.')
|
||||
return
|
||||
}
|
||||
await openWorldDirectory(filehandle )
|
||||
await openWorldDirectory(filehandle)
|
||||
}
|
||||
} else {
|
||||
await handleDroppedFile(item.getAsFile())
|
||||
|
|
|
|||
|
|
@ -12,16 +12,29 @@ type ContextMenuItem = { callback; label }
|
|||
|
||||
export const activeModalStack: Modal[] = proxy([])
|
||||
|
||||
export const replaceActiveModalStack = (name: string, newModalStack = activeModalStacks[name]) => {
|
||||
hideModal(undefined, undefined, { restorePrevious: false, force: true, })
|
||||
export const insertActiveModalStack = (name: string, newModalStack = activeModalStacks[name]) => {
|
||||
hideModal(undefined, undefined, { restorePrevious: false, force: true })
|
||||
activeModalStack.splice(0, activeModalStack.length, ...newModalStack)
|
||||
// todo restore previous
|
||||
const last = activeModalStack.at(-1)
|
||||
if (last) showModalInner(last)
|
||||
}
|
||||
|
||||
export const activeModalStacks: Record<string, Modal[]> = {}
|
||||
|
||||
window.activeModalStack = activeModalStack
|
||||
|
||||
subscribe(activeModalStack, () => {
|
||||
if (activeModalStack.length === 0) {
|
||||
if (isGameActive(false)) {
|
||||
void pointerLock.requestPointerLock()
|
||||
} else {
|
||||
showModal(document.getElementById('title-screen'))
|
||||
}
|
||||
} else {
|
||||
document.exitPointerLock()
|
||||
}
|
||||
})
|
||||
|
||||
export const customDisplayManageKeyword = 'custom'
|
||||
|
||||
const defaultModalActions = {
|
||||
|
|
@ -46,7 +59,7 @@ const showModalInner = (modal: Modal) => {
|
|||
export const showModal = (elem: (HTMLElement & Record<string, any>) | { reactType: string }) => {
|
||||
const resolved = elem instanceof HTMLElement ? { elem: ref(elem) } : elem
|
||||
const curModal = activeModalStack.at(-1)
|
||||
if (elem === curModal?.elem || !showModalInner(resolved)) return
|
||||
if (elem === curModal?.elem || (elem.reactType && elem.reactType === curModal?.reactType) || !showModalInner(resolved)) return
|
||||
if (curModal) defaultModalActions.hide(curModal)
|
||||
activeModalStack.push(resolved)
|
||||
}
|
||||
|
|
@ -75,22 +88,15 @@ export const hideModal = (modal = activeModalStack.at(-1), data: any = undefined
|
|||
}
|
||||
}
|
||||
|
||||
export const hideCurrentModal = (_data = undefined, restoredActions = undefined) => {
|
||||
export const hideCurrentModal = (_data = undefined, onHide = undefined) => {
|
||||
if (hideModal(undefined, undefined)) {
|
||||
restoredActions?.()
|
||||
if (activeModalStack.length === 0) {
|
||||
if (isGameActive(false)) {
|
||||
pointerLock.requestPointerLock()
|
||||
} else {
|
||||
showModal(document.getElementById('title-screen'))
|
||||
}
|
||||
}
|
||||
onHide?.()
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
export const currentContextMenu = proxy({ items: [] as ContextMenuItem[] | null, x: 0, y: 0, })
|
||||
export const currentContextMenu = proxy({ items: [] as ContextMenuItem[] | null, x: 0, y: 0 })
|
||||
|
||||
export const showContextmenu = (items: ContextMenuItem[], { clientX, clientY }) => {
|
||||
Object.assign(currentContextMenu, {
|
||||
|
|
|
|||
4
src/globals.d.ts
vendored
4
src/globals.d.ts
vendored
|
|
@ -3,8 +3,8 @@
|
|||
declare const THREE: typeof import('three')
|
||||
// todo make optional
|
||||
declare const bot: import('mineflayer').Bot
|
||||
declare const viewer: import('../prismarine-viewer/viewer/lib/viewer').Viewer | undefined
|
||||
declare const worldView: import('../prismarine-viewer/viewer/lib/worldView').WorldView | undefined
|
||||
declare const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer | undefined
|
||||
declare const worldView: import('prismarine-viewer/viewer/lib/worldView').WorldView | undefined
|
||||
declare const localServer: any
|
||||
|
||||
declare interface Document {
|
||||
|
|
|
|||
88
src/index.ts
88
src/index.ts
|
|
@ -25,6 +25,7 @@ import './menus/options_screen'
|
|||
import './menus/advanced_options_screen'
|
||||
import { notification } from './menus/notification'
|
||||
import './menus/title_screen'
|
||||
import { initWithRenderer, statsEnd, statsStart } from './rightTopStats'
|
||||
|
||||
import { options, watchValue } from './optionsStorage'
|
||||
import './reactUi.jsx'
|
||||
|
|
@ -36,7 +37,6 @@ import './watchOptions'
|
|||
import downloadAndOpenFile from './downloadAndOpenFile'
|
||||
|
||||
import net from 'net'
|
||||
import Stats from 'stats.js'
|
||||
import mineflayer from 'mineflayer'
|
||||
import { WorldView, Viewer } from 'prismarine-viewer/viewer'
|
||||
import pathfinder from 'mineflayer-pathfinder'
|
||||
|
|
@ -45,6 +45,7 @@ import { Vec3 } from 'vec3'
|
|||
import blockInteraction from './blockInteraction'
|
||||
|
||||
import * as THREE from 'three'
|
||||
import { versionsByMinecraftVersion } from 'minecraft-data'
|
||||
|
||||
import { initVR } from './vr'
|
||||
import {
|
||||
|
|
@ -52,7 +53,7 @@ import {
|
|||
showModal,
|
||||
hideCurrentModal,
|
||||
activeModalStacks,
|
||||
replaceActiveModalStack,
|
||||
insertActiveModalStack,
|
||||
isGameActive,
|
||||
miscUiState,
|
||||
gameAdditionalState
|
||||
|
|
@ -78,7 +79,7 @@ import serverOptions from './defaultLocalServerOptions'
|
|||
import updateTime from './updateTime'
|
||||
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import _ from 'lodash'
|
||||
import _ from 'lodash-es'
|
||||
|
||||
import { genTexturePackTextures, watchTexturepackInViewer } from './texturePack'
|
||||
import { connectToPeer } from './localServerMultiplayer'
|
||||
|
|
@ -100,34 +101,19 @@ if ('serviceWorker' in navigator && !isCypress() && process.env.NODE_ENV !== 'de
|
|||
|
||||
// ACTUAL CODE
|
||||
|
||||
// todo stats-gl
|
||||
const stats = new Stats()
|
||||
const stats2 = new Stats()
|
||||
stats2.showPanel(2)
|
||||
|
||||
document.body.appendChild(stats.dom)
|
||||
stats.dom.style.left = ''
|
||||
stats.dom.style.right = '0px'
|
||||
stats.dom.style.width = '80px'
|
||||
stats2.dom.style.width = '80px'
|
||||
stats2.dom.style.right = '80px'
|
||||
stats2.dom.style.left = ''
|
||||
document.body.appendChild(stats2.dom)
|
||||
|
||||
if (localStorage.hideStats || isCypress()) {
|
||||
stats.dom.style.display = 'none'
|
||||
stats2.dom.style.display = 'none'
|
||||
}
|
||||
|
||||
// Create three.js context, add to page
|
||||
const renderer = new THREE.WebGLRenderer()
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
powerPreference: options.highPerformanceGpu ? 'high-performance' : 'default',
|
||||
})
|
||||
initWithRenderer(renderer.domElement)
|
||||
window.renderer = renderer
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1) // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
renderer.domElement.id = 'viewer-canvas'
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
// Create viewer
|
||||
const viewer: import('../prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers)
|
||||
const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers)
|
||||
window.viewer = viewer
|
||||
initPanoramaOptions(viewer)
|
||||
watchTexturepackInViewer(viewer)
|
||||
|
|
@ -154,13 +140,11 @@ const renderFrame = (time: DOMHighResTimeStamp) => {
|
|||
return
|
||||
}
|
||||
}
|
||||
stats?.begin()
|
||||
stats2?.begin()
|
||||
statsStart()
|
||||
viewer.update()
|
||||
renderer.render(viewer.scene, viewer.camera)
|
||||
postRenderFrameFn()
|
||||
stats?.end()
|
||||
stats2?.end()
|
||||
statsEnd()
|
||||
}
|
||||
renderFrame(performance.now())
|
||||
|
||||
|
|
@ -203,8 +187,8 @@ window.addEventListener('mousemove', onCameraMove, { capture: true })
|
|||
|
||||
|
||||
function hideCurrentScreens() {
|
||||
activeModalStacks['main-menu'] = activeModalStack
|
||||
replaceActiveModalStack('', [])
|
||||
activeModalStacks['main-menu'] = [...activeModalStack]
|
||||
insertActiveModalStack('', [])
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
|
@ -231,7 +215,6 @@ async function main() {
|
|||
}
|
||||
|
||||
let listeners = []
|
||||
let timeouts = []
|
||||
// only for dom listeners (no removeAllListeners)
|
||||
// todo refactor them out of connect fn instead
|
||||
const registerListener: import('./utilsTs').RegisterListener = (target, event, callback) => {
|
||||
|
|
@ -258,13 +241,6 @@ async function connect(connectOptions: {
|
|||
const p2pMultiplayer = !!connectOptions.peerId
|
||||
miscUiState.singleplayer = singeplayer
|
||||
miscUiState.flyingSquid = singeplayer || p2pMultiplayer
|
||||
const oldSetTimeout = window.setTimeout
|
||||
//@ts-expect-error
|
||||
window.setTimeout = (callback, ms) => {
|
||||
const id = oldSetTimeout.call(window, callback, ms)
|
||||
timeouts.push(id)
|
||||
return id
|
||||
}
|
||||
const { renderDistance, maxMultiplayerRenderDistance } = options
|
||||
const hostprompt = connectOptions.server
|
||||
const proxyprompt = connectOptions.proxy
|
||||
|
|
@ -289,16 +265,21 @@ async function connect(connectOptions: {
|
|||
}
|
||||
console.log(`connecting to ${host} ${port} with ${username}`)
|
||||
|
||||
hideCurrentScreens()
|
||||
setLoadingScreenStatus('Logging in')
|
||||
|
||||
let disconnected = false
|
||||
let bot: mineflayer.Bot
|
||||
const destroyAll = () => {
|
||||
// ensure bot cleanup
|
||||
viewer.resetAll()
|
||||
window.localServer = undefined
|
||||
|
||||
// simple variant, still buggy
|
||||
postRenderFrameFn = () => { }
|
||||
if (bot) {
|
||||
bot.end()
|
||||
bot.emit('end', '')
|
||||
bot.removeAllListeners()
|
||||
bot._client.removeAllListeners()
|
||||
bot._client = undefined
|
||||
|
|
@ -307,10 +288,6 @@ async function connect(connectOptions: {
|
|||
window.bot = bot = undefined
|
||||
}
|
||||
removeAllListeners()
|
||||
for (const timeout of timeouts) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeouts = []
|
||||
}
|
||||
const handleError = (err) => {
|
||||
console.log('Encountered error!', err)
|
||||
|
|
@ -350,14 +327,6 @@ async function connect(connectOptions: {
|
|||
if (proxy) {
|
||||
console.log(`using proxy ${proxy} ${proxyport}`)
|
||||
|
||||
// check proxy server availability for proper error message
|
||||
// todo fix correctly
|
||||
// try {
|
||||
// await fetch(`http://${proxy}:${proxyport}/api/vm/net`, { method: 'GET' })
|
||||
// } catch (err) {
|
||||
// console.error(err)
|
||||
// throw new Error(`Proxy server ${proxy}:${proxyport} is not available`)
|
||||
// }
|
||||
//@ts-expect-error
|
||||
net.setProxy({ hostname: proxy, port: proxyport })
|
||||
}
|
||||
|
|
@ -431,7 +400,13 @@ async function connect(connectOptions: {
|
|||
checkTimeoutInterval: 240 * 1000,
|
||||
noPongTimeout: 240 * 1000,
|
||||
closeTimeout: 240 * 1000,
|
||||
respawn: options.autoRespawn,
|
||||
async versionSelectedHook(client) {
|
||||
// todo keep in sync with esbuild preload, expose cache ideally
|
||||
if (client.version === '1.20.1') {
|
||||
// ignore cache hit
|
||||
versionsByMinecraftVersion.pc['1.20.1']!['dataVersion']++
|
||||
}
|
||||
await downloadMcData(client.version)
|
||||
setLoadingScreenStatus('Connecting to server')
|
||||
}
|
||||
|
|
@ -497,6 +472,8 @@ async function connect(connectOptions: {
|
|||
})
|
||||
|
||||
bot.on('end', (endReason) => {
|
||||
if (disconnected) return
|
||||
disconnected = true
|
||||
console.log('disconnected for', endReason)
|
||||
destroyAll()
|
||||
setLoadingScreenStatus(`You have been disconnected from the server. End reason: ${endReason}`, true)
|
||||
|
|
@ -512,9 +489,9 @@ async function connect(connectOptions: {
|
|||
setLoadingScreenStatus('Loading world')
|
||||
})
|
||||
|
||||
bot.once('spawn', () => {
|
||||
// don't use spawn event, player can be dead
|
||||
bot.once('health', () => {
|
||||
if (p2pConnectTimeout) clearTimeout(p2pConnectTimeout)
|
||||
// todo display notification if not critical
|
||||
const mcData = require('minecraft-data')(bot.version)
|
||||
|
||||
setLoadingScreenStatus('Placing blocks (starting viewer)')
|
||||
|
|
@ -525,7 +502,7 @@ async function connect(connectOptions: {
|
|||
|
||||
const center = bot.entity.position
|
||||
|
||||
const worldView: import('../prismarine-viewer/viewer/lib/worldView').WorldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center)
|
||||
const worldView: import('prismarine-viewer/viewer/lib/worldView').WorldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center)
|
||||
window.worldView = worldView
|
||||
setRenderDistance()
|
||||
|
||||
|
|
@ -559,7 +536,6 @@ async function connect(connectOptions: {
|
|||
window.Vec3 = Vec3
|
||||
window.pathfinder = pathfinder
|
||||
window.debugMenu = debugMenu
|
||||
window.renderer = renderer
|
||||
|
||||
initVR(bot, renderer, viewer)
|
||||
|
||||
|
|
@ -641,7 +617,7 @@ async function connect(connectOptions: {
|
|||
}
|
||||
screenTouches++
|
||||
if (screenTouches === 3) {
|
||||
window.dispatchEvent(new MouseEvent('mousedown', { button: 1, }))
|
||||
window.dispatchEvent(new MouseEvent('mousedown', { button: 1 }))
|
||||
}
|
||||
if (capturedPointer) {
|
||||
return
|
||||
|
|
@ -731,7 +707,6 @@ async function connect(connectOptions: {
|
|||
if (loadingScreen.hasError) return
|
||||
// remove loading screen, wait a second to make sure a frame has properly rendered
|
||||
setLoadingScreenStatus(undefined)
|
||||
hideCurrentScreens()
|
||||
void viewer.waitForChunksToRender().then(() => {
|
||||
console.log('All done and ready!')
|
||||
document.dispatchEvent(new Event('cypress-world-ready'))
|
||||
|
|
@ -754,7 +729,6 @@ window.addEventListener('keydown', (e) => {
|
|||
}
|
||||
})
|
||||
} else if (pointerLock.hasPointerLock) {
|
||||
document.exitPointerLock()
|
||||
if (options.autoExitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
|
|||
// on game load
|
||||
version = getVersion(bot.version)
|
||||
blockStates = await fetch(`blocksStates/${version}.json`).then(async res => res.json())
|
||||
getImage({ path: 'blocks', } as any)
|
||||
getImage({ path: 'invsprite', } as any)
|
||||
getImage({ path: 'blocks' } as any)
|
||||
getImage({ path: 'invsprite' } as any)
|
||||
mcData = MinecraftData(version)
|
||||
})
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ subscribe(activeModalStack, () => {
|
|||
const inventoryOpened = activeModalStack.at(-1)?.reactType === 'inventory'
|
||||
if (inventoryOpened) {
|
||||
const inv = showInventory(undefined, getImage, {}, bot)
|
||||
inv.canvas.style.zIndex = 10
|
||||
inv.canvas.style.zIndex = '10'
|
||||
inv.canvas.style.position = 'fixed'
|
||||
inv.canvas.style.inset = '0'
|
||||
// todo scaling
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-multi-spaces */
|
||||
// todo move from here
|
||||
|
||||
//@ts-format-ignore-region
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//@ts-check
|
||||
const { html, css, LitElement } = require('lit')
|
||||
const { commonCss, openURL } = require('./components/common')
|
||||
const { subscribe } = require('valtio')
|
||||
const { hideCurrentModal } = require('../globalState')
|
||||
const { getScreenRefreshRate } = require('../utils')
|
||||
const { subscribe } = require('valtio')
|
||||
const { options } = require('../optionsStorage')
|
||||
const { commonCss, openURL } = require('./components/common')
|
||||
|
||||
class AdvancedOptionsScreen extends LitElement {
|
||||
/** @type {null | number} */
|
||||
|
|
@ -55,8 +55,8 @@ class AdvancedOptionsScreen extends LitElement {
|
|||
<main>
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label=${`Always Show Mobile Controls: ${options.alwaysShowMobileControls ? 'ON' : 'OFF'}`} @pmui-click=${() => {
|
||||
options.alwaysShowMobileControls = !options.alwaysShowMobileControls
|
||||
}
|
||||
options.alwaysShowMobileControls = !options.alwaysShowMobileControls
|
||||
}
|
||||
}></pmui-button>
|
||||
<!-- todo rename button, also might be unstable -->
|
||||
<pmui-button pmui-width="150px" pmui-label="Guide: Disable VSync" @click=${() => openURL('https://gist.github.com/zardoy/6e5ce377d2b4c1e322e660973da069cd')}></pmui-button>
|
||||
|
|
@ -78,6 +78,9 @@ class AdvancedOptionsScreen extends LitElement {
|
|||
<pmui-slider pmui-width="150px" pmui-label="Touch Buttons Size" pmui-value="${options.touchButtonsSize}" pmui-type="%" pmui-min="20" pmui-max="100" @input=${(e) => {
|
||||
options.touchButtonsSize = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-button pmui-width="150px" pmui-label="${`Use Dedicated GPU: ${options.highPerformanceGpu ? 'ON' : 'OFF'}`}" title="Changing requires page reload. Only for those who have two GPUs e.g. on laptops" @pmui-click=${() => {
|
||||
options.highPerformanceGpu = !options.highPerformanceGpu
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
|
||||
<pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => hideCurrentModal()}></pmui-button>
|
||||
|
|
|
|||
|
|
@ -57,14 +57,15 @@ class BossBar extends LitElement {
|
|||
this.updateBar(this.bar)
|
||||
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="title">${this.title}</div>
|
||||
<div class="bossbar" style=${styleMap(this.bossBarStyles)}>
|
||||
<div class="fill" style=${styleMap(this.fillStyles)}></div>
|
||||
<div class="fill" style=${styleMap(this.div1Styles)}></div>
|
||||
<div class="fill" style=${styleMap(this.div2Styles)}></div>
|
||||
</div>
|
||||
</div>`
|
||||
<div class="container">
|
||||
<div class="title">${this.title}</div>
|
||||
<div class="bossbar" style=${styleMap(this.bossBarStyles)}>
|
||||
<div class="fill" style=${styleMap(this.fillStyles)}></div>
|
||||
<div class="fill" style=${styleMap(this.div1Styles)}></div>
|
||||
<div class="fill" style=${styleMap(this.div2Styles)}></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
setTitle (bar) {
|
||||
|
|
@ -121,9 +122,11 @@ class BossBars extends LitElement {
|
|||
}
|
||||
|
||||
render () {
|
||||
return html`<div class="bossBars" id="bossBars">
|
||||
return html`
|
||||
<div class="bossBars" id="bossBars">
|
||||
${[...this.bossBars.values()]}
|
||||
</div>`
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
init () {
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ class BreathBar extends LitElement {
|
|||
|
||||
const breaths = breathbar.children
|
||||
|
||||
for (let i = 0; i < breaths.length; i++) {
|
||||
breaths[i].classList.remove('full')
|
||||
breaths[i].classList.remove('half')
|
||||
for (const breath of breaths) {
|
||||
breath.classList.remove('full')
|
||||
breath.classList.remove('half')
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.ceil(hValue / 2); i++) {
|
||||
|
|
|
|||
|
|
@ -8,30 +8,33 @@ let audioContext
|
|||
const sounds = {}
|
||||
|
||||
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
|
||||
let loadingSounds = []
|
||||
const loadingSounds = []
|
||||
const convertedSounds = []
|
||||
async function loadSound (path) {
|
||||
loadingSounds.push(path)
|
||||
const res = await window.fetch(path)
|
||||
const data = await res.arrayBuffer()
|
||||
|
||||
// sounds[path] = await audioContext.decodeAudioData(data)
|
||||
sounds[path] = data
|
||||
loadingSounds.splice(loadingSounds.indexOf(path), 1)
|
||||
}
|
||||
|
||||
export async function playSound (path) {
|
||||
if (!audioContext) {
|
||||
audioContext = new window.AudioContext()
|
||||
for (const [soundName, sound] of Object.entries(sounds)) {
|
||||
sounds[soundName] = await audioContext.decodeAudioData(sound)
|
||||
}
|
||||
audioContext ??= new window.AudioContext()
|
||||
|
||||
for (const [soundName, sound] of Object.entries(sounds)) {
|
||||
if (convertedSounds.includes(soundName)) continue
|
||||
sounds[soundName] = await audioContext.decodeAudioData(sound)
|
||||
convertedSounds.push(soundName)
|
||||
}
|
||||
|
||||
const volume = options.volume / 100
|
||||
|
||||
if (loadingSounds.includes(path)) return
|
||||
const soundBuffer = sounds[path]
|
||||
if (!soundBuffer) throw new Error(`Sound ${path} not loaded`)
|
||||
if (!soundBuffer) {
|
||||
console.warn(`Sound ${path} not loaded`)
|
||||
return
|
||||
}
|
||||
|
||||
const gainNode = audioContext.createGain()
|
||||
const source = audioContext.createBufferSource()
|
||||
|
|
@ -151,22 +154,23 @@ class Button extends LitElement {
|
|||
|
||||
render () {
|
||||
return html`
|
||||
<button
|
||||
class="button"
|
||||
?disabled=${this.disabled}
|
||||
@click=${this.onBtnClick}
|
||||
style="width: ${this.width};"
|
||||
data-test-id=${this.testId}
|
||||
>
|
||||
<!-- todo self host icons -->
|
||||
${this.icon ? html`<iconify-icon class="icon" icon="${this.icon}"></iconify-icon>` : ''}
|
||||
${this.label}
|
||||
</button>`
|
||||
<button
|
||||
class="button"
|
||||
?disabled=${this.disabled}
|
||||
@click=${this.onBtnClick}
|
||||
style="width: ${this.width};"
|
||||
data-test-id=${this.testId}
|
||||
>
|
||||
<!-- todo self host icons -->
|
||||
${this.icon ? html`<iconify-icon class="icon" icon="${this.icon}"></iconify-icon>` : ''}
|
||||
${this.label}
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
onBtnClick (e) {
|
||||
playSound('button_click.mp3')
|
||||
this.dispatchEvent(new window.CustomEvent('pmui-click', { detail: e, }))
|
||||
this.dispatchEvent(new window.CustomEvent('pmui-click', { detail: e }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ class FoodBar extends LitElement {
|
|||
|
||||
const foods = foodbar.children
|
||||
|
||||
for (let i = 0; i < foods.length; i++) {
|
||||
foods[i].classList.remove('full')
|
||||
foods[i].classList.remove('half')
|
||||
for (const food of foods) {
|
||||
food.classList.remove('full')
|
||||
food.classList.remove('half')
|
||||
}
|
||||
|
||||
// if (d) this.onHungerUpdate()
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ class HealthBar extends LitElement {
|
|||
|
||||
const hearts = health.children
|
||||
|
||||
for (let i = 0; i < hearts.length; i++) {
|
||||
hearts[i].classList.remove('full')
|
||||
hearts[i].classList.remove('half')
|
||||
for (const heart of hearts) {
|
||||
heart.classList.remove('full')
|
||||
heart.classList.remove('half')
|
||||
}
|
||||
|
||||
if (d) this.onDamage()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
const widgetsTexture = require('minecraft-assets/minecraft-assets/data/1.16.4/gui/widgets.png')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const invsprite = require('../../invsprite.json')
|
||||
const { isGameActive, miscUiState, showModal } = require('../../globalState')
|
||||
|
||||
const widgetsTexture = require('minecraft-assets/minecraft-assets/data/1.16.4/gui/widgets.png')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { isProbablyIphone } = require('./common')
|
||||
|
||||
class Hotbar extends LitElement {
|
||||
|
|
@ -139,7 +139,7 @@ class Hotbar extends LitElement {
|
|||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!isGameActive(true)) return
|
||||
const numPressed = +(e.code.match(/Digit(\d)/)?.[1] ?? -1)
|
||||
const numPressed = +((/Digit(\d)/.exec(e.code))?.[1] ?? -1)
|
||||
if (numPressed < 1 || numPressed > 9) return
|
||||
this.reloadHotbarSelected(numPressed - 1)
|
||||
})
|
||||
|
|
@ -154,7 +154,7 @@ class Hotbar extends LitElement {
|
|||
const slotStack = slotEl.children[1]
|
||||
slotIcon.style['background-position-x'] = `-${sprite.x}px`
|
||||
slotIcon.style['background-position-y'] = `-${sprite.y}px`
|
||||
slotStack.innerText = newItem?.count > 1 ? newItem.count : ''
|
||||
slotStack.textContent = newItem?.count > 1 ? newItem.count : ''
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ class Hotbar extends LitElement {
|
|||
const slotStack = slotEl.children[1]
|
||||
slotIcon.style['background-position-x'] = `-${sprite.x}px`
|
||||
slotIcon.style['background-position-y'] = `-${sprite.y}px`
|
||||
slotStack.innerText = item?.count > 1 ? item.count : ''
|
||||
slotStack.textContent = item?.count > 1 ? item.count : ''
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,21 +188,21 @@ class Hotbar extends LitElement {
|
|||
<p id="hotbar-item-name">${this.activeItemName}</p>
|
||||
<div id="hotbar-selected"></div>
|
||||
<div id="hotbar-items-wrapper" @pointerdown=${(e) => {
|
||||
if (!e.target.id.startsWith('hotbar')) return
|
||||
const slot = +e.target.id.split('-')[1]
|
||||
this.reloadHotbarSelected(slot)
|
||||
}}>
|
||||
${Array.from({ length: 9 }).map((_, i) => html`
|
||||
<div class="hotbar-item" id="${`hotbar-${i}`}" @pointerdown=${(e) => {
|
||||
this.reloadHotbarSelected(i)
|
||||
}}>
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
`)}
|
||||
${miscUiState.currentTouch ? html`<div class="hotbar-item hotbar-more" @pointerdown=${() => {
|
||||
showModal({ reactType: 'inventory', })
|
||||
}}>` : undefined}
|
||||
if (!e.target.id.startsWith('hotbar')) return
|
||||
const slot = +e.target.id.split('-')[1]
|
||||
this.reloadHotbarSelected(slot)
|
||||
}}>
|
||||
${Array.from({ length: 9 }).map((_, i) => html`
|
||||
<div class="hotbar-item" id="${`hotbar-${i}`}" @pointerdown=${(e) => {
|
||||
this.reloadHotbarSelected(i)
|
||||
}}>
|
||||
<div class="item-icon"></div>
|
||||
<span class="item-stack"></span>
|
||||
</div>
|
||||
`)}
|
||||
${miscUiState.currentTouch ? html`<div class="hotbar-item hotbar-more" @pointerdown=${() => {
|
||||
showModal({ reactType: 'inventory' })
|
||||
}}>` : undefined}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -187,20 +187,20 @@ class Slider extends LitElement {
|
|||
value="${this.value}"
|
||||
?disabled=${!!this.disabled}
|
||||
@input=${(e) => {
|
||||
const range = e.target
|
||||
this.ratio = (range.value - range.min) / (range.max - range.min)
|
||||
this.value = range.value
|
||||
}}
|
||||
const range = e.target
|
||||
this.ratio = (range.value - range.min) / (range.max - range.min)
|
||||
this.value = range.value
|
||||
}}
|
||||
@pointerdown=${() => {
|
||||
window.addEventListener('pointerup', (e) => {
|
||||
this.dispatchEvent(new InputEvent('change'))
|
||||
}, {
|
||||
once: true,
|
||||
})
|
||||
}}
|
||||
@keyup=${() => {
|
||||
window.addEventListener('pointerup', (e) => {
|
||||
this.dispatchEvent(new InputEvent('change'))
|
||||
}}>
|
||||
}, {
|
||||
once: true,
|
||||
})
|
||||
}}
|
||||
@keyup=${() => {
|
||||
this.dispatchEvent(new InputEvent('change'))
|
||||
}}>
|
||||
<div class="disabled" title="${this.disabled}"></div>
|
||||
<div
|
||||
class="slider-thumb"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
const { isMobile } = require('./components/common')
|
||||
const { showModal, miscUiState } = require('../globalState')
|
||||
const { options, watchValue } = require('../optionsStorage')
|
||||
const { getGamemodeNumber } = require('../utils')
|
||||
const { isMobile } = require('./components/common')
|
||||
|
||||
export const guiIcons1_17_1 = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/icons.png')
|
||||
export const guiIcons1_16_4 = require('minecraft-assets/minecraft-assets/data/1.16.4/gui/icons.png')
|
||||
|
|
@ -277,7 +277,7 @@ class Hud extends LitElement {
|
|||
onHealthUpdate()
|
||||
|
||||
const onXpUpdate = () => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
this.shadowRoot.querySelector('#xp-bar-bg').firstElementChild.style.width = `${182 * bot.experience.progress}px`
|
||||
xpLabel.innerHTML = String(bot.experience.level)
|
||||
xpLabel.style.display = bot.experience.level > 0 ? 'block' : 'none'
|
||||
|
|
@ -307,19 +307,19 @@ class Hud extends LitElement {
|
|||
return html`
|
||||
<div class="mobile-top-btns" id="mobile-top">
|
||||
<button class="debug-btn" @pointerdown=${(e) => {
|
||||
window.dispatchEvent(new MouseEvent('mousedown', { button: 1, }))
|
||||
}}>Select</button>
|
||||
window.dispatchEvent(new MouseEvent('mousedown', { button: 1 }))
|
||||
}}>Select</button>
|
||||
<button class="debug-btn" @pointerdown=${(e) => {
|
||||
this.shadowRoot.getElementById('debug-overlay').showOverlay = !this.shadowRoot.getElementById('debug-overlay').showOverlay
|
||||
}}>F3</button>
|
||||
this.shadowRoot.getElementById('debug-overlay').showOverlay = !this.shadowRoot.getElementById('debug-overlay').showOverlay
|
||||
}}>F3</button>
|
||||
<button class="chat-btn" @pointerdown=${(e) => {
|
||||
e.stopPropagation()
|
||||
this.shadowRoot.querySelector('#chat').enableChat()
|
||||
}}></button>
|
||||
e.stopPropagation()
|
||||
this.shadowRoot.querySelector('#chat').enableChat()
|
||||
}}></button>
|
||||
<button class="pause-btn" @pointerdown=${(e) => {
|
||||
e.stopPropagation()
|
||||
showModal(document.getElementById('pause-screen'))
|
||||
}}></button>
|
||||
e.stopPropagation()
|
||||
showModal(document.getElementById('pause-screen'))
|
||||
}}></button>
|
||||
</div>
|
||||
|
||||
<pmui-debug-overlay id="debug-overlay"></pmui-debug-overlay>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss } = require('./components/common')
|
||||
const { hideCurrentModal } = require('../globalState')
|
||||
const { commonCss } = require('./components/common')
|
||||
|
||||
class KeyBindsScreen extends LitElement {
|
||||
static get styles () {
|
||||
|
|
@ -135,23 +135,23 @@ class KeyBindsScreen extends LitElement {
|
|||
<main>
|
||||
<div class="keymap-list">
|
||||
${this.keymaps.map((m, i) => html`
|
||||
<div class="keymap-entry">
|
||||
<span>${m.name}</span>
|
||||
<div class="keymap-entry">
|
||||
<span>${m.name}</span>
|
||||
|
||||
<div class="keymap-entry-btns">
|
||||
<pmui-button pmui-width="72px" pmui-label="${this.selected === i ? `> ${m.key} <` : m.key}" @pmui-click=${e => {
|
||||
e.target.setAttribute('pmui-label', `> ${m.key} <`)
|
||||
this.selected = i
|
||||
this.requestUpdate()
|
||||
}}></pmui-button>
|
||||
<pmui-button pmui-width="50px" ?pmui-disabled=${m.key === m.defaultKey} pmui-label="Reset" @pmui-click=${() => {
|
||||
this.keymaps[i].key = this.keymaps[i].defaultKey
|
||||
this.requestUpdate()
|
||||
this.selected = -1
|
||||
}}></pmui-button>
|
||||
<div class="keymap-entry-btns">
|
||||
<pmui-button pmui-width="72px" pmui-label="${this.selected === i ? `> ${m.key} <` : m.key}" @pmui-click=${e => {
|
||||
e.target.setAttribute('pmui-label', `> ${m.key} <`)
|
||||
this.selected = i
|
||||
this.requestUpdate()
|
||||
}}></pmui-button>
|
||||
<pmui-button pmui-width="50px" ?pmui-disabled=${m.key === m.defaultKey} pmui-label="Reset" @pmui-click=${() => {
|
||||
this.keymaps[i].key = this.keymaps[i].defaultKey
|
||||
this.requestUpdate()
|
||||
this.selected = -1
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
`)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
@ -163,8 +163,8 @@ class KeyBindsScreen extends LitElement {
|
|||
}
|
||||
|
||||
onResetAllPress () {
|
||||
for (let i = 0; i < this.keymaps.length; i++) {
|
||||
this.keymaps[i].key = this.keymaps[i].defaultKey
|
||||
for (const keymap of this.keymaps) {
|
||||
keymap.key = keymap.defaultKey
|
||||
}
|
||||
this.requestUpdate()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss } = require('./components/common')
|
||||
const { addPanoramaCubeMap } = require('../panorama')
|
||||
const { hideModal, activeModalStacks, activeModalStack, replaceActiveModalStack, miscUiState } = require('../globalState')
|
||||
const { hideModal, activeModalStacks, activeModalStack, insertActiveModalStack, miscUiState } = require('../globalState')
|
||||
const { guessProblem } = require('../guessProblem')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { resetLocalStorageWorld } = require('../browserfs')
|
||||
const { commonCss } = require('./components/common')
|
||||
|
||||
class LoadingErrorScreen extends LitElement {
|
||||
static get styles () {
|
||||
|
|
@ -58,9 +59,10 @@ class LoadingErrorScreen extends LitElement {
|
|||
|
||||
async statusRunner () {
|
||||
const array = ['.', '..', '...', '']
|
||||
const timer = ms => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
const timer = async ms => new Promise((resolve) => {setTimeout(resolve, ms)})
|
||||
|
||||
const load = async () => {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
for (let i = 0; true; i = ((i + 1) % array.length)) {
|
||||
this._loadingDots = array[i]
|
||||
await timer(500)
|
||||
|
|
@ -84,27 +86,22 @@ class LoadingErrorScreen extends LitElement {
|
|||
<p class="last-status">${this.lastStatus ? `Last status: ${this.lastStatus}` : this.lastStatus}</p></div>
|
||||
|
||||
${this.hasError
|
||||
? html`<div class="error-buttons"><pmui-button .hidden=${!this.maybeRecoverable} pmui-width="200px" pmui-label="Back" @pmui-click=${() => {
|
||||
this.hasError = false
|
||||
this.lastStatus = ''
|
||||
miscUiState.gameLoaded = false
|
||||
if (activeModalStacks['main-menu']) {
|
||||
replaceActiveModalStack('main-menu')
|
||||
} else {
|
||||
hideModal(undefined, undefined, { force: true })
|
||||
}
|
||||
document.getElementById('play-screen').style.display = 'block'
|
||||
addPanoramaCubeMap()
|
||||
}}></pmui-button><pmui-button .hidden=${!(miscUiState.singleplayer && fsState.inMemorySave)} pmui-width="200px" pmui-label="Reset world" @pmui-click=${() => {
|
||||
if (!confirm('Are you sure you want to delete all local world content?')) return
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/g.test(key) || key === '/') {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
window.location.reload()
|
||||
}}></pmui-button><pmui-button @pmui-click=${() => window.location.reload()} pmui-label="Full Reload" pmui-width="200px"></pmui-button></div>`
|
||||
: ''
|
||||
? html`<div class="error-buttons"><pmui-button .hidden=${!this.maybeRecoverable} pmui-width="200px" pmui-label="Back" @pmui-click=${() => {
|
||||
this.hasError = false
|
||||
this.lastStatus = ''
|
||||
miscUiState.gameLoaded = false
|
||||
if (activeModalStacks['main-menu']) {
|
||||
insertActiveModalStack('main-menu')
|
||||
} else {
|
||||
hideModal(undefined, undefined, { force: true })
|
||||
}
|
||||
addPanoramaCubeMap()
|
||||
}}></pmui-button><pmui-button .hidden=${!(miscUiState.singleplayer && fsState.inMemorySave)} pmui-width="200px" pmui-label="Reset world" @pmui-click=${() => {
|
||||
if (!confirm('Are you sure you want to delete all local world content?')) return
|
||||
resetLocalStorageWorld()
|
||||
window.location.reload()
|
||||
}}></pmui-button><pmui-button @pmui-click=${() => window.location.reload()} pmui-label="Full Reload" pmui-width="200px"></pmui-button></div>`
|
||||
: ''
|
||||
}
|
||||
`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,22 @@ class Notification extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.renderHtml = false
|
||||
let timeout
|
||||
subscribe(notification, () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
this.requestUpdate()
|
||||
if (!notification.show) return
|
||||
this.renderHtml = true
|
||||
if (!notification.autoHide) return
|
||||
timeout = setTimeout(() => {
|
||||
notification.show = false
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.renderHtml) return
|
||||
const show = notification.show && notification.message
|
||||
|
|
@ -45,22 +61,6 @@ class Notification extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.renderHtml = false
|
||||
let timeout
|
||||
subscribe(notification, () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
this.requestUpdate()
|
||||
if (!notification.show) return
|
||||
this.renderHtml = true
|
||||
if (!notification.autoHide) return
|
||||
timeout = setTimeout(() => {
|
||||
notification.show = false
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
.notification {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss, isMobile } = require('./components/common')
|
||||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { showModal, hideCurrentModal, isGameActive, miscUiState } = require('../globalState')
|
||||
const { toNumber, openFilePicker, setLoadingScreenStatus } = require('../utils')
|
||||
const { options, watchValue } = require('../optionsStorage')
|
||||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { getResourcePackName, uninstallTexturePack, resourcePackState } = require('../texturePack')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { commonCss, isMobile } = require('./components/common')
|
||||
|
||||
class OptionsScreen extends LitElement {
|
||||
static get styles () {
|
||||
|
|
@ -69,47 +69,47 @@ class OptionsScreen extends LitElement {
|
|||
<main>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Mouse Sensitivity X" pmui-value="${options.mouseSensX}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
options.mouseSensX = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.mouseSensX = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Mouse Sensitivity Y" pmui-value="${options.mouseSensY}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
options.mouseSensY = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.mouseSensY = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Width" pmui-value="${options.chatWidth}" pmui-min="0" pmui-max="320" pmui-type="px" @input=${(e) => {
|
||||
options.chatWidth = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.chatWidth = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Chat Height" pmui-value="${options.chatHeight}" pmui-min="0" pmui-max="180" pmui-type="px" @input=${(e) => {
|
||||
options.chatHeight = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.chatHeight = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Scale" pmui-value="${options.chatScale}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
options.chatScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.chatScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Sound Volume" pmui-value="${options.volume}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
options.volume = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.volume = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button .disabled=${true} pmui-width="150px" pmui-label="Key Binds" @pmui-click=${() => showModal(document.getElementById('keybinds-screen'))}></pmui-button>
|
||||
<pmui-slider pmui-label="Gui Scale" pmui-value="${options.guiScale}" pmui-min="1" pmui-max="4" pmui-type="" @change=${(e) => {
|
||||
options.guiScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.guiScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Render Distance" pmui-value="${options.renderDistance}" .disabled="${isGameActive(false) && !miscUiState.singleplayer ? 'Can be changed only from main menu for now' : undefined}" pmui-min="2" pmui-max="${miscUiState.singleplayer ? 16 : 6}" pmui-type=" chunks" @change=${(e) => {
|
||||
options.renderDistance = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.renderDistance = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Field of View" pmui-value="${options.fov}" pmui-min="30" pmui-max="110" pmui-type="" @input=${(e) => {
|
||||
options.fov = +e.target.value
|
||||
}}></pmui-slider>
|
||||
options.fov = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label=${'Advanced'} @pmui-click=${() => {
|
||||
showModal(document.querySelector('pmui-advanced-optionsscreen'))
|
||||
}
|
||||
showModal(document.querySelector('pmui-advanced-optionsscreen'))
|
||||
}
|
||||
}></pmui-button>
|
||||
<pmui-button pmui-width="150px" pmui-label=${'Mouse Raw Input: ' + (options.mouseRawInput ? 'ON' : 'OFF')} title="Wether to disable any mouse acceleration (MC does it by default)" @pmui-click=${() => {
|
||||
options.mouseRawInput = !options.mouseRawInput
|
||||
|
|
@ -117,7 +117,7 @@ class OptionsScreen extends LitElement {
|
|||
}></pmui-button>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button title="Auto Fullscreen allows you to use Ctrl+W and Escape without delays" .disabled="${!navigator['keyboard'] ? "Your browser doesn't support keyboard lock API" : undefined}" pmui-width="150px" pmui-label=${'Auto Fullscreen: ' + (options.autoFullScreen ? 'ON' : 'OFF')} title="Wether to disable any mouse acceleration (MC does it by default)" @pmui-click=${() => {
|
||||
<pmui-button title="Auto Fullscreen allows you to use Ctrl+W and Escape without delays" .disabled="${navigator['keyboard'] ? undefined : 'Your browser doesn\'t support keyboard lock API'}" pmui-width="150px" pmui-label=${'Auto Fullscreen: ' + (options.autoFullScreen ? 'ON' : 'OFF')} @pmui-click=${() => {
|
||||
options.autoFullScreen = !options.autoFullScreen
|
||||
}
|
||||
}></pmui-button>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { openURL } = require('./components/common')
|
||||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { hideCurrentModal, showModal, miscUiState } = require('../globalState')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { subscribe } = require('valtio')
|
||||
const { saveWorld } = require('../builtinCommands')
|
||||
const { notification } = require('./notification')
|
||||
const { disconnect } = require('../utils')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { closeWan, openToWanAndCopyJoinLink, getJoinLink } = require('../localServerMultiplayer')
|
||||
const { notification } = require('./notification')
|
||||
const { openURL } = require('./components/common')
|
||||
|
||||
class PauseScreen extends LitElement {
|
||||
static get styles () {
|
||||
|
|
@ -80,13 +80,15 @@ class PauseScreen extends LitElement {
|
|||
<pmui-button pmui-width="204px" pmui-label="Options" @pmui-click=${() => showModal(document.getElementById('options-screen'))}></pmui-button>
|
||||
<!-- todo use qr icon (full pixelarticons package) -->
|
||||
<!-- todo also display copy link button when opened -->
|
||||
${joinButton ? html`<div class="row">
|
||||
<pmui-button pmui-width="170px" pmui-label="${miscUiState.wanOpened ? "Close Wan" : "Copy Join Link"}" @pmui-click=${() => this.clickJoinLinkButton()}></pmui-button>
|
||||
<pmui-button style="height: 0;" pmui-icon="pixelarticons:dice" pmui-width="20px" pmui-label="" @pmui-click=${() => this.clickJoinLinkButton(true)}></pmui-button>
|
||||
</div>` : ''}
|
||||
${joinButton ? html`
|
||||
<div class="row">
|
||||
<pmui-button pmui-width="170px" pmui-label="${miscUiState.wanOpened ? 'Close Wan' : 'Copy Join Link'}" @pmui-click=${async () => this.clickJoinLinkButton()}></pmui-button>
|
||||
<pmui-button style="height: 0;" pmui-icon="pixelarticons:dice" pmui-width="20px" pmui-label="" @pmui-click=${async () => this.clickJoinLinkButton(true)}></pmui-button>
|
||||
</div>
|
||||
` : ''}
|
||||
<pmui-button pmui-width="204px" pmui-label="${localServer && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect'}" @pmui-click=${async () => {
|
||||
disconnect()
|
||||
}}></pmui-button>
|
||||
disconnect()
|
||||
}}></pmui-button>
|
||||
</main>
|
||||
`
|
||||
}
|
||||
|
|
@ -102,7 +104,7 @@ class PauseScreen extends LitElement {
|
|||
if (qr) {
|
||||
const joinLink = getJoinLink()
|
||||
miscUiState.currentDisplayQr = joinLink
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { commonCss } = require('./components/common')
|
||||
const { hideCurrentModal } = require('../globalState')
|
||||
const mineflayer = require('mineflayer')
|
||||
const viewerSupportedVersions = require('prismarine-viewer/viewer/supportedVersions.json')
|
||||
const { versionsByMinecraftVersion } = require('minecraft-data')
|
||||
const { hideCurrentModal } = require('../globalState')
|
||||
const { commonCss } = require('./components/common')
|
||||
|
||||
const fullySupporedVersions = viewerSupportedVersions
|
||||
const partiallySupportVersions = mineflayer.supportedVersions
|
||||
|
|
@ -84,7 +85,7 @@ class PlayScreen extends LitElement {
|
|||
super()
|
||||
this.version = ''
|
||||
// todo set them sooner add indicator
|
||||
window.fetch('config.json').then(res => res.json()).then(c => c, (error) => {
|
||||
window.fetch('config.json').then(async res => res.json()).then(c => c, (error) => {
|
||||
console.error('Failed to load config.json', error)
|
||||
return {}
|
||||
}).then(config => {
|
||||
|
|
@ -100,7 +101,7 @@ class PlayScreen extends LitElement {
|
|||
}
|
||||
|
||||
this.server = getParam('server', 'ip') ?? config.defaultHost
|
||||
this.serverport = getParam('serverport', false) ?? config.defaultHostPort ?? 25565
|
||||
this.serverport = getParam('serverport', false) ?? config.defaultHostPort ?? 25_565
|
||||
this.proxy = getParam('proxy') ?? config.defaultProxy
|
||||
this.proxyport = getParam('proxyport', false) ?? (!config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443)
|
||||
this.version = getParam('version') || (window.localStorage.getItem('version') ?? config.defaultVersion)
|
||||
|
|
@ -175,7 +176,7 @@ class PlayScreen extends LitElement {
|
|||
pmui-id="botversion"
|
||||
pmui-value="${this.version}"
|
||||
pmui-inputmode="decimal"
|
||||
state="${this.version && (fullySupporedVersions.includes(/** @type {any} */(this.version)) ? '' : /* TODO improve check: check exact including all */ partiallySupportVersions.some(v => this.version.startsWith(v)) ? 'warning' : 'invalid')}"
|
||||
state="${this.version && (fullySupporedVersions.includes(/** @type {any} */(this.version)) ? '' : Object.keys(versionsByMinecraftVersion.pc).includes(this.version) ? 'warning' : 'invalid')}"
|
||||
.autocompleteValues=${fullySupporedVersions}
|
||||
@input=${e => { this.version = e.target.value }}
|
||||
></pmui-editbox>
|
||||
|
|
@ -203,7 +204,7 @@ class PlayScreen extends LitElement {
|
|||
this.dispatchEvent(new window.CustomEvent('connect', {
|
||||
detail: {
|
||||
server: `${this.server}:${this.serverport}`,
|
||||
proxy: `${this.proxy}${this.proxy !== '' ? `:${this.proxyport}` : ''}`,
|
||||
proxy: `${this.proxy}${this.proxy === '' ? '' : `:${this.proxyport}`}`,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
botVersion: this.version
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
const { openWorldDirectory, openWorldZip } = require('../browserfs')
|
||||
const { showModal } = require('../globalState')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { openURL } = require('./components/common')
|
||||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
const fs = require('fs')
|
||||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
|
||||
const mcImage = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/title/minecraft.png')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { showModal } = require('../globalState')
|
||||
const { openWorldDirectory, openWorldZip } = require('../browserfs')
|
||||
const { options } = require('../optionsStorage')
|
||||
const defaultLocalServerOptions = require('../defaultLocalServerOptions')
|
||||
const { openFilePicker } = require('../utils')
|
||||
const { openURL } = require('./components/common')
|
||||
|
||||
// const SUPPORT_WORLD_LOADING = !!window.showDirectoryPicker
|
||||
const SUPPORT_WORLD_LOADING = true
|
||||
|
|
@ -121,14 +121,6 @@ class TitleScreen extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
reload () {
|
||||
navigator.serviceWorker.getRegistration().then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.versionStatus = ''
|
||||
|
|
@ -147,6 +139,14 @@ class TitleScreen extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
reload () {
|
||||
navigator.serviceWorker.getRegistration().then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="game-title">
|
||||
|
|
@ -160,25 +160,25 @@ class TitleScreen extends LitElement {
|
|||
<pmui-button pmui-width="200px" pmui-label="Connect to server" pmui-test-id="connect-screen-button" @pmui-click=${() => showModal(document.getElementById('play-screen'))}></pmui-button>
|
||||
<div style="display:flex;justify-content: space-between;">
|
||||
<pmui-button pmui-width="${SUPPORT_WORLD_LOADING ? '170px' : '200px'}" pmui-test-id="singleplayer-button" pmui-label="Singleplayer" @pmui-click=${() => {
|
||||
this.style.display = 'none'
|
||||
fsState.isReadonly = false
|
||||
fsState.syncFs = true
|
||||
fsState.inMemorySave = true
|
||||
const notFirstTime = fs.existsSync('./world/level.dat')
|
||||
if (notFirstTime && !options.localServerOptions.version) {
|
||||
options.localServerOptions.version = '1.16.1' // legacy version
|
||||
} else {
|
||||
options.localServerOptions.version ??= defaultLocalServerOptions.version
|
||||
}
|
||||
this.dispatchEvent(new window.CustomEvent('singleplayer', {}))
|
||||
}}></pmui-button>
|
||||
this.style.display = 'none'
|
||||
fsState.isReadonly = false
|
||||
fsState.syncFs = true
|
||||
fsState.inMemorySave = true
|
||||
const notFirstTime = fs.existsSync('./world/level.dat')
|
||||
if (notFirstTime && !options.localServerOptions.version) {
|
||||
options.localServerOptions.version = '1.16.1' // legacy version
|
||||
} else {
|
||||
options.localServerOptions.version ??= defaultLocalServerOptions.version
|
||||
}
|
||||
this.dispatchEvent(new window.CustomEvent('singleplayer', {}))
|
||||
}}></pmui-button>
|
||||
${SUPPORT_WORLD_LOADING ? html`<pmui-button pmui-test-id="select-file-folder" pmui-icon="pixelarticons:folder" pmui-width="20px" pmui-label="" @pmui-click=${({ detail: e }) => {
|
||||
if (!!window.showDirectoryPicker && !e.shiftKey) {
|
||||
openWorldDirectory()
|
||||
} else {
|
||||
openFilePicker()
|
||||
}
|
||||
}}></pmui-button>` : ''}
|
||||
if (!!window.showDirectoryPicker && !e.shiftKey) {
|
||||
openWorldDirectory()
|
||||
} else {
|
||||
openFilePicker()
|
||||
}
|
||||
}}></pmui-button>` : ''}
|
||||
</div>
|
||||
<pmui-button pmui-width="200px" pmui-label="Options" @pmui-click=${() => showModal(document.getElementById('options-screen'))}></pmui-button>
|
||||
<div class="menu-row">
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const defaultOptions = {
|
|||
guiScale: 3,
|
||||
autoRequestCompletions: true,
|
||||
touchButtonsSize: 40,
|
||||
highPerformanceGpu: false,
|
||||
|
||||
frameLimit: false as number | false,
|
||||
alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null,
|
||||
|
|
@ -37,7 +38,10 @@ const defaultOptions = {
|
|||
preferLoadReadonly: false,
|
||||
disableLoadPrompts: false,
|
||||
guestUsername: 'guest',
|
||||
askGuestName: true
|
||||
askGuestName: true,
|
||||
|
||||
// advanced bot options
|
||||
autoRespawn: false
|
||||
}
|
||||
|
||||
export type AppOptions = typeof defaultOptions
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//@ts-check
|
||||
|
||||
import { join } from 'path'
|
||||
import { fromTexturePackPath, resourcePackState } from './texturePack'
|
||||
import fs from 'fs'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { fromTexturePackPath, resourcePackState } from './texturePack'
|
||||
import { options } from './optionsStorage'
|
||||
|
||||
let panoramaCubeMap
|
||||
|
|
@ -68,7 +68,7 @@ export async function addPanoramaCubeMap () {
|
|||
const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000)
|
||||
|
||||
const loader = new THREE.TextureLoader()
|
||||
let panorMaterials = []
|
||||
const panorMaterials = []
|
||||
await updateResourcePackSupportPanorama()
|
||||
for (const file of panoramaFiles) {
|
||||
panorMaterials.push(new THREE.MeshBasicMaterial({
|
||||
|
|
@ -111,4 +111,5 @@ export function removePanorama () {
|
|||
viewer.camera = new THREE.PerspectiveCamera(options.fov, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||
viewer.camera.updateProjectionMatrix()
|
||||
viewer.scene.remove(panoramaCubeMap)
|
||||
panoramaCubeMap = null
|
||||
}
|
||||
|
|
|
|||
76
src/react/DeathScreen.tsx
Normal file
76
src/react/DeathScreen.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { useEffect } from 'react'
|
||||
import './deathScreen.css'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { disconnect } from '../utils'
|
||||
import { MessageFormatPart, formatMessage } from '../botUtils'
|
||||
import { options } from '../optionsStorage'
|
||||
import { hideModal, showModal } from '../globalState'
|
||||
import MessageFormatted from './MessageFormatted'
|
||||
|
||||
const dieReasonProxy = proxy({ value: null as MessageFormatPart[] | null })
|
||||
|
||||
export default () => {
|
||||
const { value: dieReasonMessage } = useSnapshot(dieReasonProxy)
|
||||
|
||||
useEffect(() => {
|
||||
type DeathEvent = {
|
||||
playerId: number
|
||||
entityId: number
|
||||
message: string
|
||||
}
|
||||
|
||||
bot._client.on('death_combat_event', (data: DeathEvent) => {
|
||||
try {
|
||||
if (data.playerId !== bot.entity.id) return
|
||||
const messageParsed = JSON.parse(data.message)
|
||||
const parts = formatMessage(messageParsed)
|
||||
dieReasonProxy.value = parts
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
bot.on('death', () => {
|
||||
if (dieReasonProxy.value) return
|
||||
dieReasonProxy.value = []
|
||||
})
|
||||
|
||||
bot.on('respawn', () => {
|
||||
// todo don't close too early, instead wait for health event and make button disabled?
|
||||
dieReasonProxy.value = null
|
||||
})
|
||||
|
||||
if (bot.health === 0) {
|
||||
dieReasonProxy.value = []
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (dieReasonProxy.value) {
|
||||
showModal({ reactType: 'death-screen' })
|
||||
} else {
|
||||
hideModal({ reactType: 'death-screen' })
|
||||
}
|
||||
}, [dieReasonMessage])
|
||||
|
||||
if (!dieReasonMessage || options.autoRespawn) return null
|
||||
|
||||
return (
|
||||
<div className='deathScreen-container'>
|
||||
<div className="deathScreen">
|
||||
<h1 className='deathScreen-title'>You Died!</h1>
|
||||
<h5 className='deathScreen-reason'>
|
||||
<MessageFormatted parts={dieReasonMessage} />
|
||||
</h5>
|
||||
<div className='deathScreen-buttons-grouped'>
|
||||
<pmui-button pmui-label="Respawn" onClick={() => {
|
||||
console.log('respawn')
|
||||
bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 0 } : { actionId: 0 })
|
||||
}} />
|
||||
<pmui-button pmui-label="Disconnnect" onClick={() => {
|
||||
disconnect()
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
71
src/react/MessageFormatted.tsx
Normal file
71
src/react/MessageFormatted.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { MessageFormatPart } from '../botUtils'
|
||||
|
||||
const MessagePart = ({ part }: { part: MessageFormatPart }) => {
|
||||
const { color, italic, bold, underlined, strikethrough, text } = part
|
||||
|
||||
const applyStyles = [
|
||||
color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${getColorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : messageFormatStylesMap.white,
|
||||
italic && messageFormatStylesMap.italic,
|
||||
bold && messageFormatStylesMap.bold,
|
||||
italic && messageFormatStylesMap.italic,
|
||||
underlined && messageFormatStylesMap.underlined,
|
||||
strikethrough && messageFormatStylesMap.strikethrough
|
||||
].filter(Boolean)
|
||||
|
||||
return <span style={parseInlineStyle(applyStyles.join(' '))}>{text}</span>
|
||||
}
|
||||
|
||||
export default ({ parts }: { parts: readonly MessageFormatPart[] }) => {
|
||||
return (
|
||||
<span>
|
||||
{parts.map((part, i) => <MessagePart key={i} part={part} />)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const colorF = (color) => {
|
||||
return color.trim().startsWith('#') ? `color:${color}` : messageFormatStylesMap[color] ?? undefined
|
||||
}
|
||||
|
||||
export function getColorShadow (hex, dim = 0.25) {
|
||||
const color = parseInt(hex.replace('#', ''), 16)
|
||||
|
||||
const r = Math.trunc((color >> 16 & 0xFF) * dim)
|
||||
const g = Math.trunc((color >> 8 & 0xFF) * dim)
|
||||
const b = Math.trunc((color & 0xFF) * dim)
|
||||
|
||||
const f = (c) => ('00' + c.toString(16)).slice(-2)
|
||||
return `#${f(r)}${f(g)}${f(b)}`
|
||||
}
|
||||
|
||||
function parseInlineStyle(style: string): Record<string, any> {
|
||||
const template = document.createElement('template')
|
||||
template.setAttribute('style', style)
|
||||
return Object.fromEntries(Object.entries(template.style)
|
||||
.filter(([ key ]) => !/^\d+$/.test(key))
|
||||
.filter(([ , value ]) => Boolean(value))
|
||||
.map(([ key, value ]) => [key, value]))
|
||||
}
|
||||
|
||||
export const messageFormatStylesMap = {
|
||||
black: 'color:#000000',
|
||||
dark_blue: 'color:#0000AA',
|
||||
dark_green: 'color:#00AA00',
|
||||
dark_aqua: 'color:#00AAAA',
|
||||
dark_red: 'color:#AA0000',
|
||||
dark_purple: 'color:#AA00AA',
|
||||
gold: 'color:#FFAA00',
|
||||
gray: 'color:#AAAAAA',
|
||||
dark_gray: 'color:#555555',
|
||||
blue: 'color:#5555FF',
|
||||
green: 'color:#55FF55',
|
||||
aqua: 'color:#55FFFF',
|
||||
red: 'color:#FF5555',
|
||||
light_purple: 'color:#FF55FF',
|
||||
yellow: 'color:#FFFF55',
|
||||
white: 'color:#FFFFFF',
|
||||
bold: 'font-weight:900',
|
||||
strikethrough: 'text-decoration:line-through',
|
||||
underlined: 'text-decoration:underline',
|
||||
italic: 'font-style:italic'
|
||||
}
|
||||
33
src/react/deathScreen.css
Normal file
33
src/react/deathScreen.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.deathScreen-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.deathScreen {
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.deathScreen-title {
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.deathScreen-reason {
|
||||
color: white;
|
||||
margin-bottom: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.deathScreen-buttons-grouped {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@ import { renderToDom } from '@zardoy/react-util'
|
|||
|
||||
import { LeftTouchArea, RightTouchArea, useUsingTouch, useInterfaceState } from '@dimaka/interface'
|
||||
import { css } from '@emotion/css'
|
||||
// import DeathScreen from './react/DeathScreen'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import DeathScreen from './react/DeathScreen'
|
||||
import { contro } from './controls'
|
||||
import { activeModalStack, isGameActive, miscUiState } from './globalState'
|
||||
import { options, watchValue } from './optionsStorage'
|
||||
|
|
@ -31,11 +31,11 @@ useInterfaceState.setState({
|
|||
if (!bot) return
|
||||
if (state === 0) {
|
||||
for (const action of actionAndState) {
|
||||
contro.pressedKeyOrButtonChanged({code: action[2],}, false)
|
||||
contro.pressedKeyOrButtonChanged({ code: action[2] }, false)
|
||||
}
|
||||
} else {
|
||||
//@ts-expect-error
|
||||
contro.pressedKeyOrButtonChanged({code: actionAndState[2],}, true)
|
||||
contro.pressedKeyOrButtonChanged({ code: actionAndState[2] }, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -83,12 +83,16 @@ function useIsBotAvailable() {
|
|||
return isGameActive(false)
|
||||
}
|
||||
|
||||
const Portal = ({ children, to }) => {
|
||||
return createPortal(children, to)
|
||||
}
|
||||
|
||||
const DisplayQr = () => {
|
||||
const { currentDisplayQr } = useSnapshot(miscUiState)
|
||||
|
||||
if (!currentDisplayQr) return null
|
||||
|
||||
return createPortal(<div
|
||||
return <div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
|
|
@ -104,9 +108,8 @@ const DisplayQr = () => {
|
|||
miscUiState.currentDisplayQr = null
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG size={384} value={currentDisplayQr} style={{display: 'block', border: '2px solid black',}} />
|
||||
</div>, document.body)
|
||||
|
||||
<QRCodeSVG size={384} value={currentDisplayQr} style={{ display: 'block', border: '2px solid black' }} />
|
||||
</div>
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
|
|
@ -114,6 +117,10 @@ const App = () => {
|
|||
if (!isBotAvailable) return null
|
||||
|
||||
return <div>
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
{/* apply scaling */}
|
||||
<DeathScreen />
|
||||
</Portal>
|
||||
<DisplayQr />
|
||||
<TouchControls />
|
||||
</div>
|
||||
|
|
|
|||
54
src/rightTopStats.ts
Normal file
54
src/rightTopStats.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import Stats from 'stats.js'
|
||||
import StatsGl from 'stats-gl'
|
||||
import { isCypress } from './utils'
|
||||
|
||||
const stats = new Stats()
|
||||
const stats2 = new Stats()
|
||||
// in my case values are good: gpu: < 0.5, cpu < 0.15
|
||||
const statsGl = new StatsGl()
|
||||
stats2.showPanel(2)
|
||||
|
||||
let total = 0
|
||||
const addStat = (dom, size = 80) => {
|
||||
dom.style.left = ''
|
||||
dom.style.right = `${total}px`
|
||||
dom.style.width = '80px'
|
||||
document.body.appendChild(dom)
|
||||
total += size
|
||||
}
|
||||
addStat(stats.dom)
|
||||
addStat(stats2.dom)
|
||||
addStat(statsGl.container)
|
||||
|
||||
const hideStats = localStorage.hideStats || isCypress()
|
||||
if (hideStats) {
|
||||
stats.dom.style.display = 'none'
|
||||
stats2.dom.style.display = 'none'
|
||||
statsGl.container.style.display = 'none'
|
||||
}
|
||||
|
||||
export const initWithRenderer = (canvas) => {
|
||||
if (hideStats) return
|
||||
statsGl.init(canvas)
|
||||
statsGl.container.style.display = 'flex'
|
||||
statsGl.container.style.justifyContent = 'flex-end'
|
||||
let i = 0
|
||||
for (const _child of statsGl.container.children) {
|
||||
const child = _child as HTMLElement
|
||||
if (i++ === 0) {
|
||||
child.style.display = 'none'
|
||||
}
|
||||
child.style.position = ''
|
||||
}
|
||||
}
|
||||
|
||||
export const statsStart = () => {
|
||||
stats.begin()
|
||||
stats2.begin()
|
||||
statsGl.begin()
|
||||
}
|
||||
export const statsEnd = () => {
|
||||
stats.end()
|
||||
stats2.end()
|
||||
statsGl.end()
|
||||
}
|
||||
14
src/utils.ts
14
src/utils.ts
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ export const setLoadingScreenStatus = function (status: string | undefined, isEr
|
|||
|
||||
if (status === undefined) {
|
||||
loadingScreen.status = ''
|
||||
hideModal({ elem: loadingScreen, }, null, { force: true })
|
||||
hideModal({ elem: loadingScreen }, null, { force: true })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ export const disconnect = async () => {
|
|||
} else {
|
||||
// workaround bot.end doesn't end the socket and emit end event
|
||||
bot.end()
|
||||
bot._client.socket.end()
|
||||
bot._client.socket?.end?.()
|
||||
}
|
||||
bot._client.emit('end', 'You left the server')
|
||||
miscUiState.gameLoaded = false
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@
|
|||
"allowUnreachableCode": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
// this the only options that allows smooth transition from js to ts (by not dropping types from js files)
|
||||
// however might need to consider includeing *only needed libraries* instead of using this
|
||||
"maxNodeModuleJsDepth": 1
|
||||
// "strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue