cubbot/src/modules/Inventory.ts

201 lines
6.0 KiB
TypeScript

import { HOTBAR_END_SLOT, HOTBAR_START_SLOT, OFF_HAND_SLOT } from '../utils/constants'
import Module from '../utils/Module'
import { ISlot } from '../utils/types'
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 */
// TODO: Pick Item
// TODO: craft
// TODO: Log actions
// TODO: Move inventory (equip armor)