diff --git a/README.md b/README.md index 33751fe..5886283 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ 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 +`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 ``` diff --git a/env.sample.json b/env.sample.json index 43a4d4a..f0d93ce 100644 --- a/env.sample.json +++ b/env.sample.json @@ -15,6 +15,7 @@ }, "Combat": {}, "Chat": {}, + "Meal": {}, "Time": { "log": "debug" } diff --git a/src/modules/Combat.ts b/src/modules/Combat.ts index d450b85..12caa8c 100644 --- a/src/modules/Combat.ts +++ b/src/modules/Combat.ts @@ -4,7 +4,8 @@ import Module from '../utils/Module' import { IDelta } from '../utils/types' import Connection from './Connection' import Entities, { IAlive, ILiving, IPlayer } from './Entities' -import Life from './Life' +import Inventory from './Inventory' +import Spawn from './Spawn' import State from './State' interface IConf { @@ -28,6 +29,10 @@ interface IConf { mobs: string[] /** Targeted entities ids (override mobs) */ mobsIds?: number[] + /** Weapons names by priority */ + weapons: string[] + /** Weapons ids by priority (override weapons) */ + weaponsIds?: number[] } interface IEnemy extends IAlive { @@ -40,9 +45,8 @@ const SAFE_DELAY = SERVER_TICK_RATE * 2 export default class Combat extends Module { private state!: State - private life!: Life + private spawn!: Spawn private entities!: Entities - private connection!: Connection public umount() { this.conf.fight = false @@ -51,13 +55,27 @@ export default class Combat extends Module { protected mount() { this.state = this.load(State) - this.life = this.load(Life) + this.spawn = this.load(Spawn) this.entities = this.load(Entities) - this.connection = this.load(Connection) - this.fight() this.arrows() + if (this.conf.mobsIds && (this.conf.weaponsIds || this.conf.weapon === 'none')) { + this.fight() + } else { + this.load(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 => { switch (packet.event) { case 0: @@ -72,7 +90,7 @@ export default class Combat extends Module { }) // MAYBE: chat packet.message if (this.conf.respawn) { - this.life.respawn() + this.spawn.respawn() } } else { this.logger.info({ msg: 'Fight end', packet }) @@ -85,7 +103,7 @@ export default class Combat extends Module { return { respawn: true, fight: true, - weapon: 'always', + weapon: 'needed', arrows: 'dodge', delay: 'safe', multiaura: false, @@ -98,22 +116,18 @@ export default class Combat extends Module { /*'snow_golem',*/ 'spider', 'stray', 'vex', 'vindicator', 'witch', 'wither', 'wither_skeleton', /*'wolf',*/ 'zombie', /*'zombie_pigman',*/ 'zombie_villager', ], + weapons: [ + 'diamond_sword', 'diamond_axe', 'iron_sword', 'iron_axe', 'stone_sword', 'stone_axe', + ], } } private fight() { 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) - } + if (this.conf.weapon == 'always') { + this.equipWeapon() } + //TODO: pick shield // Find dangerous entities const dangers: IEnemy[] = ([...this.entities.players, ...this.entities.livings] as IAlive[]) @@ -139,6 +153,10 @@ export default class Combat extends Module { if (dangers.length > 0) { 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.logger.debug({ msg: 'Attack', type: 'entityId', value: entityId }) this.entities.attack(entityId) @@ -155,6 +173,14 @@ export default class Combat extends Module { } } + private equipWeapon() { + const inv = this.load(Inventory) + const weapon = inv.findFirst(this.conf.weaponsIds!) + if (weapon.slot >= 0) { + inv.selectItem(weapon.slot, false) + } + } + private arrows() { if (this.conf.arrows !== 'ignore') { const arrows = [...this.entities.objects] @@ -176,3 +202,6 @@ export default class Combat extends Module { //TODO: respawn or disconnect } + +// TODO: look and swing +// TODO: critical \ No newline at end of file diff --git a/src/modules/Connection.ts b/src/modules/Connection.ts index b98928f..f050b19 100644 --- a/src/modules/Connection.ts +++ b/src/modules/Connection.ts @@ -3,6 +3,7 @@ import Cubbot from '../Cubbot' import Data from '../utils/Data' import Module from '../utils/Module' import Chat, { IMessage } from './Chat' +import Spawn from './Spawn' type DisconnectReason = 'authservers_down' | 'banned' | 'banned.reason' | 'banned.expiration' | 'banned_ip.reason' | 'banned_ip.expiration' | 'duplicate_login' | 'flying' | 'generic' | 'idling' | 'illegal_characters' | @@ -37,7 +38,7 @@ export default class Connection extends Module { return this._oldData } - private _data?: Data + private _data = new Data() public get data() { return this._data } @@ -48,9 +49,13 @@ export default class Connection extends Module { } protected mount() { + const spawn = this.load(Spawn) + spawn.addCheck('data') + this.client.on('connect', () => { this._connected = true this.logger.trace('Connected') + this._data.onReady(() => spawn.validateCheck('data')) }) this.client.on('disconnect', ({ reason }) => { this._connected = false @@ -66,7 +71,7 @@ export default class Connection extends Module { }) this.client.on('login', () => { 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' })) // FIXME: use 'minecraft-data' when it include 1.15+ }) diff --git a/src/modules/Entities.ts b/src/modules/Entities.ts index f0ce06e..5528036 100644 --- a/src/modules/Entities.ts +++ b/src/modules/Entities.ts @@ -254,8 +254,6 @@ export default class Entities extends Module<{}> { } -// TODO: player info - // MAYBE: animation // MAYBE: attach_entity // MAYBE: entity_sound_effect diff --git a/src/modules/Inventory.ts b/src/modules/Inventory.ts index 207ba0d..0a574f1 100644 --- a/src/modules/Inventory.ts +++ b/src/modules/Inventory.ts @@ -1,9 +1,200 @@ -//TODO: window_items -//TODO: set_slot +import { HOTBAR_END_SLOT, HOTBAR_START_SLOT, OFF_HAND_SLOT } from '../utils/constants' +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() + 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 */ -//TODO: Pick Item -//TDOO: craft -//TODO: Held Item Change \ No newline at end of file +// TODO: Pick Item +// TODO: craft +// TODO: Log actions +// TODO: Move inventory (equip armor) diff --git a/src/modules/Life.ts b/src/modules/Life.ts index 169d324..cb84ccf 100644 --- a/src/modules/Life.ts +++ b/src/modules/Life.ts @@ -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 Module from '../utils/Module' +import Spawn from './Spawn' interface IConf { - /** Respawn on world join */ - spawn: boolean, /** Warn if health is lower */ lowHealth: number, - /** Life to regenerate */ - targetHealth: number, /** Log health info */ showHealth: boolean, - /** Use food if needed */ - eat: boolean, - /** Prefer saturation over food power */ - preferSaturation: boolean, - /** Warn and eat if food level is lower */ + /** Warn if food level is lower */ lowFood: number, - /** Food level needed to regenerate */ - regenerateFood: number, /** Log food level info */ showFood: boolean, } /** Monitor health, food and xp */ export default class Life extends Module { + + private _health = MAX_HEALTH public get health() { return this._health } + + private _food = MAX_FOOD public get food() { return this._food } + + private _saturation = MAX_SATURATION public get saturation() { return this._saturation } @@ -39,49 +37,37 @@ export default class Life extends Module { return this.health > 0 } - public get mustEat() { - return this.food <= this.conf.lowFood || - (this.health <= this.conf.targetHealth && this.food <= this.conf.regenerateFood) - } + private _experienceBar = 0 public get experienceBar() { return this._experienceBar } + + private _level = 0 public get level() { return this._level } + + private _totalExperience = 0 public get totalExperience() { return this._totalExperience } - private _health!: number - - private _food!: number - - 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 }) + private _events = new EventEmitter() + public get events() { + return this._events } protected mount() { - //TODO: const inventory = this.conf.eat ? this.load('inventory') : undefined + const spawn = this.load(Spawn) + spawn.addCheck('health') 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._food = data.food this._saturation = data.foodSaturation + spawn.validateCheck('health') + if (this.health <= this.conf.lowHealth) { this.logger.warn({ msg: this.alive ? 'Low health' : 'Dead', type: 'health', value: this.health }) } else if (this.conf.showHealth) { @@ -94,10 +80,7 @@ export default class Life extends Module { this.logger.info({ msg: 'Replete', type: 'food', value: this.food }) } - if (this.conf.eat && this.mustEat) { - this.logger.warn({ msg: 'TODO: Must eat' }) - // TODO: inventory.items.filter(food).orderBy(this.conf.preferSaturation) - } + this._events.emit('update') }) this.client.on('experience', data => { @@ -107,18 +90,13 @@ export default class Life extends Module { }) } - //TODO: send client settings + // MAYBE: send client settings protected getConf(): IConf { return { - spawn: true, lowHealth: LOW_HEALTH, - targetHealth: MAX_HEALTH, showHealth: false, - eat: true, - preferSaturation: true, lowFood: SPRINT_FOOD, - regenerateFood: REGENERATE_FOOD, showFood: false, } } diff --git a/src/modules/Meal.ts b/src/modules/Meal.ts new file mode 100644 index 0000000..34707af --- /dev/null +++ b/src/modules/Meal.ts @@ -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 { + + 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) + this.inv = this.load(Inventory) + this.state = this.load(State) + + const ready = () => { + this.life.events.on('update', this.eat.bind(this)) + this.eat() + } + + if (this.conf.foodsIds) { + ready() + } else { + this.load(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') + } + } + } + +} \ No newline at end of file diff --git a/src/modules/Spawn.ts b/src/modules/Spawn.ts new file mode 100644 index 0000000..7cf6b78 --- /dev/null +++ b/src/modules/Spawn.ts @@ -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 } + } + +} \ No newline at end of file diff --git a/src/modules/State.ts b/src/modules/State.ts index 76853fa..8ed6269 100644 --- a/src/modules/State.ts +++ b/src/modules/State.ts @@ -1,5 +1,6 @@ import Module from '../utils/Module' import { IDelta, IPosition, IState, gameMode } from '../utils/types' +import Spawn from './Spawn' // MAYBE: split @@ -65,11 +66,22 @@ export default class State extends Module<{ positionConfirm: boolean }> { } protected mount() { - this.client.on('login', data => this._state = data) + const spawn = this.load(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 = { 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) => { 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.logger.debug({ msg: 'Teleported', type: 'position', value: this.position }) + spawn.validateCheck('position') }) 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) break case 1: - this._players.get(player.UUID)!.gamemode = player.gamemode + const p1 = this._players.get(player.UUID) + if (p1) { + p1.gamemode = player.gamemode + } break case 2: - this._players.get(player.UUID)!.ping = player.ping + const p2 = this._players.get(player.UUID) + if (p2) { + p2.ping = player.ping + } break case 3: - this._players.get(player.UUID)!.name = player.name + const p3 = this._players.get(player.UUID) + if (p3) { + p3.name = player.name + } break case 4: this._players.delete(player.UUID) diff --git a/src/modules/index.ts b/src/modules/index.ts index da6b375..57441a6 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,9 +1,12 @@ import { IModuleType } from '../utils/Module' import Chat from './Chat' -import Client from './Connection' import Combat from './Combat' +import Client from './Connection' import Entities from './Entities' +import Inventory from './Inventory' import Life from './Life' +import Meal from './Meal' +import Spawn from './Spawn' import State from './State' import Time from './Time' @@ -14,6 +17,9 @@ export default [ Combat, Entities, Life, + Meal, State, Time, + Inventory, + Spawn, ] as IModuleType[] diff --git a/src/utils/Data.ts b/src/utils/Data.ts index a1d03f6..37a8151 100644 --- a/src/utils/Data.ts +++ b/src/utils/Data.ts @@ -11,20 +11,42 @@ export interface IEntityData { metadata: Array<{ entity?: string }> } +export interface IItemData { + display_name: string + max_stack_size: number + numeric_id: number +} + /** Load game data */ export default class Data { - private _ready = false - public get ready() { - return this._ready - } + private _onReady: Array<(data: Data) => void> = [] private _entities?: { [name: string]: IEntityData } public get 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') const path = directory + version + '.json' if (!fs.existsSync(directory)) { @@ -39,29 +61,20 @@ export default class Data { res.on('error', error => console.error({ msg: 'Downloading error', error })) res.on('end', () => { out.close() - setTimeout(() => this.load(path), 500) + setTimeout(() => this.read(path), 500) }) }).on('error', error => console.error({ msg: 'Downloading error', error })) } else { - this.load(path) + this.read(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) { + private read(path: string) { try { const data = JSON.parse(readFileSync(path).toString())[0] // NOTE: memory ??? this._entities = data.entities.entity - this._ready = true + this._items = data.items.item + this._onReady.forEach(cb => cb(this)) } catch (error) { console.error('/!\\ =( Please remove ' + path, error) process.exit(1) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 44630b4..50b24c5 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,6 +1,7 @@ export const SPRINT_FOOD = 6 export const REGENERATE_FOOD = 18 export const MAX_FOOD = 20 +export const MAX_SATURATION = 20 export const MAX_HEALTH = 20 export const LOW_HEALTH = 4 @@ -15,3 +16,7 @@ export const DELTA_RATIO = 128 * 32 export const SERVER_TICK_RATE = 50 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