Good night
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
ea4101f8b6
commit
2dc150c38b
|
@ -7,4 +7,5 @@ node_modules/
|
||||||
build/
|
build/
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
|
data/
|
||||||
server/
|
server/
|
|
@ -1,8 +1,8 @@
|
||||||
FROM node
|
FROM node
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm install
|
RUN yarn install
|
||||||
RUN npm build
|
RUN yarn run build
|
||||||
|
|
||||||
FROM node
|
FROM node
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
10
README.md
10
README.md
|
@ -11,6 +11,7 @@ Scalliony API and UI
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Config](#config)
|
- [Config](#config)
|
||||||
- [Run](#run)
|
- [Run](#run)
|
||||||
|
- [Note](#note)
|
||||||
- [Tests](#tests)
|
- [Tests](#tests)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ Cubbot uses modules with could be enabled at compile time in `src/modules/index.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
```
|
```
|
||||||
npm run build
|
yarn run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -30,12 +31,15 @@ npm run build
|
||||||
|
|
||||||
### Run
|
### 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
|
## Tests
|
||||||
```
|
```
|
||||||
npm run test
|
yarn run test
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -6,10 +6,10 @@ steps:
|
||||||
image: node
|
image: node
|
||||||
commands:
|
commands:
|
||||||
- yarn install
|
- yarn install
|
||||||
- npm run test:coverage
|
- yarn run test:coverage
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: node
|
image: node
|
||||||
commands:
|
commands:
|
||||||
- yarn install
|
- yarn install
|
||||||
- npm run build
|
- yarn run build
|
|
@ -8,7 +8,11 @@
|
||||||
"level": "info"
|
"level": "info"
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules": {
|
||||||
"Client": {},
|
"Connection": {
|
||||||
|
"reconnect": [
|
||||||
|
"idling"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Combat": {},
|
"Combat": {},
|
||||||
"Chat": {},
|
"Chat": {},
|
||||||
"Time": {
|
"Time": {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@ import * as mc from 'minecraft-protocol'
|
||||||
import { default as pino } from 'pino'
|
import { default as pino } from 'pino'
|
||||||
import moduleList from './modules'
|
import moduleList from './modules'
|
||||||
import Module, { IModuleType } from './utils/Module'
|
import Module, { IModuleType } from './utils/Module'
|
||||||
|
import Client from './modules/Connection'
|
||||||
|
|
||||||
interface IConfig {
|
interface IConfig {
|
||||||
/** Minecraft protocol options */
|
/** Minecraft protocol options */
|
||||||
|
@ -41,7 +42,7 @@ export default class Cubbot {
|
||||||
public registerModule(module: IModuleType, name?: string) {
|
public registerModule(module: IModuleType, name?: string) {
|
||||||
const n: string = name || module.name
|
const n: string = name || module.name
|
||||||
if (this._registeredModules.has(n)) {
|
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)
|
this._registeredModules.set(n, module)
|
||||||
}
|
}
|
||||||
|
@ -76,12 +77,13 @@ export default class Cubbot {
|
||||||
|
|
||||||
this.logger.debug('Loading modules')
|
this.logger.debug('Loading modules')
|
||||||
this._modules = new Map<string, Module<{}>>()
|
this._modules = new Map<string, Module<{}>>()
|
||||||
|
this.loadModule<Client>(Client).setEngine(this)
|
||||||
Object.keys(this.config.modules).forEach(this.loadModuleByName.bind(this))
|
Object.keys(this.config.modules).forEach(this.loadModuleByName.bind(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop bot */
|
/** Stop bot */
|
||||||
public umount() {
|
public umount(exit: boolean = false) {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
this.logger.debug('Unloading modules')
|
this.logger.debug('Unloading modules')
|
||||||
this.modules!.forEach(m => m.umount())
|
this.modules!.forEach(m => m.umount())
|
||||||
|
@ -91,8 +93,11 @@ export default class Cubbot {
|
||||||
//TODO: disconnect
|
//TODO: disconnect
|
||||||
this._client = undefined
|
this._client = undefined
|
||||||
|
|
||||||
this.logger.warn('Stopped')
|
if (exit) {
|
||||||
this.logger.flush()
|
this.logger.warn('Stopped')
|
||||||
|
this.logger.flush()
|
||||||
|
setTimeout(process.exit, 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,4 @@ const config = JSON.parse(stripJsonComments(readFileSync('./env.json').toString(
|
||||||
const app = new Cubbot(config)
|
const app = new Cubbot(config)
|
||||||
app.mount()
|
app.mount()
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => app.umount(true))
|
||||||
app.umount()
|
|
||||||
setTimeout(process.exit, 100)
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import md from 'minecraft-data'
|
|
||||||
import { vsprintf } from 'sprintf-js'
|
import { vsprintf } from 'sprintf-js'
|
||||||
import Module from '../utils/Module'
|
import Module from '../utils/Module'
|
||||||
|
import { IDict } from '../utils/types'
|
||||||
|
import Connection from './Connection'
|
||||||
|
|
||||||
/** Message packet payload */
|
/** 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 */
|
/** Handle chat display and replies */
|
||||||
export default class Chat extends Module<{ reply?: string }> {
|
export default class Chat extends Module<{ reply?: string, bye?: string }> {
|
||||||
|
|
||||||
/** Translation dictionary (en only) */
|
|
||||||
private dict!: { [key: string]: string }
|
|
||||||
|
|
||||||
/** Convert payload to string */
|
/** Convert payload to string */
|
||||||
public parse(msg: IMessage) {
|
public static parse(msg: IMessage, language: IDict) {
|
||||||
let text = ''
|
let text = ''
|
||||||
if (msg.text) {
|
if (msg.text) {
|
||||||
text += msg.text
|
text += msg.text
|
||||||
}
|
}
|
||||||
if (msg.translate && msg.translate in this.dict) {
|
if (msg.translate && msg.translate in language) {
|
||||||
text += vsprintf(this.dict[msg.translate], (msg.with || []).map(this.parse.bind(this)))
|
text += vsprintf(language[msg.translate], (msg.with || [])
|
||||||
|
.map(w => typeof w === 'string' ? w : Chat.parse(w, language)))
|
||||||
}
|
}
|
||||||
if (msg.extra) {
|
if (msg.extra) {
|
||||||
text += msg.extra.map(({text: t}) => t).join()
|
text += msg.extra.map(({text: t}) => t).join()
|
||||||
|
@ -26,11 +25,23 @@ export default class Chat extends Module<{ reply?: string }> {
|
||||||
return text
|
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() {
|
protected mount() {
|
||||||
this.client.on('login', () => {
|
this.connection = this.load<Connection>(Connection)
|
||||||
// FIXME: use this.client.version when 'minecraft-data' include 1.15 language
|
|
||||||
this.dict = md('1.14').language
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('chat', packet => {
|
this.client.on('chat', packet => {
|
||||||
const message = JSON.parse(packet.message)
|
const message = JSON.parse(packet.message)
|
||||||
|
@ -38,7 +49,6 @@ export default class Chat extends Module<{ reply?: string }> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: chat write
|
|
||||||
//TODO: reply
|
//TODO: reply
|
||||||
|
|
||||||
protected getConf() {
|
protected getConf() {
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,25 +2,32 @@ import { ATTACK_DIST2, SERVER_TICK_RATE } from '../utils/constants'
|
||||||
import { dist2 } from '../utils/func'
|
import { dist2 } from '../utils/func'
|
||||||
import Module from '../utils/Module'
|
import Module from '../utils/Module'
|
||||||
import { IDelta } from '../utils/types'
|
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 Life from './Life'
|
||||||
import State from './State'
|
import State from './State'
|
||||||
|
|
||||||
interface IConf {
|
interface IConf {
|
||||||
/** Respawn after death */
|
/** Respawn after death */
|
||||||
respawn: boolean,
|
respawn: boolean
|
||||||
/** Attack dangerous entities */
|
/** Attack dangerous entities */
|
||||||
fight: boolean,
|
fight: boolean
|
||||||
/** Must change weapon */
|
/** Must change weapon */
|
||||||
weapon: 'none' | 'needed' | 'always',
|
weapon: 'none' | 'needed' | 'always'
|
||||||
/** Must react to arrows */
|
/** Must react to arrows */
|
||||||
arrows: 'ignore' | 'dodge' | 'shield'
|
arrows: 'ignore' | 'dodge' | 'shield'
|
||||||
/** Time between attacks */
|
/** Time between attacks */
|
||||||
delay: number | 'item' | 'safe',
|
delay: number | 'item' | 'safe'
|
||||||
/** Attacks multiple entities at ones */
|
/** Attacks multiple entities at ones */
|
||||||
multiaura: boolean,
|
multiaura: boolean
|
||||||
/** Entity to target first */
|
/** Entity to target first */
|
||||||
priority: 'nearest'
|
priority: 'nearest'
|
||||||
|
/** Players names */
|
||||||
|
friends: string[]
|
||||||
|
/** Targeted entities names */
|
||||||
|
mobs: string[]
|
||||||
|
/** Targeted entities ids (override mobs) */
|
||||||
|
mobsIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEnemy extends IAlive {
|
interface IEnemy extends IAlive {
|
||||||
|
@ -33,7 +40,9 @@ const SAFE_DELAY = SERVER_TICK_RATE * 2
|
||||||
export default class Combat extends Module<IConf> {
|
export default class Combat extends Module<IConf> {
|
||||||
|
|
||||||
private state!: State
|
private state!: State
|
||||||
|
private life!: Life
|
||||||
private entities!: Entities
|
private entities!: Entities
|
||||||
|
private connection!: Connection
|
||||||
|
|
||||||
public umount() {
|
public umount() {
|
||||||
this.conf.fight = false
|
this.conf.fight = false
|
||||||
|
@ -42,7 +51,9 @@ export default class Combat extends Module<IConf> {
|
||||||
|
|
||||||
protected mount() {
|
protected mount() {
|
||||||
this.state = this.load<State>(State)
|
this.state = this.load<State>(State)
|
||||||
|
this.life = this.load<Life>(Life)
|
||||||
this.entities = this.load<Entities>(Entities)
|
this.entities = this.load<Entities>(Entities)
|
||||||
|
this.connection = this.load<Connection>(Connection)
|
||||||
|
|
||||||
this.fight()
|
this.fight()
|
||||||
this.arrows()
|
this.arrows()
|
||||||
|
@ -50,19 +61,21 @@ export default class Combat extends Module<IConf> {
|
||||||
this.client.on('combat_event', packet => {
|
this.client.on('combat_event', packet => {
|
||||||
switch (packet.event) {
|
switch (packet.event) {
|
||||||
case 0:
|
case 0:
|
||||||
this.logger.warn('fighting')
|
this.logger.warn({ msg: 'Fighting' })
|
||||||
break
|
break
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if (packet.playerId === this.state.state.entityId) {
|
if (packet.playerId === this.state.state.entityId) {
|
||||||
this.logger.error('%o killed me at %o',
|
this.logger.error({
|
||||||
this.entities.getAlive(packet.entityId), this.state.position)
|
msg: 'Killed', type: 'entity',
|
||||||
|
value: this.entities.getAlive(packet.entityId),
|
||||||
|
})
|
||||||
// MAYBE: chat packet.message
|
// MAYBE: chat packet.message
|
||||||
if (this.conf.respawn) {
|
if (this.conf.respawn) {
|
||||||
this.load<Life>(Life).respawn()
|
this.life.respawn()
|
||||||
}
|
}
|
||||||
} else {
|
} 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',
|
delay: 'safe',
|
||||||
multiaura: false,
|
multiaura: false,
|
||||||
priority: 'nearest',
|
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) {
|
if (this.conf.fight) {
|
||||||
//TODO: pick weapon if change wait
|
//TODO: pick weapon if change wait
|
||||||
//TODO: pick shield
|
//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
|
// Find dangerous entities
|
||||||
const dangers: IEnemy[] = ([...this.entities.players, ...this.entities.livings] as IAlive[])
|
const dangers: IEnemy[] = ([...this.entities.players, ...this.entities.livings] as IAlive[])
|
||||||
.filter(({ entityId }) => entityId !== this.state.state.entityId)
|
.filter(e => {
|
||||||
//TODO: filter friends, creative, passives
|
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
|
.map(e => ({ reach2: dist2(this.state.position, e), ...e })) // TODO: include box
|
||||||
.filter(({ reach2 }) => reach2 <= ATTACK_DIST2) // MAYBE: as option
|
.filter(({ reach2 }) => reach2 <= ATTACK_DIST2) // MAYBE: as option
|
||||||
.sort((a, b) => a.reach2 - b.reach2)
|
.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)
|
// MAYBE: include entity speed (for tiny zombie or phantom)
|
||||||
|
|
||||||
if (dangers.length > 0) {
|
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.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)
|
this.entities.attack(entityId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -107,7 +148,7 @@ export default class Combat extends Module<IConf> {
|
||||||
const attackSpeed = 1.6 //TODO: get from inventory
|
const attackSpeed = 1.6 //TODO: get from inventory
|
||||||
|
|
||||||
const delay = typeof this.conf.delay === 'number' ? this.conf.delay : (
|
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)
|
1 / attackSpeed * 20 * SERVER_TICK_RATE)
|
||||||
|
|
||||||
setTimeout(this.fight.bind(this), delay)
|
setTimeout(this.fight.bind(this), delay)
|
||||||
|
@ -120,7 +161,7 @@ export default class Combat extends Module<IConf> {
|
||||||
.filter(e => e.type === 2 && // arrow
|
.filter(e => e.type === 2 && // arrow
|
||||||
dist2(this.state.position, e) <= 99)
|
dist2(this.state.position, e) <= 99)
|
||||||
if (arrows.length > 0) {
|
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) =>
|
this.state.translate(arrows.reduce((p, arrow) =>
|
||||||
Math.abs(arrow.velocityX) < Math.abs(arrow.velocityZ) ?
|
Math.abs(arrow.velocityX) < Math.abs(arrow.velocityZ) ?
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -68,7 +68,7 @@ export interface IAlive extends IMovableE, IEquipable, IAttributable, IMetadatab
|
||||||
headPitch: number
|
headPitch: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILiving extends IAlive {
|
export interface ILiving extends IAlive {
|
||||||
entityUUID: string
|
entityUUID: string
|
||||||
type: number
|
type: number
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ interface ILiving extends IAlive {
|
||||||
interface IPlayerSpawn extends IPositioned {
|
interface IPlayerSpawn extends IPositioned {
|
||||||
playerUUID: string
|
playerUUID: string
|
||||||
}
|
}
|
||||||
interface IPlayer extends IPlayerSpawn, IAlive {
|
export interface IPlayer extends IPlayerSpawn, IAlive {
|
||||||
me?: boolean
|
me?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,117 +172,86 @@ export default class Entities extends Module<{}> {
|
||||||
// MAYBE: this.client.on('animation', packet => { })
|
// MAYBE: this.client.on('animation', packet => { })
|
||||||
// MAYBE: this.client.on('entity_status', packet => { })
|
// MAYBE: this.client.on('entity_status', packet => { })
|
||||||
|
|
||||||
this.client.on('rel_entity_move', (packet: IMove) => {
|
this.client.on('rel_entity_move', (packet: IMove) =>
|
||||||
const entity = this.getEntity(packet.entityId)
|
this.findOrLogger<IMovableE>('rel_move', packet, this.getEntity.bind(this), entity =>
|
||||||
if (entity) {
|
applyDelta(entity, packet)))
|
||||||
applyDelta(entity, packet)
|
this.client.on('entity_move_look', (packet: IMoveLook) =>
|
||||||
} else {
|
this.findOrLogger<IMovableE>('move_look', packet, this.getEntity.bind(this), entity => {
|
||||||
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) {
|
|
||||||
applyDelta(entity, packet)
|
applyDelta(entity, packet)
|
||||||
entity.pitch = packet.pitch
|
entity.pitch = packet.pitch
|
||||||
entity.yaw = packet.yaw
|
entity.yaw = packet.yaw
|
||||||
} else {
|
}))
|
||||||
this.logger.warn('move_look of unknown entity %s', packet.entityId)
|
this.client.on('entity_look', (packet: ILook) =>
|
||||||
}
|
this.findOrLogger<IMovableE>('look', packet, this.getEntity.bind(this), entity => {
|
||||||
})
|
|
||||||
this.client.on('entity_look', (packet: ILook) => {
|
|
||||||
const entity = this.getEntity(packet.entityId)
|
|
||||||
if (entity) {
|
|
||||||
entity.pitch = packet.pitch
|
entity.pitch = packet.pitch
|
||||||
entity.yaw = packet.yaw
|
entity.yaw = packet.yaw
|
||||||
} else {
|
}))
|
||||||
this.logger.warn('look of unknown entity %s', packet.entityId)
|
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_head_rotation', (packet: IHead) => {
|
this.client.on('entity_velocity', (packet: IVelocityE) =>
|
||||||
const entity = this.getAlive(packet.entityId)
|
this.findOrLogger<IMovableE>('velocity', packet, this.getEntity.bind(this), entity => {
|
||||||
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) {
|
|
||||||
entity.velocityX = packet.velocityX
|
entity.velocityX = packet.velocityX
|
||||||
entity.velocityY = packet.velocityY
|
entity.velocityY = packet.velocityY
|
||||||
entity.velocityZ = packet.velocityZ
|
entity.velocityZ = packet.velocityZ
|
||||||
} else {
|
}))
|
||||||
this.logger.warn('velocity of unknown entity %s', packet.entityId)
|
this.client.on('entity_teleport', (packet: IPositioned) =>
|
||||||
}
|
this.findOrLogger<IMovableE>('teleport', packet, this.getEntity.bind(this), entity => {
|
||||||
})
|
|
||||||
this.client.on('entity_teleport', (packet: IPositioned) => {
|
|
||||||
const entity = this.getEntity(packet.entityId)
|
|
||||||
if (entity) {
|
|
||||||
entity.x = packet.x
|
entity.x = packet.x
|
||||||
entity.y = packet.y
|
entity.y = packet.y
|
||||||
entity.z = packet.z
|
entity.z = packet.z
|
||||||
entity.pitch = packet.pitch
|
entity.pitch = packet.pitch
|
||||||
entity.yaw = packet.yaw
|
entity.yaw = packet.yaw
|
||||||
} else {
|
}))
|
||||||
this.logger.warn('teleport of unknown entity %s', packet.entityId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('entity_destroy', (packet: { entityIds: number[] }) => {
|
this.client.on('entity_destroy', (packet: { entityIds: number[] }) => {
|
||||||
packet.entityIds.forEach(id => {
|
packet.entityIds.forEach(id => {
|
||||||
if (!this.deleteEntity(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
|
// TODO: identify metadatas
|
||||||
this.client.on('entity_metadata', (packet: IMetadatas) => {
|
this.client.on('entity_metadata', (packet: IMetadatas) =>
|
||||||
const entity = this.getMetadatable(packet.entityId)
|
this.findOrLogger<IMetadatable>('metadata', packet, this.getMetadatable.bind(this), entity => {
|
||||||
if (entity) {
|
|
||||||
if (!entity.metadata) {
|
if (!entity.metadata) {
|
||||||
entity.metadata = new Map()
|
entity.metadata = new Map()
|
||||||
}
|
}
|
||||||
for (const data of packet.metadata) {
|
for (const data of packet.metadata) {
|
||||||
entity.metadata.set(data.key, data)
|
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) => { })
|
// MAYBE: this.client.on('entity', (packet: IId) => { })
|
||||||
|
|
||||||
// TODO: entity_effect, remove_entity_effect
|
// TODO: entity_effect, remove_entity_effect
|
||||||
this.client.on('entity_equipment', (packet: IEquipment) => {
|
this.client.on('entity_equipment', (packet: IEquipment) =>
|
||||||
const entity = this.getAlive(packet.entityId)
|
this.findOrLogger<IAlive>('equipment', packet, this.getAlive.bind(this), entity =>
|
||||||
if (entity) {
|
|
||||||
entity.equipment = entity.equipment ?
|
entity.equipment = entity.equipment ?
|
||||||
entity.equipment.set(packet.slot, packet.item) :
|
entity.equipment.set(packet.slot, packet.item) :
|
||||||
new Map([[packet.slot, packet.item]])
|
new Map([[packet.slot, packet.item]])))
|
||||||
} else {
|
|
||||||
this.logger.warn('equipment of unknown entity %s', packet.entityId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: identify (with properties)
|
// TODO: identify (with properties)
|
||||||
this.client.on('entity_update_attributes', (packet: IAttributes) => {
|
this.client.on('entity_update_attributes', (packet: IAttributes) =>
|
||||||
const entity = this.getAlive(packet.entityId)
|
this.findOrLogger<IAlive>('attributes', packet, this.getAlive.bind(this), entity =>
|
||||||
if (entity) {
|
entity.attributes = packet.properties))
|
||||||
entity.attributes = packet.properties
|
|
||||||
} else {
|
|
||||||
this.logger.warn('attributes of unknown entity %s', packet.entityId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getConf() {
|
protected getConf() {
|
||||||
return {}
|
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
|
// TODO: player info
|
||||||
|
|
|
@ -83,19 +83,19 @@ export default class Life extends Module<IConf> {
|
||||||
this._saturation = data.foodSaturation
|
this._saturation = data.foodSaturation
|
||||||
|
|
||||||
if (this.health <= this.conf.lowHealth) {
|
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) {
|
} 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) {
|
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) {
|
} 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) {
|
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)
|
// TODO: inventory.items.filter(food).orderBy(this.conf.preferSaturation)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Module from '../utils/Module'
|
import Module from '../utils/Module'
|
||||||
import { IDelta, IPosition, IState } from '../utils/types'
|
import { IDelta, IPosition, IState, gameMode } from '../utils/types'
|
||||||
|
|
||||||
// MAYBE: split
|
// MAYBE: split
|
||||||
|
|
||||||
|
@ -19,7 +19,13 @@ interface IPositionPacket extends IPosition {
|
||||||
teleportId: number
|
teleportId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: player_info
|
interface IPlayerInfo {
|
||||||
|
UUID: string
|
||||||
|
name: string
|
||||||
|
properties: any[]
|
||||||
|
gamemode: gameMode
|
||||||
|
ping: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Handle client state and position */
|
/** Handle client state and position */
|
||||||
export default class State extends Module<{ positionConfirm: boolean }> {
|
export default class State extends Module<{ positionConfirm: boolean }> {
|
||||||
|
@ -34,7 +40,7 @@ export default class State extends Module<{ positionConfirm: boolean }> {
|
||||||
return this._difficulty
|
return this._difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
private _abilities!: IDifficulty
|
private _abilities!: IAbilities
|
||||||
public get abilities() {
|
public get abilities() {
|
||||||
return this._abilities
|
return this._abilities
|
||||||
}
|
}
|
||||||
|
@ -44,6 +50,11 @@ export default class State extends Module<{ positionConfirm: boolean }> {
|
||||||
return this._position
|
return this._position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _players = new Map<string, IPlayerInfo>()
|
||||||
|
public get players() {
|
||||||
|
return this._players
|
||||||
|
}
|
||||||
|
|
||||||
public translate(delta: IDelta, onGround: boolean = true) {
|
public translate(delta: IDelta, onGround: boolean = true) {
|
||||||
this.client.write('position', {
|
this.client.write('position', {
|
||||||
onGround,
|
onGround,
|
||||||
|
@ -63,7 +74,7 @@ export default class State extends Module<{ positionConfirm: boolean }> {
|
||||||
this.client.on('position', (data: IPositionPacket) => {
|
this.client.on('position', (data: IPositionPacket) => {
|
||||||
if (this._position == null) {
|
if (this._position == null) {
|
||||||
if (data.flags) {
|
if (data.flags) {
|
||||||
this.logger.error('first position is relative')
|
this.logger.error('First position is relative')
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
this._position = data
|
this._position = data
|
||||||
|
@ -82,7 +93,29 @@ export default class State extends Module<{ positionConfirm: boolean }> {
|
||||||
if (this.conf.positionConfirm) {
|
if (this.conf.positionConfirm) {
|
||||||
this.client.write('teleport_confirm', { teleportId: data.teleportId })
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,17 +74,17 @@ export default class Time extends Module<IConf> {
|
||||||
if (this._distAge) {
|
if (this._distAge) {
|
||||||
this._tps = age - this._distAge
|
this._tps = age - this._distAge
|
||||||
if (this.tps <= this.conf.minTps) {
|
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
|
this._spt = (now - this._at) / this.tps
|
||||||
if (this.spt >= this.conf.maxSpt) {
|
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
|
const offset = age - this._age
|
||||||
if (Math.abs(offset) > this.conf.maxOffset) {
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IModuleType } from '../utils/Module'
|
import { IModuleType } from '../utils/Module'
|
||||||
import Chat from './Chat'
|
import Chat from './Chat'
|
||||||
import Client from './Client'
|
import Client from './Connection'
|
||||||
import Combat from './Combat'
|
import Combat from './Combat'
|
||||||
import Entities from './Entities'
|
import Entities from './Entities'
|
||||||
import Life from './Life'
|
import Life from './Life'
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,9 +32,10 @@ export interface ISlot {
|
||||||
//TODO:
|
//TODO:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type gameMode = 0|1|2|3
|
||||||
export interface IState {
|
export interface IState {
|
||||||
entityId: number,
|
entityId: number,
|
||||||
gameMode: 0|1|2|3,
|
gamemode: gameMode,
|
||||||
dimension: number,
|
dimension: number,
|
||||||
hashedSeed: number[],
|
hashedSeed: number[],
|
||||||
maxPlayers: number,
|
maxPlayers: number,
|
||||||
|
@ -43,3 +44,5 @@ export interface IState {
|
||||||
reducedDebugInfo: boolean,
|
reducedDebugInfo: boolean,
|
||||||
enableRespawnScreen: boolean
|
enableRespawnScreen: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IDict { [key: string]: string }
|
||||||
|
|
Loading…
Reference in New Issue