cubbot/src/modules/Entities.ts

261 lines
8.3 KiB
TypeScript

import { applyDelta, applyVelocity } from '../utils/func'
import Module from '../utils/Module'
import { IDelta, IMovable, IPosition, IRotation, ISlot, IState, IVelocity } from '../utils/types'
import Time from './Time'
interface IId {
entityId: number
}
interface IPositioned extends IId, IPosition { }
interface IMovableE extends IId, IMovable { }
interface IMove extends IId, IDelta { }
interface ILook extends IId, IRotation { }
interface IMoveLook extends IMove, ILook { }
interface IHead extends IId {
headPitch: number
}
interface IVelocityE extends IId, IVelocity { }
interface IObject extends IMovableE, IMetadatable {
objectId: number
type: number
data: number
}
interface IOrb extends IMovableE, IMetadatable {
count: number
}
interface IEquipment extends IId {
slot: number,
item: ISlot
}
interface IEquipable {
equipment?: Map<number, ISlot>
}
interface IAttribute {
key: string
value: number
modifiers: IModifier[]
}
interface IModifier {
uuid: string
amount: number
operation: number
}
interface IAttributes extends IId {
properties: IAttribute[]
}
interface IAttributable {
attributes?: IAttribute[]
}
interface IMetadata {
key: number
type: number
value: any
}
interface IMetadatas extends IId {
metadata: IMetadata[]
}
interface IMetadatable {
metadata?: Map<number, IMetadata>
}
export interface IAlive extends IMovableE, IEquipable, IAttributable, IMetadatable {
headPitch: number
}
export interface ILiving extends IAlive {
entityUUID: string
type: number
}
interface IPlayerSpawn extends IPositioned {
playerUUID: string
}
export interface IPlayer extends IPlayerSpawn, IAlive {
me?: boolean
}
/** Track entities */
export default class Entities extends Module<{}> {
private _objects = new Map<number, IObject>()
public get objects() {
return this._objects.values()
}
private _orbs = new Map<number, IOrb>()
public get orbs() {
return this._orbs.values()
}
private _livings = new Map<number, ILiving>()
public get livings() {
return this._livings.values()
}
private _players = new Map<number, IPlayer>()
public get players() {
return this._players.values()
}
public getEntity(entityId: number): IMovableE | undefined {
return this._players.get(entityId) || this._livings.get(entityId) ||
this._objects.get(entityId) || this._orbs.get(entityId)
}
public getAlive(entityId: number): IAlive | undefined {
return this._players.get(entityId) || this._livings.get(entityId)
}
public getMetadatable(entityId: number): IMetadatable | undefined {
return this._players.get(entityId) || this._livings.get(entityId) ||
this._objects.get(entityId) || this._orbs.get(entityId)
}
public deleteEntity(entityId: number) {
return this._players.delete(entityId) || this._livings.delete(entityId) ||
this._objects.delete(entityId) || this._orbs.delete(entityId)
}
public attack(entityId: number, mainHand: boolean = true) {
this.client.write('use_entity', { target: entityId, mouse: 1, hand: mainHand ? 0 : 1 })
}
protected mount() {
this.load<Time>(Time).events.on('tick', () => {
this._objects.forEach(applyVelocity)
// MAYBE: apply velocity to others
})
this.client.on('spawn_entity', (packet: IObject) => {
this._objects.set(packet.entityId, packet)
})
this.client.on('spawn_entity_experience_orb', (packet: IOrb) => {
this._orbs.set(packet.entityId, packet)
})
this.client.on('spawn_entity_living', (packet: ILiving) => {
this._livings.set(packet.entityId, packet)
})
this.client.on('named_entity_spawn', (packet: IPlayerSpawn) => {
const player = {
velocityX: 0,
velocityY: 0,
velocityZ: 0,
headPitch: 0,
...packet,
}
this._players.set(player.entityId, player)
})
this.client.on('login', (packet: IState) => {
this._players.set(packet.entityId, { // MAYBE: link with State
me: true,
entityId: packet.entityId,
playerUUID: '',
x: 0,
y: 0,
z: 0,
pitch: 0,
yaw: 0,
velocityX: 0,
velocityY: 0,
velocityZ: 0,
headPitch: 0,
})
})
// MAYBE: this.client.on('animation', packet => { })
// MAYBE: this.client.on('entity_status', packet => { })
this.client.on('rel_entity_move', (packet: IMove) =>
this.findOrLogger<IMovableE>('rel_move', packet, this.getEntity.bind(this), entity =>
applyDelta(entity, packet)))
this.client.on('entity_move_look', (packet: IMoveLook) =>
this.findOrLogger<IMovableE>('move_look', packet, this.getEntity.bind(this), entity => {
applyDelta(entity, packet)
entity.pitch = packet.pitch
entity.yaw = packet.yaw
}))
this.client.on('entity_look', (packet: ILook) =>
this.findOrLogger<IMovableE>('look', packet, this.getEntity.bind(this), entity => {
entity.pitch = packet.pitch
entity.yaw = packet.yaw
}))
this.client.on('entity_head_rotation', (packet: IHead) =>
this.findOrLogger<IAlive>('head_rotation', packet, this.getAlive.bind(this), entity =>
entity.headPitch = packet.headPitch))
this.client.on('entity_velocity', (packet: IVelocityE) =>
this.findOrLogger<IMovableE>('velocity', packet, this.getEntity.bind(this), entity => {
entity.velocityX = packet.velocityX
entity.velocityY = packet.velocityY
entity.velocityZ = packet.velocityZ
}))
this.client.on('entity_teleport', (packet: IPositioned) =>
this.findOrLogger<IMovableE>('teleport', packet, this.getEntity.bind(this), entity => {
entity.x = packet.x
entity.y = packet.y
entity.z = packet.z
entity.pitch = packet.pitch
entity.yaw = packet.yaw
}))
this.client.on('entity_destroy', (packet: { entityIds: number[] }) => {
packet.entityIds.forEach(id => {
if (!this.deleteEntity(id)) {
this.logger.debug({ msg: 'Can not delete entity', id })
}
})
})
// TODO: identify metadatas
this.client.on('entity_metadata', (packet: IMetadatas) =>
this.findOrLogger<IMetadatable>('metadata', packet, this.getMetadatable.bind(this), entity => {
if (!entity.metadata) {
entity.metadata = new Map()
}
for (const data of packet.metadata) {
entity.metadata.set(data.key, data)
}
}))
// MAYBE: this.client.on('entity', (packet: IId) => { })
// TODO: entity_effect, remove_entity_effect
this.client.on('entity_equipment', (packet: IEquipment) =>
this.findOrLogger<IAlive>('equipment', packet, this.getAlive.bind(this), entity =>
entity.equipment = entity.equipment ?
entity.equipment.set(packet.slot, packet.item) :
new Map([[packet.slot, packet.item]])))
// TODO: identify (with properties)
this.client.on('entity_update_attributes', (packet: IAttributes) =>
this.findOrLogger<IAlive>('attributes', packet, this.getAlive.bind(this), entity =>
entity.attributes = packet.properties))
}
protected getConf() {
return {}
}
private findOrLogger<T>(name: string, packet: { entityId: number }, query: (enitityId: number) => T | undefined,
then: (entity: T) => void) {
const entity = query(packet.entityId)
if (entity) {
then(entity)
} else {
this.logger.warn({ msg: 'Unknown entity', in: name, id: packet.entityId })
}
}
}
// MAYBE: animation
// MAYBE: attach_entity
// MAYBE: entity_sound_effect
// MAYBE: set_passengers