WIP: Add some modules
continuous-integration/drone/push Build is failing Details

master
May B. 2020-04-17 16:26:33 +02:00
parent 94d62f2afc
commit 6ca0227fa4
15 changed files with 331 additions and 38 deletions

3
.gitignore vendored
View File

@ -6,4 +6,5 @@ coverage/
node_modules/
build/
tmp/
temp/
temp/
server/

View File

@ -1,28 +1,26 @@
import { logger } from './core/logger'
import modules from './modules'
import * as mc from 'minecraft-protocol'
import Env from './core/Env'
import { logger } from './core/logger'
import modulesTypes from './modules'
export default () => {
logger.warn('Cubbot start')
const client = mc.createClient({
host: Env.orFail('CORE_HOST'),
host: Env.get('CORE_HOST'),
password: Env.orFail('CORE_PASS'),
port: Env.getAs('CORE_PORT', Number.parseInt),
username: Env.orFail('CORE_USER'),
})
client.on('connect', () => {
logger.trace('connected')
logger.trace('Connected')
})
client.on('disconnect', packet => {
logger.info('disconnected', packet.reason)
logger.warn('Disconnected ' + packet.reason)
})
client.on('login', () => {
logger.trace('logged')
logger.trace('Logged')
})
for (const module of modules) {
module.mount(client)
}
}
const modules = modulesTypes.map(t => new t(client))
}

View File

