import minecraftData from 'minecraft-data'; import settings from '../../settings.js'; import { createBot } from 'mineflayer'; import prismarine_items from 'prismarine-item'; import { pathfinder } from 'mineflayer-pathfinder'; import { plugin as pvp } from 'mineflayer-pvp'; import { plugin as collectblock } from 'mineflayer-collectblock'; import { plugin as autoEat } from 'mineflayer-auto-eat'; import plugin from 'mineflayer-armor-manager'; const armorManager = plugin; const mc_version = settings.minecraft_version; const mcdata = minecraftData(mc_version); const Item = prismarine_items(mc_version); /** * @typedef {string} ItemName * @typedef {string} BlockName */ export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak']; export const MATCHING_WOOD_BLOCKS = [ 'log', 'planks', 'sign', 'boat', 'fence_gate', 'door', 'fence', 'slab', 'stairs', 'button', 'pressure_plate', 'trapdoor' ] export const WOOL_COLORS = [ 'white', 'orange', 'magenta', 'light_blue', 'yellow', 'lime', 'pink', 'gray', 'light_gray', 'cyan', 'purple', 'blue', 'brown', 'green', 'red', 'black' ] export function initBot(username) { let bot = createBot({ username: username, host: settings.host, port: settings.port, auth: settings.auth, version: mc_version, }); bot.loadPlugin(pathfinder); bot.loadPlugin(pvp); bot.loadPlugin(collectblock); bot.loadPlugin(autoEat); bot.loadPlugin(armorManager); // auto equip armor return bot; } export function isHuntable(mob) { 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) { 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) { let item = mcdata.itemsByName[itemName]; if (item) { return item.id; } return null; } export function getItemName(itemId) { let item = mcdata.items[itemId] if (item) { return item.name; } return null; } export function getBlockId(blockName) { let block = mcdata.blocksByName[blockName]; if (block) { return block.id; } return null; } export function getBlockName(blockId) { let block = mcdata.blocks[blockId] if (block) { return block.name; } return null; } export function getAllItems(ignore) { if (!ignore) { ignore = []; } let 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) { const items = getAllItems(ignore); let itemIds = []; for (const item of items) { itemIds.push(item.id); } return itemIds; } export function getAllBlocks(ignore) { if (!ignore) { ignore = []; } let 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) { const blocks = getAllBlocks(ignore); let blockIds = []; for (const block of blocks) { blockIds.push(block.id); } return blockIds; } export function getAllBiomes() { return mcdata.biomes; } export function getItemCraftingRecipes(itemName) { let itemId = getItemId(itemName); if (!mcdata.recipes[itemId]) { return null; } let recipes = []; for (let r of mcdata.recipes[itemId]) { let recipe = {}; let ingredients = []; if (r.ingredients) { ingredients = r.ingredients; } else if (r.inShape) { ingredients = r.inShape.flat(); } for (let ingredient of ingredients) { let ingredientName = getItemName(ingredient); if (ingredientName === null) continue; if (!recipe[ingredientName]) recipe[ingredientName] = 0; recipe[ingredientName]++; } recipes.push(recipe); } return recipes; } export function getItemSmeltingIngredient(itemName) { 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) { let itemId = getItemId(itemName); let sources = []; for (let block of getAllBlocks()) { if (block.drops.includes(itemId)) { sources.push(block.name); } } return sources; } export function getItemAnimalSource(itemName) { 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) { let block = mcdata.blocksByName[blockName]; if (!block || !block.harvestTools) { return null; } return getItemName(Object.keys(block.harvestTools)[0]); // Double check first tool is always simplest } export function makeItem(name, amount=1) { return new Item(getItemId(name), amount); } /** * Returns the number of ingredients required to use the recipe once. * * @param {Recipe} recipe * @returns {Object} an object describing the number of each ingredient. */ export function ingredientsFromPrismarineRecipe(recipe) { let requiredIngedients = {}; 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); requiredIngedients[ingredientName] ??=0; requiredIngedients[ingredientName] += ingredient.count; } if (recipe.ingredients) for (const ingredient of recipe.ingredients) { if(ingredient.id<0) continue; const ingredientName = getItemName(ingredient.id); requiredIngedients[ingredientName] ??=0; requiredIngedients[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 requiredIngedients; } /** * Calculates the number of times an action, such as a crafing recipe, can be completed before running out of resources. * @template T - doesn't have to be an item. This could be any resource. * @param {Object.} availableItems - The resources available; e.g, `{'cobble_stone': 7, 'stick': 10}` * @param {Object.} requiredItems - The resources required to complete the action once; e.g, `{'cobble_stone': 3, 'stick': 2}` * @param {boolean} discrete - Is the action discrete? * @returns {{num: number, limitingResource: (T | null)}} the number of times the action can be completed and the limmiting resource; e.g `{num: 2, limitingResource: 'cobble_stone'}` */ export function calculateLimitingResource(availableItems, requiredItems, discrete=true) { let limitingResource = 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} }