cubbot/src/modules/Proxy.ts

198 lines
7.4 KiB
TypeScript

import { Client, createServer, Server, ServerOptions } from 'minecraft-protocol'
import Module from '../utils/Module'
import { IPositionPacket } from '../utils/types'
import Inventory from './Inventory'
import State from './State'
interface IConfig {
cache: boolean
commandPrefix: string
autoClose: boolean
server: ServerOptions
}
const CLIENT_CUSTOM_EVENTS = ['chat', 'tab_complete', 'keep_alive', 'update_time']
const SERVER_CUSTOM_EVENTS = ['tab_complete', 'declare_commands']
export default class Proxy extends Module<IConfig> {
private buffer: any[] = []
private proxyClient?: Client
private server?: Server
public umount() {
if (this.proxyClient) {
this.logger.info('Kicking player')
this.proxyClient.end('Connection reset by target server.')
this.proxyClient = undefined
}
if (this.server) {
this.logger.info('Closing server')
this.server.close()
this.server = undefined
}
}
protected mount(): void {
this.client.once('login', lstate => {
this.logger.debug({ msg: 'Starting server', port: this.conf.server.port })
this.server = createServer({
errorHandler: (_, err) => this.logger.error(err.message),
motd: `Proxy for ${this.client.socket.remoteAddress}:${this.client.socket.remotePort}`,
version: (this.client as any).version,
...this.conf.server,
})
this.server.on('login', (proxyClient: Client) => { // handle login
this.logger.info('Player connected')
if (this.conf.cache) {
this.buffer.forEach(data => proxyClient.writeRaw(data))
this.conf.cache = this.conf.cache && !this.conf.autoClose
if (!this.conf.cache) {
this.buffer = []
}
} else {
proxyClient.write('login', { ...lstate, maxPlayers: this.server!.maxPlayers })
proxyClient.write('position', {
x: 0,
y: 1.62,
z: 0,
pitch: 0,
yaw: 0,
flags: 0x00,
})
}
proxyClient.on('raw', (data, meta) => {
if (meta.state === 'play') {
if (!CLIENT_CUSTOM_EVENTS.includes(meta.name)) {
this.client.writeRaw(data)
}
}
})
proxyClient.on('chat', ({ message }: { message: string }) => {
if (message.startsWith(this.conf.commandPrefix)) {
const cmd = message.slice(this.conf.commandPrefix.length)
this.logger.info(cmd)
} else {
this.client.write('chat', { message })
}
})
proxyClient.on('tab_complete', data => {
/* Sent when the client needs to tab-complete a minecraft:ask_server suggestion type. */
this.logger.error({ msg: 'TODO:', data })
/*
{
transactionId: number,
start: number,
length: number,
matches: [
{
match: string,
tooltip: option | string
}
]
}*/
})
// TODO: override declare_commands
proxyClient.on('end', reason => {
this.logger.info({ msg: 'Player disconnected', reason })
if (this.conf.autoClose) {
this.umount()
} // FIXME: must handle and reemit full state to reconnect properly without storing all packets
})
// TODO: reemit actions from proxyClient (position, ...)
// TODO: reemit actions from other modules
// Binding with Inventory
const inv = this.get<Inventory>(Inventory)
if (inv) {
// MAYBE: add this.client.out.on('held_item_slot', { slotId })
inv.registerHeld(slot => proxyClient.write('held_item_slot', { slot }))
proxyClient.on('held_item_slot', ({ slotId }) =>
this.client.emit('held_item_slot', { slot: slotId }))
proxyClient.on('window_click', packet => this.logger.info({ msg: 'click', packet }))
// TODO: set_slot
}
// Binding with State
const state = this.get<State>(State)
if (state) {
// tslint:disable: no-bitwise
// TODO: onTranslate ?
proxyClient.on('position', ({ x, y, z }) => {
const position: IPositionPacket = {
flags: 0x08 | 0x10, // Relative look
pitch: 0,
yaw: 0,
x, y, z,
teleportId: 0, // Disable confirm_teleport
}
this.client.emit('position', position)
})
proxyClient.on('look', ({ pitch, yaw }) => {
const position: IPositionPacket = {
flags: 0x01 | 0x02 | 0x4, // Relative position
x: 0,
y: 0,
z: 0,
pitch, yaw,
teleportId: 0, // Disable confirm_teleport
}
this.client.emit('position', position)
})
proxyClient.on('position_look', ({ x, y, z, pitch, yaw }) => {
const position: IPositionPacket = {
flags: 0, // Absolute
x, y, z, pitch, yaw,
teleportId: 0, // Disable confirm_teleport
}
this.client.emit('position', position)
})
}
// MAYBE: add cheats
/*
"0x11": "tab_complete",
"0x12": "declare_commands",
"0x17": "set_slot",
"0x18": "set_cooldown",
*/
this.proxyClient = proxyClient
})
})
this.client.on('raw', (buffer, meta) => {
if (meta.state === 'play') {
if (this.conf.cache) {
this.buffer.push(buffer)
}
if (this.proxyClient && !SERVER_CUSTOM_EVENTS.includes(meta.name)) {
this.proxyClient.writeRaw(buffer)
}
}
})
}
protected getConf(): IConfig {
return {
cache: false,
commandPrefix: '%',
autoClose: true,
server: {
host: '0.0.0.0',
port: 25565,
maxPlayers: 1,
'online-mode': false,
},
}
}
}