@ -5,6 +5,10 @@ export default class Env {
public static get(key: string) {
return process.env[key]
}
public static getAs<T>(key: string, mapper: (val: string) => T): T | undefined {
const val = this.get(key)
return val ? mapper(val) : undefined
}
public static orElse(key: string, fallback: string) {
return this.get(key) || fallback

View File

@ -5,7 +5,7 @@ export const logger = pino({ level: Env.orFail('CORE_LOG') })
logger.trace('Logger created')
export function child(name: string, level?: string) {
const l = logger.child({ name, level: level || Env.orFail(name.toUpperCase() + '_LOG') })
const l = logger.child({ name, level: level || Env.get(name.toUpperCase() + '_LOG') })
l.trace('Logger created')
return l
}

3
src/core/utils.ts Normal file
View File

@ -0,0 +1,3 @@
export function longToNumber(arr: number[]) {
return arr[1] + 4294967296 * arr[0]
}

View File

@ -3,16 +3,13 @@ import Module from './Module'
export default class Chat extends Module {
public get key(): string {
return 'chat'
}
public mount(client: Client) {
this.prepare()
client.on('chat', packet => {
public mount() {
this.client.on('chat', packet => {
const message = JSON.parse(packet.message)
this.logger.info(message.text || message.extra.map((e: any) => e.text).join())
this.logger.info(message.text || (message.extra ? message.extra.map((e: any) => e.text).join() : packet))
})
}
//TODO: chat write
}

View File

@ -3,15 +3,34 @@ import Module from './Module'
export default class Combat extends Module {
public get key(): string {
return 'combat'
}
private respawn: boolean = true
public mount(client: Client) {
this.prepare()
client.on('combat_event', packet => {
this.logger.warn('death ?', packet)
public mount() {
this.client.on('combat_event', packet => {
switch (packet.event) {
case 0:
this.logger.warn('fighting')
break
case 1:
this.logger.info('fighting entity %s', packet.entityId)
break
case 2:
this.logger.info('dead player %s by %s', packet.playerId, packet.entityId)
//TODO: is me ?
//TODO: print chat message
if (this.respawn) {
//TODO: use Life.respawn()
this.client.write('client_command', { action: 1 })
}
}
})
}
//TODO: respawn or disconnect
//TODO: aura
//TODO: Interact Entity
}

12
src/modules/Entities.ts Normal file
View File

@ -0,0 +1,12 @@
//TODO: spawn_entity_living
//TODO: entity_metadata
//TODO: entity_update_attributes
//TODO: entity_equipment
//TODO: entity_move_look
//TODO: entity_head_rotation
//TODO: rel_entity_move
//TODO: entity_velocity
//TODO: entity_destroy
//TODO: entity_teleport
//TODO: entity_look
//TODO: set_passengers

9
src/modules/Inventory.ts Normal file
View File

@ -0,0 +1,9 @@
//TODO: window_items
//TODO: set_slot
//TODO: declare_recipes
/* Action */
//TODO: Pick Item
//TDOO: craft
//TODO: Held Item Change

68
src/modules/Life.ts Normal file
View File

@ -0,0 +1,68 @@
import { Client } from 'minecraft-protocol'
import Module from './Module'
export default class Life extends Module {
private _health!: number
public get health() {
return this._health
}
private _food!: number
public get food() {
return this._food
}
private _saturation!: number
public get saturation() {
return this._saturation
}
public get alive() {
return this.health > 0
}
private _experienceBar!: number
public get experienceBar() {
return this._experienceBar
}
private _level!: number
public get level() {
return this._level
}
private _totalExperience!: number
public get totalExperience() {
return this._totalExperience
}
public mount() {
this.client.on('update_health', data => {
if (this._health == null) {
setTimeout(this.respawn.bind(this), 500)
}
this._health = data.health
this._food = data.food
this._saturation = data.foodSaturation
if (this.health > 5) {
this.logger.info('health: %s', this.health)
} else {
this.logger.warn('low health: %s', this.health)
}
})
this.client.on('experience', data => {
this._experienceBar = data.experienceBar
this._level = data.level
this._totalExperience = data.totalExperience
})
}
public respawn() {
this.logger.info('respawn')
this.client.write('client_command', { action: 1 })
}
//TODO: send client settings
}

16
src/modules/Map.ts Normal file
View File

@ -0,0 +1,16 @@
/* Data */
//TODO: map_chunk
//TODO: world_border
//TODO: spawn_position
//TODO: block_change multi_block_change
//TODO: weather_entity aka thunderbolt
/* Actions */
//TODO: query_block_nbt
//TODO: digging

View File

@ -3,13 +3,14 @@ import { Logger } from 'pino'
import { child } from '../core/logger'
export default abstract class IModule {
protected logger!: Logger
protected logger: Logger
protected client: Client
public abstract get key(): string;
public abstract mount(client: Client): void
protected prepare(): void {
this.logger = child(this.key)
constructor(client: Client) {
this.client = client
this.logger = child(this.constructor.name)
this.mount()
}
protected abstract mount(): void
}

102
src/modules/State.ts Normal file
View File

@ -0,0 +1,102 @@
import { Client } from 'minecraft-protocol'
import Module from './Module'
//MAYBE: split
interface IState {
entityId: number,
gameMode: 0|1|2|3,
dimension: number,
hashedSeed: number[],
maxPlayers: number,
levelType: string,
viewDistance: number,
reducedDebugInfo: boolean,
enableRespawnScreen: boolean
}
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 IPosition {
x: number,
y: number,
z: number,
yaw: number,
pitch: number,
flags: number,
teleportId: number
}
//TODO: entity_status {entityId, entityStatus} aka animations
//TODO: player_info
export default class State extends Module {
public positionConfirm: boolean = true
private _state!: IState
public get state() {
return this._state
}
private _difficulty!: IDifficulty
public get difficulty() {
return this._difficulty
}
private _abilities!: IDifficulty
public get abilities() {
return this._abilities
}
private _position!: IPosition
public get position() {
return this._position
}
public mount() {
this.client.on('login', data => this._state = data)
this.client.on('difficulty', data => this._difficulty = {
level: data.difficulty, locked: data.difficultyLocked,
})
this.client.on('abilities', data => this._abilities = data)
this.client.on('position', (data: IPosition) => {
if (this._position == null) {
if (data.flags) {
this.logger.error('first postion 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),
flags: 0,
teleportId : -1,
}
}
if (this.positionConfirm) {
this.client.write('teleport_confirm', { teleportId: data.teleportId })
}
this.logger.debug({ type: 'teleported', position: this.position })
})
}
}

57
src/modules/Time.ts Normal file
View File

@ -0,0 +1,57 @@
import { Client } from 'minecraft-protocol'
import { longToNumber } from '../core/utils'
import Module from './Module'
interface IUpdateTime {
age: number[]
time: number[]
}
export default class Time extends Module {
private _age!: number
public get age() {
return this._age
}
private _time!: number
public get time() {
return this._time
}
private _tps!: number
public get tps() {
return this._tps
}
private _tpsLocal!: number
public get tpsLocal() {
return this._tpsLocal
}
private _at: number = Date.now()
public mount() {
this.client.on('update_time', (data: IUpdateTime) => {
const now = Date.now()
const age = longToNumber(data.age)
if (this._age) {
this._tps = age - this._age
if (this.tps < 20) {
this.logger.debug('Server Lag: %s', this.tps)
}
this._tpsLocal = (now - this._at) / 50
if (this._tpsLocal > 20 + .5) {
this.logger.debug('Client Lag: %s', this.tpsLocal)
}
}
this._age = age
this._at = now
this._time = Math.abs(data.time[1]) % 24000
})
}
}

View File

@ -1,7 +1,13 @@
import Chat from './Chat'
import Combat from './Combat'
import Life from './Life'
import State from './State'
import Time from './Time'
export default [
new Chat(),
new Combat(),
]
State,
Time,
Life,
Chat,
Combat,
]