Release 3.19.0 (#283)

* Update HISTORY.md

* Update package.json

* Remove viewer example

* Update HISTORY.md
This commit is contained in:
extremeheat 2022-09-22 22:47:17 -04:00 committed by GitHub
commit 30c583fcf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 7 additions and 874 deletions

View file

@ -1,3 +1,9 @@
## 3.19.0
* Add option for port redirection, fix Realm handling (#282)
* Add Port Redirect Functionality (#278) @stevarino
* Add Get-AppxPackage command to FAQ.md (#276) @stevarino
* Remove viewer example
## 3.18.0
* 1.19.21 support (#266)

View file

@ -1,58 +0,0 @@
const { Version } = require('bedrock-provider')
const { WorldView } = require('prismarine-viewer/viewer')
const World = require('prismarine-world')()
const ChunkColumn = require('./Chunk')()
const { MovementManager } = require('./movements')
class BotProvider extends WorldView {
chunks = {}
lastSentPos
positionUpdated = true
constructor () {
super()
this.connect()
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
const cc = new ChunkColumn(Version.v1_4_0, packet.x, packet.z)
cc.networkDecodeNoCache(packet.payload, packet.sub_chunk_count).then(() => {
this.loadedChunks[hash] = true
this.world.setColumn(packet.x, packet.z, cc)
const chunk = cc.serialize()
// console.log('Chunk', chunk)
if (render) this.emitter.emit('loadChunk', { x: packet.x << 4, z: packet.z << 4, chunk })
})
}
updatePlayerCamera (id, position, yaw, pitch, updateState) {
this.emit('playerMove', id, { position, yaw, pitch })
if (updateState) {
this.movements.updatePosition(position, yaw, pitch)
}
}
stopBot () {
clearInterval(this.tickLoop)
this.movements.stopPhys()
}
}
module.exports = { BotProvider }

View file

@ -1,150 +0,0 @@
/* global THREE */
const { Viewer, MapControls } = require('prismarine-viewer/viewer')
// const { Vec3 } = require('vec3')
const { ClientProvider } = require('./ClientProvider')
// const { ProxyProvider } = require('./ProxyProvider')
global.THREE = require('three')
const MCVER = '1.16.1'
class BotViewer {
start () {
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)
this.renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(this.renderer.domElement)
// Create viewer
this.viewer = new Viewer(this.renderer)
this.viewer.setVersion(MCVER)
// Attach controls to viewer
this.controls = new MapControls(this.viewer.camera, this.renderer.domElement)
// Enable damping (inertia) on movement
this.controls.enableDamping = true
this.controls.dampingFactor = 0.09
console.info('Registered handlers')
// Link WorldView and Viewer
this.viewer.listen(this.bot)
this.bot.on('spawn', ({ position, firstPerson }) => {
// Initialize viewer, load chunks
this.bot.init(position)
// Start listening for keys
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 < 10) {
this.setFirstPersonCamera(pos)
return
}
window.viewer.viewer.entities.update({
name: 'player',
id,
pos: pos.position,
width: 0.6,
height: 1.8,
yaw: pos.yaw,
pitch: pos.pitch
})
})
const oldFov = this.viewer.camera.fov
const sprintFov = this.viewer.camera.fov + 20
const sneakFov = this.viewer.camera.fov - 10
const onSprint = () => {
this.viewer.camera.fov = sprintFov
this.viewer.camera.updateProjectionMatrix()
}
const onSneak = () => {
this.viewer.camera.fov = sneakFov
this.viewer.camera.updateProjectionMatrix()
}
const onRelease = () => {
this.viewer.camera.fov = oldFov
this.viewer.camera.updateProjectionMatrix()
}
this.bot.on('startSprint', onSprint)
this.bot.on('startSneak', onSneak)
this.bot.on('stopSprint', onRelease)
this.bot.on('stopSneak', onRelease)
this.controls.update()
// Browser animation loop
const animate = () => {
window.requestAnimationFrame(animate)
if (this.controls && !this.firstPerson) this.controls.update()
this.viewer.update()
this.renderer.render(this.viewer.scene, this.viewer.camera)
}
animate()
window.addEventListener('resize', () => {
this.viewer.camera.aspect = window.innerWidth / window.innerHeight
this.viewer.camera.updateProjectionMatrix()
this.renderer.setSize(window.innerWidth, window.innerHeight)
})
}
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 () {
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) {
this.viewer.setFirstPersonCamera(entity.position, entity.yaw, entity.pitch * 2)
}
}
module.exports = { BotViewer }

View file

@ -1,18 +0,0 @@
const { ChunkColumn } = require('bedrock-provider')
const Block = require('prismarine-block')('1.16.1')
class ChunkColumnWrapped extends ChunkColumn { // pchunk compatiblity wrapper
// Block access
setBlockStateId (pos, stateId) {
super.setBlock(pos.x, pos.y, pos.z, Block.fromStateId(stateId))
}
getBlockStateId (pos) {
return super.getBlock(pos.x, pos.y, pos.z)?.stateId
}
}
module.exports = (version) => {
return ChunkColumnWrapped
}

View file

@ -1,114 +0,0 @@
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({ host: '127.0.0.1', version: '1.16.210', username: 'notch', offline: true, port: 19132, connectTimeout: 100000 })
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
this.heartbeat = setInterval(() => {
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
})
})
this.client = client
}
close () {
this.client?.close()
}
listenToBot () {
this.client.on('connect', () => {
console.log('Bot has connected!')
})
this.client.on('start_game', packet => {
this.updatePosition(packet.player_position)
this.movements.init('server', packet.player_position, /* vel */ null, packet.rotation.z || 0, packet.rotation.x || 0, 0)
})
this.client.on('spawn', () => {
this.movements.startPhys()
// server allows client to render chunks & spawn in world
this.emit('spawn', { position: this.lastPos, firstPerson: true })
this.tickLoop = setInterval(() => {
this.client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
})
})
this.client.on('level_chunk', packet => {
this.handleChunk(packet)
})
this.client.on('move_player', packet => {
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)
})
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 }

