pages235/prismarine-viewer/viewer/lib/worldView.js
2023-09-04 09:23:05 +03:00

134 lines
4.5 KiB
JavaScript

const { spiral, ViewRect, chunkPos } = require('./simpleUtils')
const { Vec3 } = require('vec3')
const EventEmitter = require('events')
class WorldView extends EventEmitter {
constructor (world, viewDistance, position = new Vec3(0, 0, 0), emitter = null) {
super()
this.world = world
this.viewDistance = viewDistance
this.loadedChunks = {}
this.lastPos = new Vec3(0, 0, 0).update(position)
this.emitter = emitter || this
this.listeners = {}
this.emitter.on('mouseClick', async (click) => {
const ori = new Vec3(click.origin.x, click.origin.y, click.origin.z)
const dir = new Vec3(click.direction.x, click.direction.y, click.direction.z)
const block = this.world.raycast(ori, dir, 256)
if (!block) return
this.emit('blockClicked', block, block.face, click.button)
})
}
listenToBot (bot) {
const worldView = this
this.listeners[bot.username] = {
// 'move': botPosition,
entitySpawn: function (e) {
if (e === bot.entity) return
worldView.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username })
},
entityMoved: function (e) {
worldView.emitter.emit('entity', { id: e.id, pos: e.position, pitch: e.pitch, yaw: e.yaw })
},
entityGone: function (e) {
worldView.emitter.emit('entity', { id: e.id, delete: true })
},
chunkColumnLoad: function (pos) {
worldView.loadChunk(pos)
},
blockUpdate: function (oldBlock, newBlock) {
const stateId = newBlock.stateId ? newBlock.stateId : ((newBlock.type << 4) | newBlock.metadata)
worldView.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId })
}
}
for (const [evt, listener] of Object.entries(this.listeners[bot.username])) {
bot.on(evt, listener)
}
for (const id in bot.entities) {
const e = bot.entities[id]
if (e && e !== bot.entity) {
this.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username })
}
}
}
removeListenersFromBot (bot) {
for (const [evt, listener] of Object.entries(this.listeners[bot.username])) {
bot.removeListener(evt, listener)
}
delete this.listeners[bot.username]
}
async init (pos) {
const [botX, botZ] = chunkPos(pos)
const positions = []
spiral(this.viewDistance * 2, this.viewDistance * 2, (x, z) => {
const p = new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
positions.push(p)
})
this.lastPos.update(pos)
await this._loadChunks(positions)
}
async _loadChunks (positions, sliceSize = 5, waitTime = 0) {
for (let i = 0; i < positions.length; i += sliceSize) {
await new Promise((resolve) => setTimeout(resolve, waitTime))
await Promise.all(positions.slice(i, i + sliceSize).map(p => this.loadChunk(p)))
}
}
async loadChunk (pos) {
const [botX, botZ] = chunkPos(this.lastPos)
const dx = Math.abs(botX - Math.floor(pos.x / 16))
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
if (dx < this.viewDistance && dz < this.viewDistance) {
const column = await this.world.getColumnAt(pos)
if (column) {
// todo optimize toJson data, make it clear why it is used
const chunk = column.toJson()
this.emitter.emit('loadChunk', { x: pos.x, z: pos.z, chunk })
this.loadedChunks[`${pos.x},${pos.z}`] = true
}
}
}
unloadChunk (pos) {
this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z })
delete this.loadedChunks[`${pos.x},${pos.z}`]
}
async updatePosition (pos, force = false) {
const [lastX, lastZ] = chunkPos(this.lastPos)
const [botX, botZ] = chunkPos(pos)
if (lastX !== botX || lastZ !== botZ || force) {
const newView = new ViewRect(botX, botZ, this.viewDistance)
for (const coords of Object.keys(this.loadedChunks)) {
const x = parseInt(coords.split(',')[0])
const z = parseInt(coords.split(',')[1])
const p = new Vec3(x, 0, z)
if (!newView.contains(Math.floor(x / 16), Math.floor(z / 16))) {
this.unloadChunk(p)
}
}
const positions = []
spiral(this.viewDistance * 2, this.viewDistance * 2, (x, z) => {
const p = new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
if (!this.loadedChunks[`${p.x},${p.z}`]) {
positions.push(p)
}
})
this.lastPos.update(pos)
await this._loadChunks(positions)
} else {
this.lastPos.update(pos)
}
}
}
module.exports = { WorldView }