307 lines
8.2 KiB
TypeScript
307 lines
8.2 KiB
TypeScript
/**
|
|
* @source https://github.com/kolbytn/mindcraft
|
|
*/
|
|
import type { Bot } from 'mineflayer'
|
|
import minecraftData from 'minecraft-data'
|
|
import prismarine_items from 'prismarine-item'
|
|
import { botConfig } from '../composables/config'
|
|
|
|
const mc_version = botConfig.version!
|
|
const mcdata = minecraftData(mc_version)
|
|
const Item = prismarine_items(mc_version)
|
|
|
|
interface MinecraftRecipe {
|
|
result: { id: number, count: number }
|
|
inShape?: Array<Array<{ id: number, count: number }>>
|
|
ingredients?: Array<{ id: number, count: number }>
|
|
requiresTable?: boolean
|
|
}
|
|
|
|
export const WOOD_TYPES: string[] = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak']
|
|
export const MATCHING_WOOD_BLOCKS: string[] = [
|
|
'log',
|
|
'planks',
|
|
'sign',
|
|
'boat',
|
|
'fence_gate',
|
|
'door',
|
|
'fence',
|
|
'slab',
|
|
'stairs',
|
|
'button',
|
|
'pressure_plate',
|
|
'trapdoor',
|
|
]
|
|
export const WOOL_COLORS: string[] = [
|
|
'white',
|
|
'orange',
|
|
'magenta',
|
|
'light_blue',
|
|
'yellow',
|
|
'lime',
|
|
'pink',
|
|
'gray',
|
|
'light_gray',
|
|
'cyan',
|
|
'purple',
|
|
'blue',
|
|
'brown',
|
|
'green',
|
|
'red',
|
|
'black',
|
|
]
|
|
|
|
export function isHuntable(mob: { name?: string, metadata: any[] }): boolean {
|
|
if (!mob || !mob.name)
|
|
return false
|
|
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep']
|
|
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16] // metadata 16 is not baby
|
|
}
|
|
|
|
export function isHostile(mob: { name?: string, type?: string }): boolean {
|
|
if (!mob || !mob.name)
|
|
return false
|
|
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem'
|
|
}
|
|
|
|
export function getItemId(itemName: string): number | null {
|
|
const item = mcdata.itemsByName[itemName]
|
|
if (item) {
|
|
return item.id
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getItemName(itemId: number): string | null {
|
|
const item = mcdata.items[itemId]
|
|
if (item) {
|
|
return item.name
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getBlockId(blockName: string): number | null {
|
|
const block = mcdata.blocksByName[blockName]
|
|
if (block) {
|
|
return block.id
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getBlockName(blockId: number): string | null {
|
|
const block = mcdata.blocks[blockId]
|
|
if (block) {
|
|
return block.name
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getAllItems(ignore: string[] = []): any[] {
|
|
const items = []
|
|
for (const itemId in mcdata.items) {
|
|
const item = mcdata.items[itemId]
|
|
if (!ignore.includes(item.name)) {
|
|
items.push(item)
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
export function getAllItemIds(ignore: string[] = []): number[] {
|
|
const items = getAllItems(ignore)
|
|
const itemIds = []
|
|
for (const item of items) {
|
|
itemIds.push(item.id)
|
|
}
|
|
return itemIds
|
|
}
|
|
|
|
export function getAllBlocks(ignore: string[] = []): any[] {
|
|
const blocks = []
|
|
for (const blockId in mcdata.blocks) {
|
|
const block = mcdata.blocks[blockId]
|
|
if (!ignore.includes(block.name)) {
|
|
blocks.push(block)
|
|
}
|
|
}
|
|
return blocks
|
|
}
|
|
|
|
export function getAllBlockIds(ignore: string[] = []): number[] {
|
|
const blocks = getAllBlocks(ignore)
|
|
const blockIds = []
|
|
for (const block of blocks) {
|
|
blockIds.push(block.id)
|
|
}
|
|
return blockIds
|
|
}
|
|
|
|
export function getAllBiomes(): any {
|
|
return mcdata.biomes
|
|
}
|
|
|
|
export function getItemCraftingRecipes(itemName: string): Record<string, number>[] | null {
|
|
const itemId = getItemId(itemName)
|
|
if (!itemId || !mcdata.recipes[itemId]) {
|
|
return null
|
|
}
|
|
|
|
const recipes: Record<string, number>[] = []
|
|
for (const r of mcdata.recipes[itemId] as MinecraftRecipe[]) {
|
|
const recipe: Record<string, number> = {}
|
|
let ingredients: Array<{ id: number, count: number }> = []
|
|
|
|
if (r.ingredients) {
|
|
ingredients = r.ingredients
|
|
}
|
|
else if (r.inShape) {
|
|
ingredients = r.inShape.flat()
|
|
}
|
|
|
|
for (const ingredient of ingredients) {
|
|
const ingredientName = getItemName(ingredient.id)
|
|
if (ingredientName === null)
|
|
continue
|
|
recipe[ingredientName] ??= 0
|
|
recipe[ingredientName] += ingredient.count
|
|
}
|
|
recipes.push(recipe)
|
|
}
|
|
|
|
return recipes
|
|
}
|
|
|
|
export function isSmeltable(itemName: string): boolean {
|
|
const misc_smeltables = ['beef', 'chicken', 'cod', 'mutton', 'porkchop', 'rabbit', 'salmon', 'tropical_fish', 'potato', 'kelp', 'sand', 'cobblestone', 'clay_ball']
|
|
return itemName.includes('raw') || itemName.includes('log') || misc_smeltables.includes(itemName)
|
|
}
|
|
|
|
export function getSmeltingFuel(bot: Bot): any {
|
|
let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal')
|
|
if (fuel)
|
|
return fuel
|
|
fuel = bot.inventory.items().find(i => i.name.includes('log') || i.name.includes('planks'))
|
|
if (fuel)
|
|
return fuel
|
|
return bot.inventory.items().find(i => i.name === 'coal_block' || i.name === 'lava_bucket')
|
|
}
|
|
|
|
export function getFuelSmeltOutput(fuelName: string): number {
|
|
if (fuelName === 'coal' || fuelName === 'charcoal')
|
|
return 8
|
|
if (fuelName.includes('log') || fuelName.includes('planks'))
|
|
return 1.5
|
|
if (fuelName === 'coal_block')
|
|
return 80
|
|
if (fuelName === 'lava_bucket')
|
|
return 100
|
|
return 0
|
|
}
|
|
|
|
export function getItemSmeltingIngredient(itemName: string): string | undefined {
|
|
return {
|
|
baked_potato: 'potato',
|
|
steak: 'raw_beef',
|
|
cooked_chicken: 'raw_chicken',
|
|
cooked_cod: 'raw_cod',
|
|
cooked_mutton: 'raw_mutton',
|
|
cooked_porkchop: 'raw_porkchop',
|
|
cooked_rabbit: 'raw_rabbit',
|
|
cooked_salmon: 'raw_salmon',
|
|
dried_kelp: 'kelp',
|
|
iron_ingot: 'raw_iron',
|
|
gold_ingot: 'raw_gold',
|
|
copper_ingot: 'raw_copper',
|
|
glass: 'sand',
|
|
}[itemName]
|
|
}
|
|
|
|
export function getItemBlockSources(itemName: string): string[] {
|
|
const itemId = getItemId(itemName)
|
|
const sources: string[] = []
|
|
for (const block of getAllBlocks()) {
|
|
if (block.drops.includes(itemId)) {
|
|
sources.push(block.name)
|
|
}
|
|
}
|
|
return sources
|
|
}
|
|
|
|
export function getItemAnimalSource(itemName: string): string | undefined {
|
|
return {
|
|
raw_beef: 'cow',
|
|
raw_chicken: 'chicken',
|
|
raw_cod: 'cod',
|
|
raw_mutton: 'sheep',
|
|
raw_porkchop: 'pig',
|
|
raw_rabbit: 'rabbit',
|
|
raw_salmon: 'salmon',
|
|
leather: 'cow',
|
|
wool: 'sheep',
|
|
}[itemName]
|
|
}
|
|
|
|
export function getBlockTool(blockName: string): string | null {
|
|
const block = mcdata.blocksByName[blockName]
|
|
if (!block || !block.harvestTools) {
|
|
return null
|
|
}
|
|
const toolId = Number(Object.keys(block.harvestTools)[0])
|
|
return getItemName(toolId)
|
|
}
|
|
|
|
export function makeItem(name: string, amount: number = 1): any {
|
|
const itemId = getItemId(name)
|
|
if (itemId === null)
|
|
throw new Error(`Unknown item: ${name}`)
|
|
return new Item(itemId, amount)
|
|
}
|
|
|
|
export function ingredientsFromPrismarineRecipe(recipe: MinecraftRecipe): Record<string, number> {
|
|
const requiredIngredients: Record<string, number> = {}
|
|
if (recipe.inShape) {
|
|
for (const ingredient of recipe.inShape.flat()) {
|
|
if (ingredient.id < 0)
|
|
continue // prismarine-recipe uses id -1 as an empty crafting slot
|
|
const ingredientName = getItemName(ingredient.id)
|
|
if (ingredientName) {
|
|
requiredIngredients[ingredientName] ??= 0
|
|
requiredIngredients[ingredientName] += ingredient.count
|
|
}
|
|
}
|
|
}
|
|
if (recipe.ingredients) {
|
|
for (const ingredient of recipe.ingredients) {
|
|
if (ingredient.id < 0)
|
|
continue
|
|
const ingredientName = getItemName(ingredient.id)
|
|
if (ingredientName) {
|
|
requiredIngredients[ingredientName] ??= 0
|
|
requiredIngredients[ingredientName] -= ingredient.count
|
|
}
|
|
// Yes, the `-=` is intended.
|
|
// prismarine-recipe uses positive numbers for the shaped ingredients but negative for unshaped.
|
|
// Why this is the case is beyond my understanding.
|
|
}
|
|
}
|
|
return requiredIngredients
|
|
}
|
|
|
|
export function calculateLimitingResource<T extends string>(
|
|
availableItems: Record<T, number>,
|
|
requiredItems: Record<T, number>,
|
|
discrete: boolean = true,
|
|
): { num: number, limitingResource: T | null } {
|
|
let limitingResource: T | null = null
|
|
let num = Infinity
|
|
for (const itemType in requiredItems) {
|
|
if (availableItems[itemType] < requiredItems[itemType] * num) {
|
|
limitingResource = itemType
|
|
num = availableItems[itemType] / requiredItems[itemType]
|
|
}
|
|
}
|
|
if (discrete)
|
|
num = Math.floor(num)
|
|
return { num, limitingResource }
|
|
}
|