View file

@ -1,89 +0,0 @@
const { Relay } = require('bedrock-protocol')
const { BotProvider } = require('./BotProvider')
const { diff } = require('./util')
class ProxyProvider extends BotProvider {
lastPlayerMovePacket
connect () {
const proxy = new Relay({
host: '0.0.0.0',
port: 19130,
// logging: true,
destination: {
host: '127.0.0.1',
port: 19132
}
})
proxy.listen()
console.info('Waiting for connect')
proxy.on('join', (client, server) => {
client.on('clientbound', ({ name, params }) => {
if (name === 'level_chunk') {
this.handleChunk(params, true)
} else if (name === 'start_game') {
this.movements.init('', params.player_position, null, params.rotation.z, params.rotation.x, 0)
} else if (name === 'play_status') {
this.movements.startPhys()
this.emit('spawn', { position: this.movements.lastPos, firstPerson: true })
console.info('Started physics!')
} else if (name === 'move_player') {
console.log('move_player', params)
this.movements.updatePosition(params.position, params.yaw, params.pitch, params.head_yaw, params.tick)
}
if (name.includes('entity') || name.includes('network_chunk_publisher_update') || name.includes('tick') || name.includes('level')) return
console.log('CB', name)
})
client.on('serverbound', ({ name, params }) => {
// { name, params }
if (name === 'player_auth_input') {
this.movements.pushInputState(params.input_data, params.yaw, params.pitch)
this.movements.pushCameraControl(params, 1)
// Log Movement deltas
{ // eslint-disable-line
this.lastMovePacket = params
if (this.firstPlayerMovePacket) {
const id = diff(this.firstPlayerMovePacket.input_data, params.input_data)
const md = diff(this.firstPlayerMovePacket.move_vector, params.move_vector)
const dd = diff(this.firstPlayerMovePacket.delta, params.delta)
if (id || md) {
if (globalThis.logging) console.log('Move', params.position, id, md, dd)
globalThis.movements ??= []
globalThis.movements.push(params)
}
}
if (!this.firstPlayerMovePacket) {
this.firstPlayerMovePacket = params
for (const key in params.input_data) {
params.input_data[key] = false
}
params.input_data._value = 0n
params.move_vector = { x: 0, z: 0 }
params.delta = { x: 0, y: 0, z: 0 }
}
}
} else if (!name.includes('tick') && !name.includes('level')) {
console.log('Sending', name)
}
})
console.info('Client and Server Connected!')
})
this.proxy = proxy
}
listenToBot () {
}
close () {
this.proxy?.close()
}
}
module.exports = { ProxyProvider }
globalThis.logging = true

