prismarine-web-client-mirror/index.js

444 lines
13 KiB
JavaScript
Raw Normal View History

/* global THREE */
require('./lib/menu')
2021-03-14 13:24:09 -04:00
require('./lib/loading_screen')
2021-03-14 14:33:41 -04:00
require('./lib/hotbar')
2021-03-14 15:34:35 -04:00
require('./lib/chat')
2021-03-14 16:48:01 -04:00
require('./lib/crosshair')
require('./lib/playerlist')
require('./lib/debugmenu')
const net = require('net')
2021-02-27 17:12:11 -05:00
// Workaround for process.versions.node not existing in the browser
process.versions.node = '14.0.0'
const mineflayer = require('mineflayer')
const { WorldView, Viewer } = require('prismarine-viewer/viewer')
2021-03-12 19:14:17 -05:00
const pathfinder = require('mineflayer-pathfinder')
2021-03-12 20:19:07 -05:00
const { Vec3 } = require('vec3')
2021-02-27 17:12:11 -05:00
global.THREE = require('three')
2021-03-21 18:13:32 -04:00
const { initVR } = require('./lib/vr')
2021-03-03 21:11:55 -05:00
2021-03-20 21:54:10 -04:00
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration)
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError)
})
})
}
const maxPitch = 0.5 * Math.PI
const minPitch = -0.5 * Math.PI
2021-03-20 11:12:20 -04:00
// 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)
const textures = []
const loader = new THREE.TextureLoader()
let breakStartTime
2021-03-20 11:12:20 -04:00
// Menu panorama background
function getPanoramaMesh () {
const geometry = new THREE.SphereGeometry(500, 60, 40)
geometry.scale(-1, 1, 1)
const texture = new THREE.TextureLoader().load('title_blured.jpg')
const material = new THREE.MeshBasicMaterial({ map: texture })
const mesh = new THREE.Mesh(geometry, material)
mesh.rotation.y = Math.PI
mesh.onBeforeRender = () => {
mesh.rotation.y += 0.0005
mesh.rotation.x = -Math.sin(mesh.rotation.y * 3) * 0.3
}
return mesh
}
function removePanorama () {
viewer.scene.remove(panoramaMesh)
panoramaMesh = null
}
let panoramaMesh = getPanoramaMesh()
viewer.scene.add(panoramaMesh)
// Browser animation loop
const animate = () => {
window.requestAnimationFrame(animate)
viewer.update()
renderer.render(viewer.scene, viewer.camera)
}
animate()
window.addEventListener('resize', () => {
viewer.camera.aspect = window.innerWidth / window.innerHeight
viewer.camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
2021-02-27 17:12:11 -05:00
async function main () {
const showEl = (str) => { document.getElementById(str).style = 'display:block' }
const menu = document.getElementById('prismarine-menu')
menu.addEventListener('connect', e => {
const options = e.detail
menu.style = 'display: none;'
showEl('hotbar')
showEl('crosshair')
showEl('chatbox')
showEl('loading-background')
showEl('playerlist')
showEl('debugmenu')
2021-03-20 11:12:20 -04:00
removePanorama()
connect(options)
})
}
async function connect (options) {
2021-03-14 13:24:09 -04:00
const loadingScreen = document.getElementById('loading-background')
2021-03-14 14:33:41 -04:00
const hotbar = document.getElementById('hotbar')
2021-03-14 15:34:35 -04:00
const chat = document.getElementById('chatbox')
const playerList = document.getElementById('playerlist')
const debugMenu = document.getElementById('debugmenu')
2021-03-14 13:24:09 -04:00
2021-02-27 17:12:11 -05:00
const viewDistance = 6
const hostprompt = options.server
const proxyprompt = options.proxy
const username = options.username
const password = options.password
let host, port, proxy, proxyport
if (!hostprompt.includes(':')) {
host = hostprompt
port = 25565
} else {
[host, port] = hostprompt.split(':')
port = parseInt(port, 10)
}
if (!proxyprompt.includes(':')) {
proxy = proxyprompt
proxyport = undefined
} else {
[proxy, proxyport] = proxyprompt.split(':')
proxyport = parseInt(proxyport, 10)
}
2021-02-27 17:12:11 -05:00
console.log(`connecting to ${host} ${port} with ${username}`)
if (proxy) {
console.log(`using proxy ${proxy} ${proxyport}`)
net.setProxy({ hostname: proxy, port: proxyport })
}
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Logging in'
2021-02-27 17:12:11 -05:00
const bot = mineflayer.createBot({
host,
port,
username,
2021-03-12 20:35:07 -05:00
password,
viewDistance: 'tiny',
2021-03-12 20:51:45 -05:00
checkTimeoutInterval: 240 * 1000,
noPongTimeout: 240 * 1000,
closeTimeout: 240 * 1000
2021-02-27 17:12:11 -05:00
})
2021-03-14 14:33:41 -04:00
hotbar.bot = bot
debugMenu.bot = bot
2021-03-14 14:33:41 -04:00
bot.on('error', (err) => {
console.log('Encountered error!', err)
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Error encountered. Please reload the page'
})
2021-03-12 20:27:03 -05:00
bot.on('kicked', (kickReason) => {
console.log('User was kicked!', kickReason)
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'The Minecraft server kicked you. Please reload the page to rejoin'
})
2021-03-12 20:27:03 -05:00
bot.on('end', (endReason) => {
console.log('disconnected for', endReason)
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'You have been disconnected from the server. Please reload the page to rejoin'
})
bot.once('login', () => {
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Loading world...'
2021-02-27 17:12:11 -05:00
})
bot.once('spawn', () => {
2021-03-12 19:14:17 -05:00
const mcData = require('minecraft-data')(bot.version)
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Placing blocks (starting viewer)...'
2021-02-27 17:12:11 -05:00
console.log('bot spawned - starting viewer')
const version = bot.version
const center = bot.entity.position
const worldView = new WorldView(bot.world, viewDistance, center)
2021-03-14 15:34:35 -04:00
chat.init(bot._client, renderer)
playerList.init(bot)
2021-02-27 17:12:11 -05:00
viewer.setVersion(version)
2021-03-14 14:33:41 -04:00
hotbar.viewerVersion = viewer.version
2021-03-12 19:14:17 -05:00
window.worldView = worldView
window.bot = bot
window.mcData = mcData
window.viewer = viewer
window.Vec3 = Vec3
window.pathfinder = pathfinder
window.debugMenu = debugMenu
2021-03-18 03:28:47 -04:00
window.renderer = renderer
window.settings = {
mouseSensXValue: window.localStorage.getItem('mouseSensX') ?? 0.005,
mouseSensYValue: window.localStorage.getItem('mouseSensY') ?? 0.005,
set mouseSensX (v) { this.mouseSensXValue = v; window.localStorage.setItem('mouseSensX', v) },
set mouseSensY (v) { this.mouseSensYValue = v; window.localStorage.setItem('mouseSensY', v) },
get mouseSensX () { return this.mouseSensXValue },
get mouseSensY () { return this.mouseSensYValue }
}
2021-03-12 19:14:17 -05:00
2021-03-21 18:13:32 -04:00
initVR(bot, renderer, viewer)
2021-03-12 22:35:11 -05:00
// Link WorldView and Viewer
viewer.listen(worldView)
2021-02-27 17:12:11 -05:00
worldView.listenToBot(bot)
worldView.init(bot.entity.position)
// Create cursor mesh
const boxGeometry = new THREE.BoxBufferGeometry(1.001, 1.001, 1.001)
const cursorMesh = new THREE.LineSegments(new THREE.EdgesGeometry(boxGeometry), new THREE.LineBasicMaterial({ color: 0 }))
viewer.scene.add(cursorMesh)
function updateCursor () {
const cursorBlock = bot.blockAtCursor()
debugMenu.cursorBlock = cursorBlock
if (!cursorBlock || !bot.canDigBlock(cursorBlock)) {
cursorMesh.visible = false
return
} else {
cursorMesh.visible = true
}
cursorMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5)
}
// Create block break mesh
const material = new THREE.MeshBasicMaterial({
transparent: true,
alphaTest: 0.1
})
for (let i = 0; i < 10; i++) {
const texture = loader.load('textures/' + viewer.version + '/blocks/destroy_stage_' + i + '.png')
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestFilter
textures.push(texture)
}
const geometry = new THREE.BoxGeometry(1.001, 1.001, 1.001)
const blockBreakMesh = new THREE.Mesh(geometry, material)
viewer.scene.add(blockBreakMesh)
blockBreakMesh.visible = false
async function updateBreakMesh (x, y, z, time) {
const thisBreakStartTime = new Date().getTime()
breakStartTime = thisBreakStartTime
console.log('updating break mesh')
blockBreakMesh.position.set(x + 0.5, y + 0.5, z + 0.5)
// eslint-disable-next-line promise/param-names
const timer = ms => new Promise(res => setTimeout(res, ms))
const animate = async () => {
for (let i = 0; i < 10; i++) {
if (thisBreakStartTime !== breakStartTime) break
await timer(time / 10)
console.log('At stage ' + i)
blockBreakMesh.material.map = textures[i]
blockBreakMesh.visible = true
}
hideBreakMesh()
updateCursor()
}
animate()
}
function hideBreakMesh () {
console.log('hiding break mesh & canceling loop')
breakStartTime = new Date().getTime()
blockBreakMesh.visible = false
}
bot.on('diggingAborted', () => {
console.log('digging aborted')
hideBreakMesh()
keepMouseDownAction = false
})
bot.on('diggingCompleted', () => {
console.log('digging completed')
hideBreakMesh()
})
// Bot position callback
2021-02-27 17:12:11 -05:00
function botPosition () {
viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
worldView.updatePosition(bot.entity.position)
updateCursor()
2021-02-27 17:12:11 -05:00
}
bot.on('move', botPosition)
2021-03-21 18:13:32 -04:00
botPosition()
2021-02-27 17:12:11 -05:00
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Setting callbacks...'
2021-02-27 17:12:11 -05:00
function moveCallback (e) {
bot.entity.pitch -= e.movementY * window.settings.mouseSensYValue
bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch))
bot.entity.yaw -= e.movementX * window.settings.mouseSensXValue
viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch)
updateCursor()
2021-02-27 17:12:11 -05:00
}
2021-03-01 19:00:57 -05:00
2021-02-27 17:12:11 -05:00
function changeCallback () {
if (document.pointerLockElement === renderer.domElement ||
document.mozPointerLockElement === renderer.domElement ||
document.webkitPointerLockElement === renderer.domElement) {
document.addEventListener('mousemove', moveCallback, false)
} else {
document.removeEventListener('mousemove', moveCallback, false)
}
}
2021-02-27 17:12:11 -05:00
document.addEventListener('pointerlockchange', changeCallback, false)
document.addEventListener('mozpointerlockchange', changeCallback, false)
document.addEventListener('webkitpointerlockchange', changeCallback, false)
2021-03-20 20:32:46 -04:00
let lastTouch
document.addEventListener('touchmove', (e) => {
2021-03-20 20:57:54 -04:00
window.scrollTo(0, 0)
e.preventDefault()
2021-03-20 20:57:54 -04:00
e.stopPropagation()
2021-03-20 20:32:46 -04:00
if (lastTouch !== undefined) {
moveCallback({ movementX: e.touches[0].pageX - lastTouch.pageX, movementY: e.touches[0].pageY - lastTouch.pageY })
}
lastTouch = e.touches[0]
2021-03-20 20:57:54 -04:00
}, { passive: false })
2021-03-20 20:32:46 -04:00
document.addEventListener('touchend', (e) => {
lastTouch = undefined
}, { passive: false })
2021-02-27 17:12:11 -05:00
renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock ||
renderer.domElement.mozRequestPointerLock ||
renderer.domElement.webkitRequestPointerLock
document.addEventListener('mousedown', (e) => {
renderer.domElement.requestPointerLock()
})
document.addEventListener('contextmenu', (e) => e.preventDefault(), false)
const codes = {
KeyW: 'forward',
KeyS: 'back',
KeyA: 'right',
KeyD: 'left',
Space: 'jump',
ShiftLeft: 'sneak',
ControlLeft: 'sprint'
}
2021-02-27 17:12:11 -05:00
document.addEventListener('keydown', (e) => {
if (chat.inChat) return
2021-02-27 17:12:11 -05:00
console.log(e.code)
if (e.code in codes) {
bot.setControlState(codes[e.code], true)
2021-02-27 17:12:11 -05:00
}
if (e.code.startsWith('Digit')) {
const numPressed = e.code.substr(5)
2021-03-14 14:49:49 -04:00
hotbar.reloadHotbarSelected(numPressed - 1)
}
if (e.code === 'KeyQ') {
if (bot.heldItem) bot.tossStack(bot.heldItem)
}
2021-02-27 17:12:11 -05:00
}, false)
2021-02-27 17:12:11 -05:00
document.addEventListener('keyup', (e) => {
if (e.code in codes) {
bot.setControlState(codes[e.code], false)
2021-02-27 17:12:11 -05:00
}
}, false)
async function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
let keepMouseDownAction = false
document.addEventListener('mousedown', async (e) => {
if (document.pointerLockElement !== renderer.domElement) return
keepMouseDownAction = true
while (keepMouseDownAction) { // eslint-disable-line
const cursorBlock = bot.blockAtCursor()
if (!cursorBlock) {
await sleep(100)
continue
2021-03-01 13:50:56 -05:00
}
if (e.button === 0) {
if (bot.canDigBlock(cursorBlock)) {
updateBreakMesh(cursorBlock.position.x, cursorBlock.position.y, cursorBlock.position.z, bot.digTime(cursorBlock))
await bot.dig(cursorBlock, 'ignore')
} else {
await sleep(100)
continue
}
} else if (e.button === 2) {
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)]
const vec = vecArray[cursorBlock.face]
const delta = cursorBlock.intersect.minus(cursorBlock.position)
await bot._placeBlockWithOptions(cursorBlock, vec, { delta, forceLook: 'ignore' })
} else {
return
}
}
}, false)
document.addEventListener('mouseup', (e) => {
keepMouseDownAction = false
bot.stopDigging()
}, false)
2021-02-27 16:27:59 -05:00
2021-03-14 13:24:09 -04:00
loadingScreen.status = 'Done!'
console.log(loadingScreen.status) // only do that because it's read in index.html and npm run fix complains.
setTimeout(function () {
// remove loading screen, wait a second to make sure a frame has properly rendered
2021-03-14 13:24:09 -04:00
loadingScreen.style = 'display: none;'
}, 2500)
// TODO: Remove after #85 is done
debugMenu.customEntries.hp = bot.health
debugMenu.customEntries.food = bot.food
debugMenu.customEntries.saturation = bot.foodSaturation
bot.on('health', () => {
debugMenu.customEntries.hp = bot.health
debugMenu.customEntries.food = bot.food
debugMenu.customEntries.saturation = bot.foodSaturation
})
2021-02-27 17:12:11 -05:00
})
2021-02-27 16:27:59 -05:00
}
2021-02-27 17:12:11 -05:00
main()