import Module from '../utils/Module' import { IDelta, IPosition, IState, gameMode } from '../utils/types' import Spawn from './Spawn' // MAYBE: split interface IDifficulty { level: number, locked: boolean } interface IAbilities { // TODO: Flags Byte Bit mask. 0x08: damage disabled (god mode), 0x04: can fly, 0x02: is flying, 0x01: is Creative flyingSpeed: number, walkingSpeed: number } interface IPositionPacket extends IPosition { flags: number teleportId: number } interface IPlayerInfo { UUID: string name: string properties: any[] gamemode: gameMode ping: number } /** Handle client state and position */ export default class State extends Module<{ positionConfirm: boolean }> { private _state!: IState public get state() { return this._state } private _difficulty!: IDifficulty public get difficulty() { return this._difficulty } private _abilities!: IAbilities public get abilities() { return this._abilities } private _position!: IPosition public get position() { return this._position } private _players = new Map() public get players() { return this._players } public translate(delta: IDelta, onGround: boolean = true) { this.client.write('position', { onGround, x: this.position.x += delta.dX, y: this.position.y += delta.dY, z: this.position.z += delta.dZ, }) } protected mount() { const spawn = this.load(Spawn) spawn.addCheck('state') spawn.addCheck('abilities') spawn.addCheck('position') this.client.on('login', data => { this._state = data spawn.validateCheck('state') }) this.client.on('difficulty', data => this._difficulty = { level: data.difficulty, locked: data.difficultyLocked, }) this.client.on('abilities', data => { this._abilities = data spawn.validateCheck('abilities') }) this.client.on('position', (data: IPositionPacket) => { if (this._position == null) { if (data.flags) { this.logger.error('First position is relative') return } else { this._position = data } } else { // tslint:disable: no-bitwise this._position = { x: data.x + (data.flags & 0x01 ? this._position.x : 0), y: data.y + (data.flags & 0x02 ? this._position.y : 0), z: data.z + (data.flags & 0x04 ? this._position.z : 0), yaw: data.yaw + (data.flags & 0x08 ? this._position.yaw : 0), pitch: data.pitch + (data.flags & 0x10 ? this._position.pitch : 0), } } if (this.conf.positionConfirm) { this.client.write('teleport_confirm', { teleportId: data.teleportId }) } this.logger.debug({ msg: 'Teleported', type: 'position', value: this.position }) spawn.validateCheck('position') }) this.client.on('player_info', (packet: { action: number, data: IPlayerInfo[] }) => { for (const player of packet.data) { switch (packet.action) { case 0: this._players.set(player.UUID, player) break case 1: const p1 = this._players.get(player.UUID) if (p1) { p1.gamemode = player.gamemode } break case 2: const p2 = this._players.get(player.UUID) if (p2) { p2.ping = player.ping } break case 3: const p3 = this._players.get(player.UUID) if (p3) { p3.name = player.name } break case 4: this._players.delete(player.UUID) break } } }) } protected getConf() { return { positionConfirm: true } } } // TODO: vehicule_move