View file

@ -1,22 +0,0 @@
html {
overflow: hidden;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: sans-serif;
}
a {
text-decoration: none;
}
canvas {
height: 100%;
width: 100%;
font-size: 0;
margin: 0;
padding: 0;
}

View file

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Prismarine Viewer</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
<div id='splash'>
<div class='header'>Prismarine Viewer</div>
<div>
<div>Connecting to 127.0.0.1, port 19132...</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

View file

@ -1,4 +0,0 @@
const { BotViewer } = require('./BotViewer')
global.viewer = new BotViewer()
global.viewer.start()

View file

@ -1,304 +0,0 @@
const { Physics, PlayerState } = require('prismarine-physics')
const { performance } = require('perf_hooks')
const { d2r, r2d } = require('./util')
const vec3 = require('vec3')
const PHYSICS_INTERVAL_MS = 50
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000
const AXES = ['forward', 'back', 'left', 'right']
class MovementManager {
// Server auth movement : we send inputs, server calculates position & sends back
serverMovements = false
constructor (bot) {
this.bot = bot
this.world = bot.world
// Physics tick
this.tick = 0n
}
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) {
if (!isNaN(rot.x)) this.player.entity.yaw = rot.x
if (!isNaN(rot.y)) this.player.entity.pitch = rot.y
if (!isNaN(rot.z)) this.player.entity.headYaw = rot.z
}
// Ask the server to be in a new position
requestPosition (time, inputState) {
const positionUpdated = !this.lastSentPos || !this.lastPos.equals(this.lastSentPos)
const rotationUpdated = !this.lastSentRot || !this.lastRot.equals(this.lastSentRot)
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 || this.player.entity.pitch)
if (this.serverMovements) {
globalThis.movePayload = {
pitch: r2d(this.player.entity.pitch),
yaw: r2d(this.player.entity.yaw),
position: {
x: this.lastPos.x,
y: this.lastPos.y + 1.62,
z: this.lastPos.z
},
move_vector: { // Minecraft coords, N: Z+1, S: Z-1, W: X+1, E: X-1
x: inputState.left ? 1 : (inputState.right ? -1 : 0),
z: inputState.up ? 1 : (inputState.down ? -1 : 0)
},
head_yaw: r2d(this.player.entity.yaw),
input_data: inputState,
input_mode: 'mouse',
play_mode: 'screen',
tick: this.tick,
delta: this.lastSentPos?.minus(this.lastPos) ?? { x: 0, y: 0, z: 0 }
}
this.bot.client.queue('player_auth_input', globalThis.movePayload)
}
this.positionUpdated = false
this.lastSentPos = this.lastPos
this.lastSentRot = this.lastRot
}
}
init (movementAuthority, position, velocity, yaw = 0, pitch = 0, headYaw = 0) {
if (movementAuthority.includes('server')) {
this.serverMovements = true
}
this.player = {
version: '1.16.1',
inventory: {
slots: []
},
entity: {
effects: {},
position: vec3(position),
velocity: vec3(velocity),
onGround: false,
isInWater: false,
isInLava: false,
isInWeb: false,
isCollidedHorizontally: false,
isCollidedVertically: false,
yaw,
pitch,
headYaw // bedrock
},
events: { // Control events to send next tick
startSprint: false,
stopSprint: false,
startSneak: false,
stopSneak: false
},
sprinting: false,
jumpTicks: 0,
jumpQueued: false,
downJump: false
}
const mcData = require('minecraft-data')('1.16.1')
this.physics = Physics(mcData, this.world)
this.controls = {
forward: false,
back: false,
left: false,
right: false,
jump: false,
sprint: false,
sneak: false
}
}
// This function should be executed each tick (every 0.05 seconds)
// How it works: https://gafferongames.com/post/fix_your_timestep/
timeAccumulator = 0
lastPhysicsFrameTime = null
inputQueue = []
doPhysics () {
const now = performance.now()
const deltaSeconds = (now - this.lastPhysicsFrameTime) / 1000
this.lastPhysicsFrameTime = now
this.timeAccumulator += deltaSeconds
while (this.timeAccumulator >= PHYSICS_TIMESTEP) {
const q = this.inputQueue.shift()
if (q) {
Object.assign(this.playerState.control, q)
if (!isNaN(q.yaw)) this.player.entity.yaw = q.yaw
if (!isNaN(q.pitch)) this.player.entity.pitch = q.pitch
}
this.playerState = new PlayerState(this.player, this.controls)
this.physics.simulatePlayer(this.playerState, this.world.sync).apply(this.player)
this.lastPos = this.playerState.pos
this.requestPosition(PHYSICS_TIMESTEP, {
ascend: false,
descend: false,
// Players bob up and down in water, north jump is true when going up.
// In water this is only true after the player has reached max height before bobbing back down.
north_jump: this.player.jumpTicks > 0, // Jump
jump_down: this.controls.jump, // Jump
sprint_down: this.controls.sprint,
change_height: false,
jumping: this.controls.jump, // Jump
auto_jumping_in_water: false,
sneaking: false,
sneak_down: false,
up: this.controls.forward,
down: this.controls.back,
left: this.controls.right,
right: this.controls.left,
up_left: false,
up_right: false,
want_up: this.controls.jump, // Jump
want_down: false,
want_down_slow: false,
want_up_slow: false,
sprinting: false,
ascend_scaffolding: false,
descend_scaffolding: false,
sneak_toggle_down: false,
persist_sneak: false,
start_sprinting: this.player.events.startSprint || false,
stop_sprinting: this.player.events.stopSprint || false,
start_sneaking: this.player.events.startSneak || false,
stop_sneaking: this.player.events.stopSneak || false,
// Player is Update Aqatic swimming
start_swimming: false,
// Player stops Update Aqatic swimming
stop_swimming: false,
start_jumping: this.player.jumpTicks === 1, // Jump
start_gliding: false,
stop_gliding: false
})
this.timeAccumulator -= PHYSICS_TIMESTEP
this.tick++
}
}
startPhys () {
console.log('Start phys')
this.physicsLoop = setInterval(() => {
this.doPhysics()
}, PHYSICS_INTERVAL_MS)
}
get sprinting () {
return this.player.sprinting
}
set sprinting (val) {
this.player.events.startSprint = val
this.player.events.stopSprint = !val
if (val && !this.player.sprinting) {
this.bot.emit('startSprint')
} else {
this.bot.emit('stopSprint')
}
this.player.sprinting = val
}
_lastInput = { control: '', time: 0 }
/**
* 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, time = Date.now()) {
// 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
const isAxis = AXES.includes(control)
let hasOtherAxisKeyDown = false
for (const c of AXES) {
if (this.controls[c] && c !== control) {
hasOtherAxisKeyDown = true
}
}
if (control === 'sprint') {
if (state && hasOtherAxisKeyDown) { // sprint down + a axis movement key
this.sprinting = true
} else if ((!state || !hasOtherAxisKeyDown) && this.sprinting) { // sprint up or movement key up & current sprinting
this.bot.emit('stopSprint')
this.sprinting = false
}
} else if (isAxis && this.controls.sprint) {
if (!state && !hasOtherAxisKeyDown) {
this.sprinting = false
} else if (state && !hasOtherAxisKeyDown) {
this.sprinting = true
}
} else if (control === 'sneak') {
if (state) {
this.player.events.startSneak = true
this.bot.emit('startSneak')
} else {
this.player.events.stopSneak = true
this.bot.emit('stopSneak')
}
} else if (control === 'forward' && this._lastInput.control === 'forward' && (Date.now() - this._lastInput.time) < 100 && !this.controls.sprint) {
// double tap forward within 0.5 seconds, toggle sprint
// this.controls.sprint = true
// this.sprinting = true
}
this._lastInput = { control, time }
this.controls[control] = state
}
stopPhys () {
clearInterval(this.physicsLoop)
}
// Called when a proxy player sends a PlayerInputPacket. We need to apply these inputs tick-by-tick
// as these packets are sent by the client every tick.
pushInputState (state, yaw, pitch) {
const yawRad = d2r(yaw)
const pitchRad = d2r(pitch)
this.inputQueue.push({
forward: state.up,
back: state.down, // TODO: left and right switched ???
left: state.right,
right: state.left,
jump: state.jump_down,
sneak: state.sneak_down,
yaw: yawRad,
pitch: pitchRad
})
// debug
globalThis.debugYaw = [yaw, yawRad]
}
// Called when a proxy player sends a PlayerInputPacket. We need to apply these inputs tick-by-tick
// as these packets are sent by the client every tick.
pushCameraControl (state, id = 1) {
let { x, y, z } = state.position
if (id === 1) y -= 1.62 // account for player bb
const adjPos = vec3({ x, y, z })
// Sneak resyncs the position for easy testing
this.bot.updatePlayerCamera(id, adjPos, d2r(state.yaw), d2r(state.pitch), state.input_data.sneak_down)
}
// Server gives us a new position
updatePosition (pos, yaw, pitch, headYaw, tick) {
this.lastPos = pos
this.lastRot = { x: yaw, y: pitch, z: headYaw }
if (tick) this.tick = tick
}
// User has moved the camera. Update the movements stored.
onViewerCameraMove (newYaw, newPitch, newHeadYaw) {
this.lastRot = { x: newYaw, y: newPitch, z: newHeadYaw }
}
}
module.exports = { MovementManager }

View file

@ -1,9 +0,0 @@
// Required to detect electron in prismarine-viewer
globalThis.isElectron = true
// If you need to disable node integration:
// * Node.js APIs will only be avaliable in this file
// * Use this file to load a viewer manager class
// based on one of the examples
// * Expose this class to the global window
// * Interact with the class in your code

View file

@ -1,22 +0,0 @@
const difference = (o1, o2) => Object.keys(o2).reduce((diff, key) => {
if (o1[key] === o2[key]) return diff
return {
...diff,
[key]: o2[key]
}
}, {})
const diff = (o1, o2) => { const dif = difference(o1, o2); return Object.keys(dif).length ? dif : null }
const d2r = deg => (180 - (deg < 0 ? (360 + deg) : deg)) * (Math.PI / 180)
const r2d = rad => {
let deg = rad * (180 / Math.PI)
deg = deg % 360
return 180 - deg
}
module.exports = {
diff,
d2r,
r2d
}

View file

@ -1,2 +0,0 @@
// hack for path resolving
require('prismarine-viewer/viewer/lib/worker')

View file

@ -1,44 +0,0 @@
const path = require('path')
const { app, BrowserWindow, globalShortcut } = require('electron')
function createMainWindow () {
const window = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false,
preload: path.join(__dirname, './client/preload.js')
}
})
// Open dev tools on load
window.webContents.openDevTools()
window.loadFile(path.join(__dirname, './client/index.html'))
window.webContents.on('devtools-opened', () => {
window.focus()
setImmediate(() => {
window.focus()
})
})
return window
}
app.on('ready', () => {
const win = createMainWindow()
globalShortcut.register('CommandOrControl+W', () => {
win.webContents.sendInputEvent({
type: 'keyDown',
keyCode: 'W'
})
})
})
app.on('window-all-closed', function () {
app.quit()
})
app.allowRendererProcessReuse = false

View file

@ -1,15 +0,0 @@
{
"name": "bedrock-protocol-viewer",
"description": "bedrock-protocol prismarine-viewer example",
"scripts": {
"start": "electron ."
},
"dependencies": {
"bedrock-protocol": "file:../../",
"browserify-cipher": "^1.0.1",
"electron": "^12.0.2",
"patch-package": "^6.4.7",
"prismarine-physics": "^1.2.2",
"prismarine-viewer": "^1.19.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "bedrock-protocol",
"version": "3.18.0",
"version": "3.19.0",
"description": "Minecraft Bedrock Edition protocol library",
"main": "index.js",
"scripts": {