diff --git a/examples/viewer/client/BotProvider.js b/examples/viewer/client/BotProvider.js index 4549959..4d23377 100644 --- a/examples/viewer/client/BotProvider.js +++ b/examples/viewer/client/BotProvider.js @@ -15,8 +15,19 @@ class BotProvider extends WorldView { this.listenToBot() this.world = new World() this.movements = new MovementManager(this) + + this.onKeyDown = () => {} + this.onKeyUp = () => {} + + this.removeAllListeners('mouseClick') } + raycast () { + // TODO : fix + } + + get entity () { return this.movements.player.entity } + handleChunk (packet, render = true) { const hash = (packet.x << 4) + ',' + (packet.z << 4) if (this.loadChunk[hash]) return diff --git a/examples/viewer/client/BotViewer.js b/examples/viewer/client/BotViewer.js index a29cc6f..384008e 100644 --- a/examples/viewer/client/BotViewer.js +++ b/examples/viewer/client/BotViewer.js @@ -1,7 +1,7 @@ /* global THREE */ const { Viewer, MapControls } = require('prismarine-viewer/viewer') // const { Vec3 } = require('vec3') -// const { BotProvider } = require('./BotProvider') +const { ClientProvider } = require('./ClientProvider') const { ProxyProvider } = require('./ProxyProvider') global.THREE = require('three') @@ -9,8 +9,8 @@ const MCVER = '1.16.1' class BotViewer { start () { - // this.bot = new BotProvider() - this.bot = new ProxyProvider() + this.bot = new ClientProvider() + // this.bot = new ProxyProvider() // Create three.js context, add to page this.renderer = new THREE.WebGLRenderer() this.renderer.setPixelRatio(window.devicePixelRatio || 1) @@ -36,15 +36,18 @@ class BotViewer { this.registerBrowserEvents() if (firstPerson && this.bot.movements) { + this.viewer.camera.position.set(position.x, position.y, position.z) this.firstPerson = true + this.controls.enabled = false } else { this.viewer.camera.position.set(position.x, position.y, position.z) } }) this.bot.on('playerMove', (id, pos) => { - if (this.firstPerson && id === 1) { + if (this.firstPerson && id < 10) { this.setFirstPersonCamera(pos) + return } window.viewer.viewer.entities.update({ @@ -58,6 +61,13 @@ class BotViewer { }) }) + this.bot.on('startSprint', () => { + this.viewer.camera.fov += 20 + }) + this.bot.on('stopSprint', () => { + this.viewer.camera.fov -= 20 + }) + this.controls.update() // Browser animation loop @@ -76,12 +86,43 @@ class BotViewer { }) } - onKeyDown = (evt) => { - console.log('Key down', evt) + onMouseMove = (e) => { + if (this.firstPerson) { + this.bot.entity.pitch -= e.movementY * 0.005 + this.bot.entity.yaw -= e.movementX * 0.004 + } + } + + onPointerLockChange = () => { + const e = this.renderer.domElement + if (document.pointerLockElement === e) { + e.parentElement.addEventListener('mousemove', this.onMouseMove, { passive: true }) + } else { + e.parentElement.removeEventListener('mousemove', this.onMouseMove, false) + } + } + + onMouseDown = () => { + if (this.firstPerson && !document.pointerLockElement) { + this.renderer.domElement.requestPointerLock() + } } registerBrowserEvents () { - this.renderer.domElement.parentElement.addEventListener('keydown', this.onKeyDown) + const e = this.renderer.domElement + e.parentElement.addEventListener('keydown', this.bot.onKeyDown) + e.parentElement.addEventListener('keyup', this.bot.onKeyUp) + e.parentElement.addEventListener('mousedown', this.onMouseDown) + document.addEventListener('pointerlockchange', this.onPointerLockChange, false) + } + + unregisterBrowserEvents () { + const e = this.renderer.domElement + e.parentElement.removeEventListener('keydown', this.bot.onKeyDown) + e.parentElement.removeEventListener('keyup', this.bot.onKeyUp) + e.parentElement.removeEventListener('mousemove', this.onMouseMove) + e.parentElement.removeEventListener('mousedown', this.onMouseDown) + document.removeEventListener('pointerlockchange', this.onPointerLockChange, false) } setFirstPersonCamera (entity) { diff --git a/examples/viewer/client/ClientProvider.js b/examples/viewer/client/ClientProvider.js index 8c13da8..62aae74 100644 --- a/examples/viewer/client/ClientProvider.js +++ b/examples/viewer/client/ClientProvider.js @@ -1,7 +1,18 @@ const { Client } = require('bedrock-protocol') const { BotProvider } = require('./BotProvider') +const controlMap = { + forward: ['KeyW', 'KeyZ'], + back: 'KeyS', + left: ['KeyA', 'KeyQ'], + right: 'KeyD', + sneak: 'ShiftLeft', + jump: 'Space' +} + class ClientProvider extends BotProvider { + downKeys = new Set() + connect () { const client = new Client({ hostname: '127.0.0.1', version: '1.16.210', port: 19132, connectTimeout: 100000 }) @@ -20,7 +31,10 @@ class ClientProvider extends BotProvider { client.queue('client_cache_status', { enabled: false }) client.queue('request_chunk_radius', { chunk_radius: 1 }) - client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n }) + + this.heartbeat = setInterval(() => { + client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n }) + }) }) this.client = client @@ -36,11 +50,13 @@ class ClientProvider extends BotProvider { }) this.client.on('start_game', packet => { this.updatePosition(packet.player_position) + this.movements.init('', packet.player_position, null, packet.rotation.z, packet.rotation.x, 0) }) this.client.on('spawn', () => { + this.movements.startPhys() // server allows client to render chunks & spawn in world - this.emit('spawn', { position: this.lastPos }) + this.emit('spawn', { position: this.lastPos, firstPerson: true }) this.tickLoop = setInterval(() => { this.client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n }) @@ -52,17 +68,45 @@ class ClientProvider extends BotProvider { }) this.client.on('move_player', packet => { - if (packet.runtime_id === this.client.entityId) this.updatePosition(packet.position) + if (packet.runtime_id === this.client.entityId) { this.movements.updatePosition(packet.position, packet.yaw, packet.pitch, packet.head_yaw, packet.tick) } }) this.client.on('set_entity_motion', packet => { - if (packet.runtime_id === this.client.entityId) this.updatePosition(packet.position) + // if (packet.runtime_id === this.client.entityId) this.updatePosition(packet.position) }) this.client.on('tick_sync', (packet) => { this.lastTick = packet.response_time }) } + + onKeyDown = (evt) => { + const code = evt.code + for (const control in controlMap) { + if (controlMap[control].includes(code)) { + this.movements.setControlState(control, true) + break + } + if (evt.ctrlKey) { + this.movements.setControlState('sprint', true) + } + } + this.downKeys.add(code) + } + + onKeyUp = (evt) => { + const code = evt.code + if (code == 'ControlLeft' && this.downKeys.has('ControlLeft')) { + this.movements.setControlState('sprint', false) + } + for (const control in controlMap) { + if (controlMap[control].includes(code)) { + this.movements.setControlState(control, false) + break + } + } + this.downKeys.delete(code) + } } module.exports = { ClientProvider } diff --git a/examples/viewer/client/movements.js b/examples/viewer/client/movements.js index a405722..d5f52c0 100644 --- a/examples/viewer/client/movements.js +++ b/examples/viewer/client/movements.js @@ -19,9 +19,7 @@ class MovementManager { get lastPos () { return this.player.entity.position.clone() } set lastPos (newPos) { this.player.entity.position.set(newPos.x, newPos.y, newPos.z) } - get lastRot () { return vec3(this.player.entity.yaw, this.player.entity.pitch, this.player.entity.headYaw) } - set lastRot (rot) { this.player.entity.yaw = rot.x this.player.entity.pitch = rot.y @@ -33,10 +31,10 @@ class MovementManager { const positionUpdated = !this.lastSentPos || !this.lastPos.equals(this.lastSentPos) const rotationUpdated = !this.lastSentRot || !this.lastRot.equals(this.lastSentRot) - if (positionUpdated) { + if (positionUpdated || rotationUpdated) { this.lastSentPos = this.lastPos.clone() - console.log('We computed', this.lastPos) - this.bot.updatePlayerCamera(2, this.lastSentPos, this.playerState.yaw, this.playerState.pitch) + // console.log('We computed', this.lastPos) + this.bot.updatePlayerCamera(2, this.lastSentPos, this.playerState.yaw, this.playerState.pitch || this.player.entity.pitch) if (this.serverMovements) { this.client.queue('player_auth_input', { pitch: this.player.pitch, @@ -175,6 +173,7 @@ class MovementManager { stop_gliding: false }) this.timeAccumulator -= PHYSICS_TIMESTEP + this.tick++ } } @@ -185,16 +184,29 @@ class MovementManager { }, PHYSICS_INTERVAL_MS) } + /** + * Sets the active control state and also keeps track of key toggles. + * @param {'forward' | 'back' | 'left' | 'right' | 'jump' | 'sprint' | 'sneak'} control + * @param {boolean} state + */ setControlState (control, state) { + // HACK ! switch left and right, fixes control issue + if (control === 'left') control = 'right' + else if (control === 'right') control = 'left' + if (this.controls[control] === state) return if (control === 'sprint') { this.player.events.startSprint = state this.player.events.stopSprint = !state + if (state) this.bot.emit('startSprint') + else this.bot.emit('stopSprint') this.controls.sprint = true } else if (control === 'sneak') { this.player.events.startSneak = state this.player.events.stopSneak = !state this.controls.sprint = true + } else { + this.controls[control] = state } } diff --git a/examples/viewer/index.js b/examples/viewer/index.js index f2a942c..5d17e38 100644 --- a/examples/viewer/index.js +++ b/examples/viewer/index.js @@ -1,5 +1,5 @@ const path = require('path') -const { app, BrowserWindow } = require('electron') +const { app, BrowserWindow, globalShortcut } = require('electron') function createMainWindow () { const window = new BrowserWindow({ @@ -28,6 +28,10 @@ function createMainWindow () { app.on('ready', () => { createMainWindow() + + globalShortcut.register('CommandOrControl+W', () => { + // no op + }) }) app.on('window-all-closed', function () {