Good night
continuous-integration/drone/push Build is passing Details

master
May B. 2020-04-20 23:07:23 +02:00
parent ea4101f8b6
commit 2dc150c38b
21 changed files with 2927 additions and 3650 deletions

1
.gitignore vendored
View File

@ -7,4 +7,5 @@ node_modules/
build/
tmp/
temp/
data/
server/

View File

@ -1,8 +1,8 @@
FROM node
WORKDIR /app
COPY . .
RUN npm install
RUN npm build
RUN yarn install
RUN yarn run build
FROM node
WORKDIR /app

View File

@ -11,6 +11,7 @@ Scalliony API and UI
- [Usage](#usage)
- [Config](#config)
- [Run](#run)
- [Note](#note)
- [Tests](#tests)
- [License](#license)
@ -19,7 +20,7 @@ Cubbot uses modules with could be enabled at compile time in `src/modules/index.
## Building
```
npm run build
yarn run build
```
## Usage
@ -30,12 +31,15 @@ npm run build
### Run
```
npm run test
yarn run test
```
### Note
`minecraft-data` package isn't updated for 1.15+. As a workaround, data will be downloader for `https://raw.githubusercontent.com/Pokechu22/Burger/gh-pages/{version}.json` and cached in `data` folder
## Tests
```
npm run test
yarn run test
```
## License

0
data/.gitkeep Normal file
View File

View File

@ -6,10 +6,10 @@ steps:
image: node
commands:
- yarn install
- npm run test:coverage
- yarn run test:coverage
- name: build
image: node
commands:
- yarn install
- npm run build
- yarn run build

View File

@ -8,7 +8,11 @@
"level": "info"
},
"modules": {
"Client": {},
"Connection": {
"reconnect": [
"idling"
]
},
"Combat": {},
"Chat": {},
"Time": {

3485
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ import * as mc from 'minecraft-protocol'
import { default as pino } from 'pino'
import moduleList from './modules'
import Module, { IModuleType } from './utils/Module'
import Client from './modules/Connection'
interface IConfig {
/** Minecraft protocol options */
@ -41,7 +42,7 @@ export default class Cubbot {
public registerModule(module: IModuleType, name?: string) {
const n: string = name || module.name
if (this._registeredModules.has(n)) {
this.logger.warn('Overriding module %s', n)
this.logger.warn({ msg: 'Overriding module', value: n })
}
this._registeredModules.set(n, module)
}
@ -76,12 +77,13 @@ export default class Cubbot {
this.logger.debug('Loading modules')
this._modules = new Map<string, Module<{}>>()
this.loadModule<Client>(Client).setEngine(this)
Object.keys(this.config.modules).forEach(this.loadModuleByName.bind(this))
}
}
/** Stop bot */
public umount() {
public umount(exit: boolean = false) {
if (this.client) {
this.logger.debug('Unloading modules')
this.modules!.forEach(m => m.umount())
@ -91,8 +93,11 @@ export default class Cubbot {
//TODO: disconnect
this._client = undefined
this.logger.warn('Stopped')
this.logger.flush()
if (exit) {
this.logger.warn('Stopped')
this.logger.flush()
setTimeout(process.exit, 100)
}
}
}

View File

@ -7,7 +7,4 @@ const config = JSON.parse(stripJsonComments(readFileSync('./env.json').toString(
const app = new Cubbot(config)
app.mount()
process.on('SIGINT', () => {
app.umount()
setTimeout(process.exit, 100)
})
process.on('SIGINT', () => app.umount(true))

View File

@ -1,24 +1,23 @@
import md from 'minecraft-data'
import { vsprintf } from 'sprintf-js'
import Module from '../utils/Module'
import { IDict } from '../utils/types'
import Connection from './Connection'
/** Message packet payload */
interface IMessage { text?: string, extra?: Array<{text: string}>, translate?: string, with?: IMessage[] }
export interface IMessage { text?: string, extra?: Array<{text: string}>, translate?: string, with?: IMessage[] }
/** Handle chat display and replies */
export default class Chat extends Module<{ reply?: string }> {
/** Translation dictionary (en only) */
private dict!: { [key: string]: string }
export default class Chat extends Module<{ reply?: string, bye?: string }> {
/** Convert payload to string */
public parse(msg: IMessage) {
public static parse(msg: IMessage, language: IDict) {
let text = ''
if (msg.text) {
text += msg.text
}
if (msg.translate && msg.translate in this.dict) {
text += vsprintf(this.dict[msg.translate], (msg.with || []).map(this.parse.bind(this)))
if (msg.translate && msg.translate in language) {
text += vsprintf(language[msg.translate], (msg.with || [])
.map(w => typeof w === 'string' ? w : Chat.parse(w, language)))
}
if (msg.extra) {
text += msg.extra.map(({text: t}) => t).join()
@ -26,11 +25,23 @@ export default class Chat extends Module<{ reply?: string }> {
return text
}
private connection!: Connection
public parse(msg: IMessage) {
return Chat.parse(msg, this.connection.oldData.language)
}
public write(message: string) {
this.client.write('chat', { message })
}
public umount() {
if (this.connection.connected && this.conf.bye) {
this.write(this.conf.bye)
}
}
protected mount() {
this.client.on('login', () => {
// FIXME: use this.client.version when 'minecraft-data' include 1.15 language
this.dict = md('1.14').language
})
this.connection = this.load<Connection>(Connection)
this.client.on('chat', packet => {
const message = JSON.parse(packet.message)
@ -38,7 +49,6 @@ export default class Chat extends Module<{ reply?: string }> {
})
}
//TODO: chat write
//TODO: reply
protected getConf() {

View File

@ -1,32 +0,0 @@
import Module from '../utils/Module'
/** Conection informations */
export default class Client extends Module<{ reconnect: boolean }> {
public umount() {
//TODO: proper disconnect
}
protected mount() {
this.client.on('connect', () => {
this.logger.trace('Connected')
})
this.client.on('disconnect', packet => {
this.logger.warn('Disconnected %s' + packet.reason)
})
this.client.on('kick_disconnect', packet => {
this.logger.warn('Kicked %s', packet.reason)
})
this.client.on('login', () => {
this.logger.trace('Logged')
})
this.client.on('error', error => {
this.logger.error('error %o', error)
})
}
protected getConf() {
return { reconnect: true }
}
}

View File

@ -2,25 +2,32 @@ import { ATTACK_DIST2, SERVER_TICK_RATE } from '../utils/constants'
import { dist2 } from '../utils/func'
import Module from '../utils/Module'
import { IDelta } from '../utils/types'
import Entities, { IAlive } from './Entities'
import Connection from './Connection'
import Entities, { IAlive, ILiving, IPlayer } from './Entities'
import Life from './Life'
import State from './State'
interface IConf {
/** Respawn after death */
respawn: boolean,
respawn: boolean
/** Attack dangerous entities */
fight: boolean,
fight: boolean
/** Must change weapon */
weapon: 'none' | 'needed' | 'always',
weapon: 'none' | 'needed' | 'always'
/** Must react to arrows */
arrows: 'ignore' | 'dodge' | 'shield'
/** Time between attacks */
delay: number | 'item' | 'safe',
delay: number | 'item' | 'safe'
/** Attacks multiple entities at ones */
multiaura: boolean,
multiaura: boolean
/** Entity to target first */
priority: 'nearest'
/** Players names */
friends: string[]
/** Targeted entities names */
mobs: string[]
/** Targeted entities ids (override mobs) */
mobsIds?: number[]
}
interface IEnemy extends IAlive {
@ -33,7 +40,9 @@ const SAFE_DELAY = SERVER_TICK_RATE * 2
export default class Combat extends Module<IConf> {
private state!: State
private life!: Life
private entities!: Entities
private connection!: Connection
public umount() {
this.conf.fight = false
@ -42,7 +51,9 @@ export default class Combat extends Module<IConf> {
protected mount() {
this.state = this.load<State>(State)
this.life = this.load<Life>(Life)
this.entities = this.load<Entities>(Entities)
this.connection = this.load<Connection>(Connection)
this.fight()
this.arrows()
@ -50,19 +61,21 @@ export default class Combat extends Module<IConf> {
this.client.on('combat_event', packet => {
switch (packet.event) {
case 0:
this.logger.warn('fighting')
this.logger.warn({ msg: 'Fighting' })
break
case 2:
if (packet.playerId === this.state.state.entityId) {
this.logger.error('%o killed me at %o',
this.entities.getAlive(packet.entityId), this.state.position)
this.logger.error({
msg: 'Killed', type: 'entity',
value: this.entities.getAlive(packet.entityId),
})
// MAYBE: chat packet.message
if (this.conf.respawn) {
this.load<Life>(Life).respawn()
this.life.respawn()
}
} else {
this.logger.info('killed %o', packet)
this.logger.info({ msg: 'Fight end', packet })
}
}
})
@ -77,6 +90,14 @@ export default class Combat extends Module<IConf> {
delay: 'safe',
multiaura: false,
priority: 'nearest',
friends: [],
mobs: [ // TODO: find if aggressive
'blaze', 'cave_spider', 'creeper', 'drowned', 'elder_guardian', 'ender_dragon', 'enderman', 'endermite',
'evoker', 'evoker_fangs', 'ghast', 'giant', 'guardian', 'husk', 'illusioner', /*'iron_golem',*/
'magma_cube', 'phantom', 'pillager', 'player', 'ravager', 'shulker', 'silverfish', 'skeleton', 'slime',
/*'snow_golem',*/ 'spider', 'stray', 'vex', 'vindicator', 'witch', 'wither', 'wither_skeleton',
/*'wolf',*/ 'zombie', /*'zombie_pigman',*/ 'zombie_villager',
],
}
}
@ -84,11 +105,31 @@ export default class Combat extends Module<IConf> {
if (this.conf.fight) {
//TODO: pick weapon if change wait
//TODO: pick shield
if (!this.conf.mobsIds) {
if (this.connection.data?.ready) {
this.conf.mobsIds = this.conf.mobs
.map(name => this.connection.data!.entities![name].id)
} else {
this.logger.warn('Waiting data')
setTimeout(this.fight.bind(this), SAFE_DELAY)
}
}
// Find dangerous entities
const dangers: IEnemy[] = ([...this.entities.players, ...this.entities.livings] as IAlive[])
.filter(({ entityId }) => entityId !== this.state.state.entityId)
//TODO: filter friends, creative, passives
.filter(e => {
if ((e as ILiving).type !== undefined) { //TODO: add type to players
return this.conf.mobsIds!.includes((e as ILiving).type)
}
if ((e as IPlayer).playerUUID !== undefined && !(e as IPlayer).me) {
const data = this.state.players.get((e as IPlayer).playerUUID)
if (!data) {
return false
}
return (data.gamemode % 2 === 0) && !this.conf.friends.includes(data.name)
}
return false
})
.map(e => ({ reach2: dist2(this.state.position, e), ...e })) // TODO: include box
.filter(({ reach2 }) => reach2 <= ATTACK_DIST2) // MAYBE: as option
.sort((a, b) => a.reach2 - b.reach2)
@ -96,10 +137,10 @@ export default class Combat extends Module<IConf> {
// MAYBE: include entity speed (for tiny zombie or phantom)
if (dangers.length > 0) {
this.logger.info('fighting %s target(s)', dangers.length);
this.logger.info({ msg: 'Fighting', type: 'count', value: dangers.length });
(this.conf.multiaura ? dangers : dangers.slice(0, 1)).forEach(({ entityId }) => {
this.logger.debug('attack %s', entityId)
this.logger.debug({ msg: 'Attack', type: 'entityId', value: entityId })
this.entities.attack(entityId)
})
}
@ -107,7 +148,7 @@ export default class Combat extends Module<IConf> {
const attackSpeed = 1.6 //TODO: get from inventory
const delay = typeof this.conf.delay === 'number' ? this.conf.delay : (
this.conf.delay === 'safe' && dangers.length == 0 ? SAFE_DELAY : // TODO: exponential back-off
this.conf.delay === 'safe' && dangers.length === 0 ? SAFE_DELAY : // TODO: exponential back-off
1 / attackSpeed * 20 * SERVER_TICK_RATE)
setTimeout(this.fight.bind(this), delay)
@ -120,7 +161,7 @@ export default class Combat extends Module<IConf> {
.filter(e => e.type === 2 && // arrow
dist2(this.state.position, e) <= 99)
if (arrows.length > 0) {
this.logger.warn('%s arrows', arrows.length)
this.logger.warn({ msg: 'Arrows', type: 'count', value: arrows.length })
this.state.translate(arrows.reduce((p, arrow) =>
Math.abs(arrow.velocityX) < Math.abs(arrow.velocityZ) ?

101
src/modules/Connection.ts Normal file
View File

@ -0,0 +1,101 @@
import md from 'minecraft-data'
import Cubbot from '../Cubbot'
import Data from '../utils/Data'
import Module from '../utils/Module'
import Chat, { IMessage } from './Chat'
type DisconnectReason = 'authservers_down' | 'banned' | 'banned.reason' | 'banned.expiration' | 'banned_ip.reason' |
'banned_ip.expiration' | 'duplicate_login' | 'flying' | 'generic' | 'idling' | 'illegal_characters' |
'invalid_entity_attacked' | 'invalid_player_movement' | 'invalid_vehicle_movement' | 'ip_banned' | 'kicked' |
'outdated_client' | 'outdated_server' | 'server_shutdown' | 'slow_login' | 'unverified_username' |
'not_whitelisted' | 'server_full' | 'name_taken' | 'unexpected_query_response' | 'genericReason' |
'disconnected' | 'lost' | 'kicked' | 'timeout' | 'closed' | 'loginFailed' | 'loginFailedInfo' |
'loginFailedInfo.serversUnavailable' | 'loginFailedInfo.invalidSession' | 'quitting' | 'endOfStream' |
'overflow' | 'spam'
const REASON_PREFIX = 'disconnect.'
interface IConfig {
/** Game data url */
dataSource: string
/** Game data cache path */
dataDir: string
reconnect: DisconnectReason[] | boolean
reconnectDelay: number
}
/** Connection informations */
export default class Connection extends Module<IConfig> {
private _connected = false
public get connected() {
return this._connected
}
private _oldData = md('1.14')
public get oldData() {
return this._oldData
}
private _data?: Data
public get data() {
return this._data
}
private _engine?: Cubbot
public setEngine(value: Cubbot) {
this._engine = value
}
protected mount() {
this.client.on('connect', () => {
this._connected = true
this.logger.trace('Connected')
})
this.client.on('disconnect', ({ reason }) => {
this._connected = false
const message: IMessage = JSON.parse(reason)
this.logger.warn({ msg: 'Disconnected', type: 'reason', value: Chat.parse(message, this.oldData.language) })
this.bye(message.translate)
})
this.client.on('kick_disconnect', ({ reason }) => {
this._connected = false
const message: IMessage = JSON.parse(reason)
this.logger.warn({ msg: 'Kicked', type: 'reason', value: Chat.parse(message, this.oldData.language) })
this.bye(message.translate)
})
this.client.on('login', () => {
this.logger.trace('Logged')
this._data = new Data(this.conf.dataSource, this.conf.dataDir, (this.client as any).version,
this.logger.child({ name: 'Data', level: 'info' }))
// FIXME: use 'minecraft-data' when it include 1.15+
})
this.client.on('error', error => {
this.logger.error({ msg: 'Error', value: error })
})
}
protected getConf() {
return {
dataDir: './data/',
dataSource: 'https://pokechu22.github.io/Burger/',
reconnect: false,
reconnectDelay: 5000,
}
}
private bye(reason?: string) {
if (this.conf.reconnect === true || (this.conf.reconnect !== false && reason !== undefined &&
this.conf.reconnect.some(r => reason.endsWith(REASON_PREFIX + r)))
) {
this._engine?.umount(false)
this.logger.info({ msg: 'Reconnecting', type: 'ms', value: this.conf.reconnectDelay })
// MAYBE: exponental backoff
setTimeout(() => this._engine?.mount(), this.conf.reconnectDelay)
} else {
this.logger.info('Bye')
this._engine?.umount(true)
}
}
}

View File

@ -68,7 +68,7 @@ export interface IAlive extends IMovableE, IEquipable, IAttributable, IMetadatab
headPitch: number
}
interface ILiving extends IAlive {
export interface ILiving extends IAlive {
entityUUID: string
type: number
}
@ -76,7 +76,7 @@ interface ILiving extends IAlive {
interface IPlayerSpawn extends IPositioned {
playerUUID: string
}
interface IPlayer extends IPlayerSpawn, IAlive {
export interface IPlayer extends IPlayerSpawn, IAlive {
me?: boolean
}
@ -172,117 +172,86 @@ export default class Entities extends Module<{}> {
// MAYBE: this.client.on('animation', packet => { })
// MAYBE: this.client.on('entity_status', packet => { })
this.client.on('rel_entity_move', (packet: IMove) => {
const entity = this.getEntity(packet.entityId)
if (entity) {
applyDelta(entity, packet)
} else {
this.logger.warn('rel_move of unknown entity %s', packet.entityId)
}
})
this.client.on('entity_move_look', (packet: IMoveLook) => {
const entity = this.getEntity(packet.entityId)
if (entity) {
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
} else {
this.logger.warn('move_look of unknown entity %s', packet.entityId)
}
})
this.client.on('entity_look', (packet: ILook) => {
const entity = this.getEntity(packet.entityId)
if (entity) {
}))
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
} else {
this.logger.warn('look of unknown entity %s', packet.entityId)
}
})
this.client.on('entity_head_rotation', (packet: IHead) => {
const entity = this.getAlive(packet.entityId)
if (entity) {
entity.headPitch = packet.headPitch
} else {
this.logger.warn('head_rotation of unknown entity %s', packet.entityId)
}
})
this.client.on('entity_velocity', (packet: IVelocityE) => {
const entity = this.getEntity(packet.entityId)
if (entity) {
}))
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
} else {
this.logger.warn('velocity of unknown entity %s', packet.entityId)
}
})
this.client.on('entity_teleport', (packet: IPositioned) => {
const entity = this.getEntity(packet.entityId)
if (entity) {
}))
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
} else {
this.logger.warn('teleport of unknown entity %s', packet.entityId)
}
})
}))
this.client.on('entity_destroy', (packet: { entityIds: number[] }) => {
packet.entityIds.forEach(id => {
if (!this.deleteEntity(id)) {
this.logger.debug('can not delete entity %s', id)
this.logger.debug({ msg: 'Can not delete entity', id })
}
})
})
// TODO: identify metadatas
this.client.on('entity_metadata', (packet: IMetadatas) => {
const entity = this.getMetadatable(packet.entityId)
if (entity) {
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)
}
} else {
this.logger.warn('metadata of unknown entity %s (%s)', packet.entityId)
}
})
}))
// MAYBE: this.client.on('entity', (packet: IId) => { })
// TODO: entity_effect, remove_entity_effect
this.client.on('entity_equipment', (packet: IEquipment) => {
const entity = this.getAlive(packet.entityId)
if (entity) {
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]])
} else {
this.logger.warn('equipment of unknown entity %s', packet.entityId)
}
})
new Map([[packet.slot, packet.item]])))
// TODO: identify (with properties)
this.client.on('entity_update_attributes', (packet: IAttributes) => {
const entity = this.getAlive(packet.entityId)
if (entity) {
entity.attributes = packet.properties
} else {
this.logger.warn('attributes of unknown entity %s', packet.entityId)
}
})
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 })
}
}
}
// TODO: player info

View File

@ -83,19 +83,19 @@ export default class Life extends Module<IConf> {
this._saturation = data.foodSaturation
if (this.health <= this.conf.lowHealth) {
this.logger.warn({ type: 'health', status: this.alive ? 'low' : 'ko', health: this.health })
this.logger.warn({ msg: this.alive ? 'Low health' : 'Dead', type: 'health', value: this.health })
} else if (this.conf.showHealth) {
this.logger.info({ type: 'health', status: 'ok', health: this.health })
this.logger.info({ msg: 'Healthy', type: 'health', value: this.health })
}
if (this.food <= this.conf.lowFood) {
this.logger.warn({ type: 'food', status: 'low', food: this.food, saturation: this.saturation })
this.logger.warn({ msg: 'Hungry', type: 'food', value: this.food })
} else if (this.conf.showHealth) {
this.logger.info({ type: 'food', status: 'ok', food: this.food, saturation: this.saturation })
this.logger.info({ msg: 'Replete', type: 'food', value: this.food })
}
if (this.conf.eat && this.mustEat) {
this.logger.warn('TODO: must Eat')
this.logger.warn({ msg: 'TODO: Must eat' })
// TODO: inventory.items.filter(food).orderBy(this.conf.preferSaturation)
}
})

View File

@ -1,5 +1,5 @@
import Module from '../utils/Module'
import { IDelta, IPosition, IState } from '../utils/types'
import { IDelta, IPosition, IState, gameMode } from '../utils/types'
// MAYBE: split
@ -19,7 +19,13 @@ interface IPositionPacket extends IPosition {
teleportId: number
}
// TODO: player_info
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 }> {
@ -34,7 +40,7 @@ export default class State extends Module<{ positionConfirm: boolean }> {
return this._difficulty
}
private _abilities!: IDifficulty
private _abilities!: IAbilities
public get abilities() {
return this._abilities
}
@ -44,6 +50,11 @@ export default class State extends Module<{ positionConfirm: boolean }> {
return this._position
}
private _players = new Map<string, IPlayerInfo>()
public get players() {
return this._players
}
public translate(delta: IDelta, onGround: boolean = true) {
this.client.write('position', {
onGround,
@ -63,7 +74,7 @@ export default class State extends Module<{ positionConfirm: boolean }> {
this.client.on('position', (data: IPositionPacket) => {
if (this._position == null) {
if (data.flags) {
this.logger.error('first position is relative')
this.logger.error('First position is relative')
return
} else {
this._position = data
@ -82,7 +93,29 @@ export default class State extends Module<{ positionConfirm: boolean }> {
if (this.conf.positionConfirm) {
this.client.write('teleport_confirm', { teleportId: data.teleportId })
}
this.logger.debug({ type: 'teleported', position: this.position })
this.logger.debug({ msg: 'Teleported', type: 'position', value: this.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:
this._players.get(player.UUID)!.gamemode = player.gamemode
break
case 2:
this._players.get(player.UUID)!.ping = player.ping
break
case 3:
this._players.get(player.UUID)!.name = player.name
break
case 4:
this._players.delete(player.UUID)
break
}
}
})
}

View File

@ -74,17 +74,17 @@ export default class Time extends Module<IConf> {
if (this._distAge) {
this._tps = age - this._distAge
if (this.tps <= this.conf.minTps) {
this.logger.debug('Server Lag: %s / 20 tps', this.tps, SERVER_TPS)
this.logger.debug({ msg: 'Server lag', type: 'tps', value: this.tps })
}
this._spt = (now - this._at) / this.tps
if (this.spt >= this.conf.maxSpt) {
this.logger.debug('Network Lag: %s / %s spt', this.spt, SERVER_TICK_RATE)
this.logger.debug({ msg: 'Network lag', type: 'spt', value: this.spt })
}
const offset = age - this._age
if (Math.abs(offset) > this.conf.maxOffset) {
this.logger.debug('Client Off-sync %s ticks', offset)
this.logger.debug({ msg: 'Client OffSync', type: 'ticks', value: offset })
}
}

View File

@ -1,6 +1,6 @@
import { IModuleType } from '../utils/Module'
import Chat from './Chat'
import Client from './Client'
import Client from './Connection'
import Combat from './Combat'
import Entities from './Entities'
import Life from './Life'

70
src/utils/Data.ts Normal file
View File

@ -0,0 +1,70 @@
import fs, { readFileSync } from 'fs'
import https from 'https'
import { Logger } from 'pino'
export interface IEntityData {
id: number
name: string
display_name: string
height: number
width: number
metadata: Array<{ entity?: string }>
}
/** Load game data */
export default class Data {
private _ready = false
public get ready() {
return this._ready
}
private _entities?: { [name: string]: IEntityData }
public get entities() {
return this._entities
}
constructor(source: string, directory: string, version: string, logger: Logger) {
logger.debug('Loading data file')
const path = directory + version + '.json'
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory)
}
if (!fs.existsSync(path)) {
logger.warn({ msg: 'Data file must be downloaded', type: 'version', value: version, note: 'will probably fail' })
const url = source + version + '.json'
const out = fs.createWriteStream(path)
https.get(url, res => {
res.on('data', data => out.write(data))
res.on('error', error => console.error({ msg: 'Downloading error', error }))
res.on('end', () => {
out.close()
setTimeout(() => this.load(path), 500)
})
}).on('error', error => console.error({ msg: 'Downloading error', error }))
} else {
this.load(path)
}
}
public getEntityById(entityId: number) {
if (this._entities) {
for (const name in (this._entities as object)) {
if (this._entities[name].id === entityId) {
return this._entities[name]
}
}
}
}
private load(path: string) {
try {
const data = JSON.parse(readFileSync(path).toString())[0] // NOTE: memory ???
this._entities = data.entities.entity
this._ready = true
} catch (error) {
console.error('/!\\ =( Please remove ' + path, error)
process.exit(1)
}
}
}

View File

@ -32,9 +32,10 @@ export interface ISlot {
//TODO:
}
export type gameMode = 0|1|2|3
export interface IState {
entityId: number,
gameMode: 0|1|2|3,
gamemode: gameMode,
dimension: number,
hashedSeed: number[],
maxPlayers: number,
@ -43,3 +44,5 @@ export interface IState {
reducedDebugInfo: boolean,
enableRespawnScreen: boolean
}
export interface IDict { [key: string]: string }

2556
yarn.lock Normal file

File diff suppressed because it is too large Load Diff