Inventory
continuous-integration/drone/push Build is passing Details

master
May B. 2020-04-23 00:08:52 +02:00
parent 2dc150c38b
commit 095c5c8ce2
13 changed files with 478 additions and 100 deletions

View File

@ -35,7 +35,7 @@ yarn run test
``` ```
### Note ### 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 `minecraft-data` package isn't updated for 1.15+. As a workaround, data will be downloader from `https://pokechu22.github.io/Burger/{version}.json` and cached in `data` folder
## Tests ## Tests
``` ```

View File

@ -15,6 +15,7 @@
}, },
"Combat": {}, "Combat": {},
"Chat": {}, "Chat": {},
"Meal": {},
"Time": { "Time": {
"log": "debug" "log": "debug"
} }

View File

@ -4,7 +4,8 @@ import Module from '../utils/Module'
import { IDelta } from '../utils/types' import { IDelta } from '../utils/types'
import Connection from './Connection' import Connection from './Connection'
import Entities, { IAlive, ILiving, IPlayer } from './Entities' import Entities, { IAlive, ILiving, IPlayer } from './Entities'
import Life from './Life' import Inventory from './Inventory'
import Spawn from './Spawn'
import State from './State' import State from './State'
interface IConf { interface IConf {
@ -28,6 +29,10 @@ interface IConf {
mobs: string[] mobs: string[]
/** Targeted entities ids (override mobs) */ /** Targeted entities ids (override mobs) */
mobsIds?: number[] mobsIds?: number[]
/** Weapons names by priority */
weapons: string[]
/** Weapons ids by priority (override weapons) */
weaponsIds?: number[]
} }
interface IEnemy extends IAlive { interface IEnemy extends IAlive {
@ -40,9 +45,8 @@ 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 spawn!: Spawn
private entities!: Entities private entities!: Entities
private connection!: Connection
public umount() { public umount() {
this.conf.fight = false this.conf.fight = false
@ -51,13 +55,27 @@ 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.spawn = this.load<Spawn>(Spawn)
this.entities = this.load<Entities>(Entities) this.entities = this.load<Entities>(Entities)
this.connection = this.load<Connection>(Connection)
this.fight()
this.arrows() this.arrows()
if (this.conf.mobsIds && (this.conf.weaponsIds || this.conf.weapon === 'none')) {
this.fight()
} else {
this.load<Connection>(Connection).data.onReady(data => {
if (!this.conf.mobsIds) {
this.conf.mobsIds = this.conf.mobs
.map(name => data.entities![name].id)
}
if (!this.conf.weaponsIds) {
this.conf.weaponsIds = this.conf.weapons
.map(name => data.items![name].numeric_id)
}
this.fight()
})
}
this.client.on('combat_event', packet => { this.client.on('combat_event', packet => {
switch (packet.event) { switch (packet.event) {
case 0: case 0:
@ -72,7 +90,7 @@ export default class Combat extends Module<IConf> {
}) })
// MAYBE: chat packet.message // MAYBE: chat packet.message
if (this.conf.respawn) { if (this.conf.respawn) {
this.life.respawn() this.spawn.respawn()
} }
} else { } else {
this.logger.info({ msg: 'Fight end', packet }) this.logger.info({ msg: 'Fight end', packet })
@ -85,7 +103,7 @@ export default class Combat extends Module<IConf> {
return { return {
respawn: true, respawn: true,
fight: true, fight: true,
weapon: 'always', weapon: 'needed',
arrows: 'dodge', arrows: 'dodge',
delay: 'safe', delay: 'safe',
multiaura: false, multiaura: false,
@ -98,22 +116,18 @@ export default class Combat extends Module<IConf> {
/*'snow_golem',*/ 'spider', 'stray', 'vex', 'vindicator', 'witch', 'wither', 'wither_skeleton', /*'snow_golem',*/ 'spider', 'stray', 'vex', 'vindicator', 'witch', 'wither', 'wither_skeleton',
/*'wolf',*/ 'zombie', /*'zombie_pigman',*/ 'zombie_villager', /*'wolf',*/ 'zombie', /*'zombie_pigman',*/ 'zombie_villager',
], ],
weapons: [
'diamond_sword', 'diamond_axe', 'iron_sword', 'iron_axe', 'stone_sword', 'stone_axe',
],
} }
} }
private fight() { private fight() {
if (this.conf.fight) { if (this.conf.fight) {
//TODO: pick weapon if change wait if (this.conf.weapon == 'always') {
//TODO: pick shield this.equipWeapon()
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)
}
} }
//TODO: pick shield
// 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[])
@ -139,6 +153,10 @@ export default class Combat extends Module<IConf> {
if (dangers.length > 0) { if (dangers.length > 0) {
this.logger.info({ msg: 'Fighting', type: 'count', value: dangers.length }); this.logger.info({ msg: 'Fighting', type: 'count', value: dangers.length });
if (this.conf.weapon === 'needed') {
this.equipWeapon()
}
(this.conf.multiaura ? dangers : dangers.slice(0, 1)).forEach(({ entityId }) => { (this.conf.multiaura ? dangers : dangers.slice(0, 1)).forEach(({ entityId }) => {
this.logger.debug({ msg: 'Attack', type: 'entityId', value: entityId }) this.logger.debug({ msg: 'Attack', type: 'entityId', value: entityId })
this.entities.attack(entityId) this.entities.attack(entityId)
@ -155,6 +173,14 @@ export default class Combat extends Module<IConf> {
} }
} }
private equipWeapon() {
const inv = this.load<Inventory>(Inventory)
const weapon = inv.findFirst(this.conf.weaponsIds!)
if (weapon.slot >= 0) {
inv.selectItem(weapon.slot, false)
}
}
private arrows() { private arrows() {
if (this.conf.arrows !== 'ignore') { if (this.conf.arrows !== 'ignore') {
const arrows = [...this.entities.objects] const arrows = [...this.entities.objects]
@ -176,3 +202,6 @@ export default class Combat extends Module<IConf> {
//TODO: respawn or disconnect //TODO: respawn or disconnect
} }
// TODO: look and swing
// TODO: critical

View File

@ -3,6 +3,7 @@ import Cubbot from '../Cubbot'
import Data from '../utils/Data' import Data from '../utils/Data'
import Module from '../utils/Module' import Module from '../utils/Module'
import Chat, { IMessage } from './Chat' import Chat, { IMessage } from './Chat'
import Spawn from './Spawn'
type DisconnectReason = 'authservers_down' | 'banned' | 'banned.reason' | 'banned.expiration' | 'banned_ip.reason' | type DisconnectReason = 'authservers_down' | 'banned' | 'banned.reason' | 'banned.expiration' | 'banned_ip.reason' |
'banned_ip.expiration' | 'duplicate_login' | 'flying' | 'generic' | 'idling' | 'illegal_characters' | 'banned_ip.expiration' | 'duplicate_login' | 'flying' | 'generic' | 'idling' | 'illegal_characters' |
@ -37,7 +38,7 @@ export default class Connection extends Module<IConfig> {
return this._oldData return this._oldData
} }
private _data?: Data private _data = new Data()
public get data() { public get data() {
return this._data return this._data
} }
@ -48,9 +49,13 @@ export default class Connection extends Module<IConfig> {
} }
protected mount() { protected mount() {
const spawn = this.load<Spawn>(Spawn)
spawn.addCheck('data')
this.client.on('connect', () => { this.client.on('connect', () => {
this._connected = true this._connected = true
this.logger.trace('Connected') this.logger.trace('Connected')
this._data.onReady(() => spawn.validateCheck('data'))
}) })
this.client.on('disconnect', ({ reason }) => { this.client.on('disconnect', ({ reason }) => {
this._connected = false this._connected = false
@ -66,7 +71,7 @@ export default class Connection extends Module<IConfig> {
}) })
this.client.on('login', () => { this.client.on('login', () => {
this.logger.trace('Logged') this.logger.trace('Logged')
this._data = new Data(this.conf.dataSource, this.conf.dataDir, (this.client as any).version, this._data.load(this.conf.dataSource, this.conf.dataDir, (this.client as any).version,
this.logger.child({ name: 'Data', level: 'info' })) this.logger.child({ name: 'Data', level: 'info' }))
// FIXME: use 'minecraft-data' when it include 1.15+ // FIXME: use 'minecraft-data' when it include 1.15+
}) })

View File

@ -254,8 +254,6 @@ export default class Entities extends Module<{}> {
} }
// TODO: player info
// MAYBE: animation // MAYBE: animation
// MAYBE: attach_entity // MAYBE: attach_entity
// MAYBE: entity_sound_effect // MAYBE: entity_sound_effect

View File

@ -1,9 +1,200 @@
//TODO: window_items import { HOTBAR_END_SLOT, HOTBAR_START_SLOT, OFF_HAND_SLOT } from '../utils/constants'
//TODO: set_slot import Module from '../utils/Module'
import { ISlot } from '../utils/types'
//TODO: declare_recipes type Ingredient = ISlot[]
type IRecipe = IRecipeCraftingShapeless | IRecipeCraftingShaped | IRecipeSpecial | IRecipeSmelting | IRecipeStoneCutting
interface IRecipeCraftingShapeless {
type: 'minecraft:crafting_shapeless'
recipeId: string
data: {
group: string,
ingredients: Ingredient[],
result: ISlot,
}
}
interface IRecipeCraftingShaped {
type: 'minecraft:crafting_shaped'
recipeId: string
data: {
group: string,
width: number,
height: number,
ingredients: Ingredient[][],
result: ISlot,
}
}
interface IRecipeSpecial {
type: string
recipeId: string
}
interface IRecipeSmelting {
type: 'minecraft:smelting' | 'minecraft:blasting' | 'minecraft:smoking' | 'minecraft:campfire_cooking'
recipeId: string
data: {
group: string,
ingredient: Ingredient,
result: ISlot,
experience: number,
cookTime: number,
}
}
interface IRecipeStoneCutting {
type: 'minecraft:stonecutting'
recipeId: string
data: {
group: string,
ingredient: Ingredient,
result: ISlot,
}
}
interface IWindow {
items?: ISlot[]
closed?: true
properties?: number[]
}
const NULL_SLOT: ISlot = { present: false, itemId: -1, itemCount: 0, nbtData: [] }
export default class Inventory extends Module<{}> {
private _recipes!: IRecipe[]
public get recipes() {
return this._recipes
}
private _windows = new Map<number, IWindow>()
public get inventory() {
return this._windows.get(0)?.items || []
}
public get armor() {
return {
head: this.inventory[5],
chest: this.inventory[6],
legs: this.inventory[7],
feet: this.inventory[8],
}
}
private get hotbar() {
return this.inventory.slice(HOTBAR_START_SLOT, HOTBAR_END_SLOT)
}
private get offItem() {
return this.inventory[OFF_HAND_SLOT]
}
private _held = 0
public get mainHand() {
return this._held
}
public get mainItem() {
return this.hotbar[this.mainHand]
}
private _actionCounter = 0
public click(windowId: number, slot: number, mouseButton: number, mode: number, item: ISlot) {
this.client.write('window_click', { windowId, slot, mouseButton, action: this._actionCounter++, mode, item })
}
public toHotbar(to: number, from: number, windowId = 0) {
const inv = this._windows.get(windowId)?.items
const item = inv ? inv[from] : NULL_SLOT
this.click(windowId, from, to, 2, item)
}
public drop(from: number, windowId = 0, stack = false) {
this.click(windowId, from, stack ? 1 : 0, 4, NULL_SLOT)
}
public setHeld(slotId: number) {
this.client.write('held_item_slot', { slotId })
this._held = slotId
}
public swapOffItem() {
this.client.write('block_dig', { status: 6, location: { x: 0, y: 0, z: 0 }, face: 0 })
}
public selectItem(slot: number, allowOff: boolean) {
let hand = -1
if (slot === OFF_HAND_SLOT && allowOff) {
hand = 1
} else {
let hotSlot = slot
if (hotSlot < HOTBAR_START_SLOT || hotSlot > HOTBAR_END_SLOT) {
hotSlot = this.hotbar.findIndex(s => !s.present)
if (hotSlot < 0) {
hotSlot = this.mainHand + 1 % (HOTBAR_END_SLOT - HOTBAR_START_SLOT)
}
this.toHotbar(hotSlot, slot)
} else {
hotSlot -= HOTBAR_START_SLOT
}
if (hotSlot !== this.mainHand || Math.random() > 0.9) { // Force held randomlys
this.setHeld(hotSlot)
}
hand = 0
}
return hand
}
public useItem(slot: number, allowOff = true) {
this.client.write('use_item', { hand: this.selectItem(slot, allowOff) })
}
public findFirst(order: number[]) {
return this.inventory.reduce((prev, slot, pos) => {
const idx = order.indexOf(slot.itemId)
return idx < 0 || (prev.value > -1 && prev.value <= idx) ? prev : { slot: pos, value: idx }
}, { slot: -1, value: -1 })
}
protected mount(): void {
this.client.on('declare_recipes', packet => this._recipes = packet.recipes)
this.client.on('window_items', packet => this.updateWindow(packet.windowId, { items: packet.items}))
this.client.on('close_window', packet => this.updateWindow(packet.windowId, { closed: true }))
this.client.on('craft_progress_bar', packet => {
const window = this._windows.get(packet.windowId) || {}
if (!window.properties) {
window.properties = []
}
window.properties[packet.property] = packet.value
this._windows.set(packet.windowId, window)
})
this.client.on('transaction', packet => {
// TODO: action list
if (!packet.accepted) {
this.logger.warn('Conflict')
this.client.write('transaction', packet)
}
})
this.client.on('set_slot', packet => {
const window = this._windows.get(packet.windowId) || {}
if (!window.items) {
window.items = []
}
window.items[packet.slot] = packet.item
this._windows.set(packet.windowId, window)
})
this.client.on('held_item_slot', packet => this._held = packet.slot)
}
protected getConf() {
return {}
}
private updateWindow(windowId: number, data: IWindow) {
const window = this._windows.get(windowId) || {}
this._windows.set(windowId, {...window, ...data})
}
// TODO: autodrop
}
// MAYBE: horse_window
/* Action */ /* Action */
//TODO: Pick Item // TODO: Pick Item
//TDOO: craft // TODO: craft
//TODO: Held Item Change // TODO: Log actions
// TODO: Move inventory (equip armor)

View File

@ -1,36 +1,34 @@
import { LOW_HEALTH, MAX_HEALTH, REGENERATE_FOOD, SPRINT_FOOD } from '../utils/constants' import { EventEmitter } from 'events'
import { LOW_HEALTH, MAX_FOOD, MAX_HEALTH, MAX_SATURATION, SPRINT_FOOD } from '../utils/constants'
import { round } from '../utils/func' import { round } from '../utils/func'
import Module from '../utils/Module' import Module from '../utils/Module'
import Spawn from './Spawn'
interface IConf { interface IConf {
/** Respawn on world join */
spawn: boolean,
/** Warn if health is lower */ /** Warn if health is lower */
lowHealth: number, lowHealth: number,
/** Life to regenerate */
targetHealth: number,
/** Log health info */ /** Log health info */
showHealth: boolean, showHealth: boolean,
/** Use food if needed */ /** Warn if food level is lower */
eat: boolean,
/** Prefer saturation over food power */
preferSaturation: boolean,
/** Warn and eat if food level is lower */
lowFood: number, lowFood: number,
/** Food level needed to regenerate */
regenerateFood: number,
/** Log food level info */ /** Log food level info */
showFood: boolean, showFood: boolean,
} }
/** Monitor health, food and xp */ /** Monitor health, food and xp */
export default class Life extends Module<IConf> { export default class Life extends Module<IConf> {
private _health = MAX_HEALTH
public get health() { public get health() {
return this._health return this._health
} }
private _food = MAX_FOOD
public get food() { public get food() {
return this._food return this._food
} }
private _saturation = MAX_SATURATION
public get saturation() { public get saturation() {
return this._saturation return this._saturation
} }
@ -39,49 +37,37 @@ export default class Life extends Module<IConf> {
return this.health > 0 return this.health > 0
} }
public get mustEat() { private _experienceBar = 0
return this.food <= this.conf.lowFood ||
(this.health <= this.conf.targetHealth && this.food <= this.conf.regenerateFood)
}
public get experienceBar() { public get experienceBar() {
return this._experienceBar return this._experienceBar
} }
private _level = 0
public get level() { public get level() {
return this._level return this._level
} }
private _totalExperience = 0
public get totalExperience() { public get totalExperience() {
return this._totalExperience return this._totalExperience
} }
private _health!: number private _events = new EventEmitter()
public get events() {
private _food!: number return this._events
private _saturation!: number
private _experienceBar!: number
private _level!: number
private _totalExperience!: number
public respawn() {
this.logger.info('respawn')
this.client.write('client_command', { action: 1 })
} }
protected mount() { protected mount() {
//TODO: const inventory = this.conf.eat ? this.load('inventory') : undefined const spawn = this.load<Spawn>(Spawn)
spawn.addCheck('health')
this.client.on('update_health', data => { this.client.on('update_health', data => {
if (this.conf.spawn && this._health == null) {
setTimeout(this.respawn.bind(this), 500)
}
this._health = round(data.health) this._health = round(data.health)
this._food = data.food this._food = data.food
this._saturation = data.foodSaturation this._saturation = data.foodSaturation
spawn.validateCheck('health')
if (this.health <= this.conf.lowHealth) { if (this.health <= this.conf.lowHealth) {
this.logger.warn({ msg: this.alive ? 'Low health' : 'Dead', type: 'health', value: 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) {
@ -94,10 +80,7 @@ export default class Life extends Module<IConf> {
this.logger.info({ msg: 'Replete', type: 'food', value: this.food }) this.logger.info({ msg: 'Replete', type: 'food', value: this.food })
} }
if (this.conf.eat && this.mustEat) { this._events.emit('update')
this.logger.warn({ msg: 'TODO: Must eat' })
// TODO: inventory.items.filter(food).orderBy(this.conf.preferSaturation)
}
}) })
this.client.on('experience', data => { this.client.on('experience', data => {
@ -107,18 +90,13 @@ export default class Life extends Module<IConf> {
}) })
} }
//TODO: send client settings // MAYBE: send client settings
protected getConf(): IConf { protected getConf(): IConf {
return { return {
spawn: true,
lowHealth: LOW_HEALTH, lowHealth: LOW_HEALTH,
targetHealth: MAX_HEALTH,
showHealth: false, showHealth: false,
eat: true,
preferSaturation: true,
lowFood: SPRINT_FOOD, lowFood: SPRINT_FOOD,
regenerateFood: REGENERATE_FOOD,
showFood: false, showFood: false,
} }
} }

96
src/modules/Meal.ts Normal file
View File

@ -0,0 +1,96 @@
import { MAX_HEALTH, REGENERATE_FOOD, SPRINT_FOOD } from '../utils/constants'
import Module from '../utils/Module'
import Connection from './Connection'
import Inventory from './Inventory'
import Life from './Life'
import State from './State'
interface IConf {
/** Life to regenerate */
targetHealth: number,
/** Minimal food level */
minFood: number,
/** Food level needed to regenerate */
regenerateFood: number,
/** Food names by priority */
foods: string[]
/** Food ids by priority (override foods) */
foodsIds?: number[]
}
export default class Meal extends Module<IConf> {
private life!: Life
private inv!: Inventory
private state!: State
private _eating?: NodeJS.Timeout
public get mustEat() {
return this.life.food <= this.conf.minFood ||
(this.life.health <= this.conf.targetHealth && this.life.food <= this.conf.regenerateFood)
}
protected mount() {
this.life = this.load<Life>(Life)
this.inv = this.load<Inventory>(Inventory)
this.state = this.load<State>(State)
const ready = () => {
this.life.events.on('update', this.eat.bind(this))
this.eat()
}
if (this.conf.foodsIds) {
ready()
} else {
this.load<Connection>(Connection).data.onReady(data => {
this.conf.foodsIds = this.conf.foods
.map(name => data.items![name].numeric_id)
ready()
})
}
}
protected getConf() {
return {
minFood: SPRINT_FOOD,
targetHealth: MAX_HEALTH,
regenerateFood: REGENERATE_FOOD,
foods: [
'golden_carrot', 'cooked_porkchop', 'cooked_beef', 'rabbit_stew', 'cooked_mutton', 'cooked_salmon',
// 'golden_apple', 'enchanted_golden_apple',
'beetroot_soup', 'cooked_chicken', 'mushroom_stem', 'baked_potato', 'bread', 'cooked_cod',
'cooked_rabbit', 'pumpkin_pie', 'carrot', 'apple', 'beef', 'porkchop', 'rabbit', 'honey_bottle',
'melon_slice', 'mutton', 'beetroot', 'dried_kelp', 'potato', 'cookie', 'cod', 'salmon', 'tropical_fish',
'chicken', 'rotten_flesh',
],
}
}
private eat() {
if (this.mustEat && !this._eating) {
const food = this.inv.findFirst(this.conf.foodsIds!)
if (food.slot >= 0) {
this.client.on('entity_status', packet => {
if (this._eating && packet.entityId === this.state.state.entityId && packet.entityStatus === 9) {
clearTimeout(this._eating)
this._eating = undefined
}
})
this.logger.info('Eating %s', this.conf.foods[food.value])
this.inv.useItem(food.slot)
this._eating = setTimeout(() => {
this.logger.error('Fail eating')
this._eating = undefined
this.eat()
}, 3000)
} else {
this.logger.warn('No food available')
}
}
}
}

34
src/modules/Spawn.ts Normal file
View File

@ -0,0 +1,34 @@
import Module from '../utils/Module'
export default class Spawn extends Module<{ spawn: boolean }> {
private _checkList: string[] = []
public addCheck(name: string) {
this._checkList.push(name)
}
public validateCheck(name: string) {
const idx = this._checkList.indexOf(name)
if (idx > -1) {
this._checkList.splice(this._checkList.indexOf(name), 1)
this.logger.trace({ msg: 'Spawn ready', value: name, remain: this._checkList.length })
if (this._checkList.length === 0 && this.conf.spawn) {
this.respawn()
}
}
}
public respawn() {
this.logger.info('Respawn')
this.client.write('client_command', { action: 1 })
}
protected mount() {
// passive
}
protected getConf() {
return { spawn: true }
}
}

View File

@ -1,5 +1,6 @@
import Module from '../utils/Module' import Module from '../utils/Module'
import { IDelta, IPosition, IState, gameMode } from '../utils/types' import { IDelta, IPosition, IState, gameMode } from '../utils/types'
import Spawn from './Spawn'
// MAYBE: split // MAYBE: split
@ -65,11 +66,22 @@ export default class State extends Module<{ positionConfirm: boolean }> {
} }
protected mount() { protected mount() {
this.client.on('login', data => this._state = data) const spawn = this.load<Spawn>(Spawn)
spawn.addCheck('state')
spawn.addCheck('abilities')
spawn.addCheck('position')
this.client.on('login', data => {
this._state = data
spawn.validateCheck('state')
})
this.client.on('difficulty', data => this._difficulty = { this.client.on('difficulty', data => this._difficulty = {
level: data.difficulty, locked: data.difficultyLocked, level: data.difficulty, locked: data.difficultyLocked,
}) })
this.client.on('abilities', data => this._abilities = data) this.client.on('abilities', data => {
this._abilities = data
spawn.validateCheck('abilities')
})
this.client.on('position', (data: IPositionPacket) => { this.client.on('position', (data: IPositionPacket) => {
if (this._position == null) { if (this._position == null) {
@ -94,6 +106,7 @@ export default class State extends Module<{ positionConfirm: boolean }> {
this.client.write('teleport_confirm', { teleportId: data.teleportId }) this.client.write('teleport_confirm', { teleportId: data.teleportId })
} }
this.logger.debug({ msg: 'Teleported', type: 'position', value: this.position }) this.logger.debug({ msg: 'Teleported', type: 'position', value: this.position })
spawn.validateCheck('position')
}) })
this.client.on('player_info', (packet: { action: number, data: IPlayerInfo[] }) => { this.client.on('player_info', (packet: { action: number, data: IPlayerInfo[] }) => {
@ -103,13 +116,22 @@ export default class State extends Module<{ positionConfirm: boolean }> {
this._players.set(player.UUID, player) this._players.set(player.UUID, player)
break break
case 1: case 1:
this._players.get(player.UUID)!.gamemode = player.gamemode const p1 = this._players.get(player.UUID)
if (p1) {
p1.gamemode = player.gamemode
}
break break
case 2: case 2:
this._players.get(player.UUID)!.ping = player.ping const p2 = this._players.get(player.UUID)
if (p2) {
p2.ping = player.ping
}
break break
case 3: case 3:
this._players.get(player.UUID)!.name = player.name const p3 = this._players.get(player.UUID)
if (p3) {
p3.name = player.name
}
break break
case 4: case 4:
this._players.delete(player.UUID) this._players.delete(player.UUID)

View File

@ -1,9 +1,12 @@
import { IModuleType } from '../utils/Module' import { IModuleType } from '../utils/Module'
import Chat from './Chat' import Chat from './Chat'
import Client from './Connection'
import Combat from './Combat' import Combat from './Combat'
import Client from './Connection'
import Entities from './Entities' import Entities from './Entities'
import Inventory from './Inventory'
import Life from './Life' import Life from './Life'
import Meal from './Meal'
import Spawn from './Spawn'
import State from './State' import State from './State'
import Time from './Time' import Time from './Time'
@ -14,6 +17,9 @@ export default [
Combat, Combat,
Entities, Entities,
Life, Life,
Meal,
State, State,
Time, Time,
Inventory,
Spawn,
] as IModuleType[] ] as IModuleType[]

View File

@ -11,20 +11,42 @@ export interface IEntityData {
metadata: Array<{ entity?: string }> metadata: Array<{ entity?: string }>
} }
export interface IItemData {
display_name: string
max_stack_size: number
numeric_id: number
}
/** Load game data */ /** Load game data */
export default class Data { export default class Data {
private _ready = false private _onReady: Array<(data: Data) => void> = []
public get ready() {
return this._ready
}
private _entities?: { [name: string]: IEntityData } private _entities?: { [name: string]: IEntityData }
public get entities() { public get entities() {
return this._entities return this._entities
} }
constructor(source: string, directory: string, version: string, logger: Logger) { private _items?: { [name: string]: IItemData }
public get items() {
return this._items
}
public onReady(cb: ((data: Data) => void)) {
this._onReady.push(cb)
}
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]
}
}
}
}
public load(source: string, directory: string, version: string, logger: Logger) {
logger.debug('Loading data file') logger.debug('Loading data file')
const path = directory + version + '.json' const path = directory + version + '.json'
if (!fs.existsSync(directory)) { if (!fs.existsSync(directory)) {
@ -39,29 +61,20 @@ export default class Data {
res.on('error', error => console.error({ msg: 'Downloading error', error })) res.on('error', error => console.error({ msg: 'Downloading error', error }))
res.on('end', () => { res.on('end', () => {
out.close() out.close()
setTimeout(() => this.load(path), 500) setTimeout(() => this.read(path), 500)
}) })
}).on('error', error => console.error({ msg: 'Downloading error', error })) }).on('error', error => console.error({ msg: 'Downloading error', error }))
} else { } else {
this.load(path) this.read(path)
} }
} }
public getEntityById(entityId: number) { private read(path: string) {
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 { try {
const data = JSON.parse(readFileSync(path).toString())[0] // NOTE: memory ??? const data = JSON.parse(readFileSync(path).toString())[0] // NOTE: memory ???
this._entities = data.entities.entity this._entities = data.entities.entity
this._ready = true this._items = data.items.item
this._onReady.forEach(cb => cb(this))
} catch (error) { } catch (error) {
console.error('/!\\ =( Please remove ' + path, error) console.error('/!\\ =( Please remove ' + path, error)
process.exit(1) process.exit(1)

View File

@ -1,6 +1,7 @@
export const SPRINT_FOOD = 6 export const SPRINT_FOOD = 6
export const REGENERATE_FOOD = 18 export const REGENERATE_FOOD = 18
export const MAX_FOOD = 20 export const MAX_FOOD = 20
export const MAX_SATURATION = 20
export const MAX_HEALTH = 20 export const MAX_HEALTH = 20
export const LOW_HEALTH = 4 export const LOW_HEALTH = 4
@ -15,3 +16,7 @@ export const DELTA_RATIO = 128 * 32
export const SERVER_TICK_RATE = 50 export const SERVER_TICK_RATE = 50
export const SERVER_TPS = 1 / SERVER_TICK_RATE * 1000 export const SERVER_TPS = 1 / SERVER_TICK_RATE * 1000
export const OFF_HAND_SLOT = 45
export const HOTBAR_START_SLOT = 36
export const HOTBAR_END_SLOT = 44