refactor: with loads of better utils

This commit is contained in:
Neko Ayaka
2025-01-09 18:40:12 +08:00
parent d94dfd4e8c
commit eb7bfcc52e
10 changed files with 1862 additions and 371 deletions
+37 -10
View File
@@ -6,25 +6,52 @@ import type { Mineflayer } from '../libs/mineflayer'
import pf from 'mineflayer-pathfinder'
import * as mc from '../utils/mcdata'
export function getNearestFreeSpace(mineflayer: Mineflayer, size: number = 1, distance: number = 8): Vec3 | undefined {
const emptyPositions = mineflayer.bot.findBlocks({
matching: (block: Block) => block?.name === 'air',
export function getNearestFreeSpace(
mineflayer: Mineflayer,
size: number = 1,
distance: number = 8,
): Vec3 | undefined {
/**
* Get the nearest empty space with solid blocks beneath it of the given size.
* @param {number} size - The (size x size) of the space to find, default 1.
* @param {number} distance - The maximum distance to search, default 8.
* @returns {Vec3} - The south west corner position of the nearest free space.
* @example
* let position = world.getNearestFreeSpace( 1, 8);
*/
const empty_pos = mineflayer.bot.findBlocks({
matching: (block: Block | null) => {
return block !== null && block.name === 'air'
},
maxDistance: distance,
count: 1000,
})
return emptyPositions.find((pos) => {
for (let i = 0; i < empty_pos.length; i++) {
let empty = true
for (let x = 0; x < size; x++) {
for (let z = 0; z < size; z++) {
const top = mineflayer.bot.blockAt(pos.offset(x, 0, z))
const bottom = mineflayer.bot.blockAt(pos.offset(x, -1, z))
if (!top || top.name !== 'air' || !bottom?.drops?.length || !bottom.diggable) {
return false
const top = mineflayer.bot.blockAt(empty_pos[i].offset(x, 0, z))
const bottom = mineflayer.bot.blockAt(empty_pos[i].offset(x, -1, z))
if (
!top
|| top.name !== 'air'
|| !bottom
|| (bottom.drops?.length ?? 0) === 0
|| !bottom.diggable
) {
empty = false
break
}
}
if (!empty)
break
}
return true
})
if (empty) {
return empty_pos[i]
}
}
return undefined
}
export function getNearestBlocks(mineflayer: Mineflayer, blockTypes: string[] | string | null = null, distance: number = 16, count: number = 10000): Block[] {
@@ -0,0 +1,166 @@
import type { Block } from 'prismarine-block'
import type { Mineflayer } from '../../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import pathfinder from 'mineflayer-pathfinder'
import { getNearestBlocks } from '../../composables/world'
import { breakBlockAt } from '../blocks'
import { ensurePickaxe } from './ensure'
import { pickupNearbyItems } from './world-interactions'
const logger = useLogg('Action:CollectBlock').useGlobalConfig()
export async function collectBlock(
mineflayer: Mineflayer,
blockType: string,
num = 1,
range = 16,
): Promise<boolean> {
if (num < 1) {
logger.log(`Invalid number of blocks to collect: ${num}.`)
return false
}
const blockTypes = [blockType]
// Add block variants
if (
[
'coal',
'diamond',
'emerald',
'iron',
'gold',
'lapis_lazuli',
'redstone',
'copper',
].includes(blockType)
) {
blockTypes.push(`${blockType}_ore`, `deepslate_${blockType}_ore`)
}
if (blockType.endsWith('ore')) {
blockTypes.push(`deepslate_${blockType}`)
}
if (blockType === 'dirt') {
blockTypes.push('grass_block')
}
let collected = 0
while (collected < num) {
const blocks = getNearestBlocks(mineflayer, blockTypes, range)
if (blocks.length === 0) {
if (collected === 0)
logger.log(`No ${blockType} nearby to collect.`)
else logger.log(`No more ${blockType} nearby to collect.`)
break
}
const block = blocks[0]
try {
// Equip appropriate tool
if (mineflayer.bot.game.gameMode !== 'creative') {
await mineflayer.bot.tool.equipForBlock(block)
const itemId = mineflayer.bot.heldItem ? mineflayer.bot.heldItem.type : null
if (!block.canHarvest(itemId)) {
logger.log(`Don't have right tools to harvest ${block.name}.`)
if (block.name.includes('ore') || block.name.includes('stone')) {
await ensurePickaxe(mineflayer)
}
throw new Error('Don\'t have right tools to harvest block.')
}
}
// Implement vein mining
const veinBlocks = findVeinBlocks(mineflayer, block, 100, range, 1)
for (const veinBlock of veinBlocks) {
if (collected >= num)
break
// Move to the block using pathfinder
const goal = new pathfinder.goals.GoalGetToBlock(
veinBlock.position.x,
veinBlock.position.y,
veinBlock.position.z,
)
await mineflayer.bot.pathfinder.goto(goal)
// Break the block and collect drops
await mineAndCollect(mineflayer, veinBlock)
collected++
// Check if inventory is full
if (mineflayer.bot.inventory.emptySlotCount() === 0) {
logger.log('Inventory is full, cannot collect more items.')
break
}
}
}
catch (err) {
logger.log(`Failed to collect ${blockType}: ${err}.`)
continue
}
}
logger.log(`Collected ${collected} ${blockType}(s).`)
return collected > 0
}
// Helper function to mine a block and collect drops
async function mineAndCollect(mineflayer: Mineflayer, block: Block): Promise<void> {
// Break the block
await breakBlockAt(mineflayer, block.position.x, block.position.y, block.position.z)
// Use your existing function to pick up nearby items
await pickupNearbyItems(mineflayer, 5)
}
// Function to find connected blocks (vein mining)
function findVeinBlocks(
mineflayer: Mineflayer,
startBlock: Block,
maxBlocks = 100,
maxDistance = 16,
floodRadius = 1,
): Block[] {
const veinBlocks: Block[] = []
const visited = new Set<string>()
const queue: Block[] = [startBlock]
while (queue.length > 0 && veinBlocks.length < maxBlocks) {
const block = queue.shift()
if (!block)
continue
const key = block.position.toString()
if (visited.has(key))
continue
visited.add(key)
if (block.name !== startBlock.name)
continue
if (block.position.distanceTo(startBlock.position) > maxDistance)
continue
veinBlocks.push(block)
// Check neighboring blocks within floodRadius
for (let dx = -floodRadius; dx <= floodRadius; dx++) {
for (let dy = -floodRadius; dy <= floodRadius; dy++) {
for (let dz = -floodRadius; dz <= floodRadius; dz++) {
if (dx === 0 && dy === 0 && dz === 0)
continue // Skip the current block
const neighborPos = block.position.offset(dx, dy, dz)
const neighborBlock = mineflayer.bot.blockAt(neighborPos)
if (neighborBlock && !visited.has(neighborPos.toString())) {
queue.push(neighborBlock)
}
}
}
}
}
return veinBlocks
}
@@ -0,0 +1,500 @@
import type { Mineflayer } from '../../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import { getItemId } from '../../utils/mcdata'
import { craftRecipe } from '../crafting'
import { moveAway } from '../movement'
import { collectBlock } from './collectBlock'
import { gatherWood } from './gatherWood'
import { getItemCount } from './inventory'
// Constants for crafting and gathering
const PLANKS_PER_LOG = 4
const STICKS_PER_PLANK = 2
const logger = useLogg('Action:Ensure').useGlobalConfig()
// Helper function to ensure a crafting table
export async function ensureCraftingTable(mineflayer: Mineflayer): Promise<boolean> {
logger.log('Bot: Checking for a crafting table...')
let hasCraftingTable = getItemCount(mineflayer, 'crafting_table') > 0
if (hasCraftingTable) {
logger.log('Bot: Crafting table is available.')
return true
}
while (!hasCraftingTable) {
const planksEnsured = await ensurePlanks(mineflayer, 4)
if (!planksEnsured) {
logger.error('Bot: Failed to ensure planks.')
continue
}
// Craft crafting table
hasCraftingTable = await craftRecipe(mineflayer, 'crafting_table', 1)
if (hasCraftingTable) {
mineflayer.bot.chat('I have made a crafting table.')
logger.log('Bot: Crafting table crafted.')
}
else {
logger.error('Bot: Failed to craft crafting table.')
}
}
return hasCraftingTable
}
// Helper function to ensure a specific amount of planks
export async function ensurePlanks(mineflayer: Mineflayer, neededAmount: number): Promise<boolean> {
logger.log('Bot: Checking for planks...')
let planksCount = getItemCount(mineflayer, 'planks')
if (neededAmount < planksCount) {
logger.log('Bot: Have enough planks.')
return true
}
while (neededAmount > planksCount) {
const logsNeeded = Math.ceil((neededAmount - planksCount) / PLANKS_PER_LOG)
// Get all available log types in inventory
const availableLogs = mineflayer.bot.inventory
.items()
.filter(item => item.name.includes('log'))
// If no logs available, gather more wood
if (availableLogs.length === 0) {
await gatherWood(mineflayer, logsNeeded, 80)
logger.error('Bot: Not enough logs for planks.')
continue
}
// Iterate over each log type to craft planks
for (const log of availableLogs) {
const logType = log.name.replace('_log', '') // Get log type without "_log" suffix
const logsToCraft = Math.min(log.count, logsNeeded)
logger.log(
`Trying to make ${logsToCraft * PLANKS_PER_LOG} ${logType}_planks`,
)
logger.log(`NeededAmount: ${neededAmount}, while I have ${planksCount}`)
const crafted = await craftRecipe(
mineflayer,
`${logType}_planks`,
logsToCraft * PLANKS_PER_LOG,
)
if (crafted) {
planksCount = getItemCount(mineflayer, 'planks')
mineflayer.bot.chat(
`I have crafted ${logsToCraft * PLANKS_PER_LOG} ${logType} planks.`,
)
logger.log(`Bot: ${logType} planks crafted.`)
}
else {
logger.error(`Bot: Failed to craft ${logType} planks.`)
return false
}
// Check if we have enough planks after crafting
if (planksCount >= neededAmount)
break
}
}
return planksCount >= neededAmount
};
// Helper function to ensure a specific amount of sticks
export async function ensureSticks(mineflayer: Mineflayer, neededAmount: number): Promise<boolean> {
logger.log('Bot: Checking for sticks...')
let sticksCount = getItemCount(mineflayer, 'stick')
if (neededAmount <= sticksCount) {
logger.log('Bot: Have enough sticks.')
return true
}
while (neededAmount >= sticksCount) {
const planksCount = getItemCount(mineflayer, 'planks')
const planksNeeded = Math.max(
Math.ceil((neededAmount - sticksCount) / STICKS_PER_PLANK),
4,
)
if (planksCount >= planksNeeded) {
try {
const sticksId = getItemId('stick')
const recipe = await mineflayer.bot.recipesFor(sticksId, null, 1, null)[0]
await mineflayer.bot.craft(recipe, neededAmount - sticksCount)
sticksCount = getItemCount(mineflayer, 'stick')
mineflayer.bot.chat(`I have made ${Math.abs(neededAmount - sticksCount)} sticks.`)
logger.log(`Bot: Sticks crafted.`)
}
catch (err) {
logger.withError(err).error('Bot: Failed to craft sticks.')
return false
}
}
else {
await ensurePlanks(mineflayer, planksNeeded)
logger.error('Bot: Not enough planks for sticks.')
}
}
return sticksCount >= neededAmount
}
// Ensure a specific number of chests
export async function ensureChests(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
logger.log(`Bot: Checking for ${quantity} chest(s)...`)
// Count the number of chests the bot already has
let chestCount = getItemCount(mineflayer, 'chest')
if (chestCount >= quantity) {
logger.log(`Bot: Already has ${quantity} or more chest(s).`)
return true
}
while (chestCount < quantity) {
const planksEnsured = await ensurePlanks(mineflayer, 8 * quantity) // 8 planks per chest
if (!planksEnsured) {
logger.error('Bot: Failed to ensure planks for chest(s).')
continue
}
// Craft the chest(s)
const crafted = await craftRecipe(mineflayer, 'chest', quantity - chestCount)
if (crafted) {
chestCount = getItemCount(mineflayer, 'chest')
mineflayer.bot.chat(`I have crafted ${quantity} chest(s).`)
logger.log(`Bot: ${quantity} chest(s) crafted.`)
continue
}
else {
logger.error('Bot: Failed to craft chest(s).')
}
}
return chestCount >= quantity
}
// Ensure a specific number of furnaces
export async function ensureFurnaces(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
logger.log(`Bot: Checking for ${quantity} furnace(s)...`)
// Count the number of furnaces the bot already has
let furnaceCount = getItemCount(mineflayer, 'furnace')
if (furnaceCount >= quantity) {
logger.log(`Bot: Already has ${quantity} or more furnace(s).`)
return true
}
while (furnaceCount < quantity) {
const stoneEnsured = await ensureCobblestone(mineflayer, 8 * (quantity - furnaceCount)) // 8 stone blocks per furnace
if (!stoneEnsured) {
logger.error('Bot: Failed to ensure stone for furnace(s).')
continue
}
// Craft the furnace(s)
const crafted = await craftRecipe(mineflayer, 'furnace', quantity - furnaceCount)
if (crafted) {
furnaceCount = getItemCount(mineflayer, 'furnace')
mineflayer.bot.chat(`I have crafted ${quantity} furnace(s).`)
logger.log(`Bot: ${quantity} furnace(s) crafted.`)
continue
}
else {
logger.error('Bot: Failed to craft furnace(s).')
}
}
return furnaceCount >= quantity
}
// Ensure a specific number of torches
export async function ensureTorches(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
logger.log(`Bot: Checking for ${quantity} torch(es)...`)
// Count the number of torches the bot already has
let torchCount = getItemCount(mineflayer, 'torch')
if (torchCount >= quantity) {
logger.log(`Bot: Already has ${quantity} or more torch(es).`)
return true
}
while (torchCount < quantity) {
const sticksEnsured = await ensureSticks(mineflayer, quantity - torchCount) // 1 stick per 4 torches
const coalEnsured = await ensureCoal(
mineflayer,
Math.ceil((quantity - torchCount) / 4),
) // 1 coal per 4 torches
if (!sticksEnsured || !coalEnsured) {
logger.error('Bot: Failed to ensure sticks or coal for torch(es).')
continue
}
// Craft the torch(es)
const crafted = await craftRecipe(mineflayer, 'torch', quantity - torchCount)
if (crafted) {
torchCount = getItemCount(mineflayer, 'torch')
mineflayer.bot.chat(`I have crafted ${quantity} torch(es).`)
logger.log(`Bot: ${quantity} torch(es) crafted.`)
continue
}
else {
logger.error('Bot: Failed to craft torch(es).')
}
}
return torchCount >= quantity
}
// Ensure a campfire
// Todo: rework
export async function ensureCampfire(mineflayer: Mineflayer): Promise<boolean> {
logger.log('Bot: Checking for a campfire...')
const hasCampfire = getItemCount(mineflayer, 'campfire') > 0
if (hasCampfire) {
logger.log('Bot: Campfire is already available.')
return true
}
const logsEnsured = await ensurePlanks(mineflayer, 3) // Need 3 logs for a campfire
const sticksEnsured = await ensureSticks(mineflayer, 3) // Need 3 sticks for a campfire
const coalEnsured = await ensureCoal(mineflayer, 1) // Need 1 coal or charcoal for a campfire
if (!logsEnsured || !sticksEnsured || !coalEnsured) {
logger.error('Bot: Failed to ensure resources for campfire.')
}
const crafted = await craftRecipe(mineflayer, 'campfire', 1)
if (crafted) {
mineflayer.bot.chat('I have crafted a campfire.')
logger.log('Bot: Campfire crafted.')
return true
}
else {
logger.error('Bot: Failed to craft campfire.')
}
return hasCampfire
}
// Helper function to gather cobblestone
export async function ensureCobblestone(mineflayer: Mineflayer, requiredCobblestone: number, maxDistance: number = 4): Promise<boolean> {
let cobblestoneCount = getItemCount(mineflayer, 'cobblestone')
while (cobblestoneCount < requiredCobblestone) {
logger.log('Bot: Gathering more cobblestone...')
const cobblestoneShortage = requiredCobblestone - cobblestoneCount
try {
const success = await collectBlock(
mineflayer,
'stone',
cobblestoneShortage,
maxDistance,
)
if (!success) {
await moveAway(mineflayer, 30)
continue
}
}
catch (err) {
if (err instanceof Error && err.message.includes('right tools')) {
await ensurePickaxe(mineflayer)
continue
}
else {
logger.withError(err).error('Error collecting cobblestone')
await moveAway(mineflayer, 30)
continue
}
}
cobblestoneCount = getItemCount(mineflayer, 'cobblestone')
}
logger.log('Bot: Collected enough cobblestone.')
return true
}
export async function ensureCoal(mineflayer: Mineflayer, neededAmount: number, maxDistance: number = 4): Promise<boolean> {
logger.log('Bot: Checking for coal...')
let coalCount = getItemCount(mineflayer, 'coal')
while (coalCount < neededAmount) {
logger.log('Bot: Gathering more coal...')
const coalShortage = neededAmount - coalCount
try {
await collectBlock(mineflayer, 'stone', coalShortage, maxDistance)
}
catch (err) {
if (err instanceof Error && err.message.includes('right tools')) {
await ensurePickaxe(mineflayer)
continue
}
else {
logger.withError(err).error('Error collecting cobblestone:')
moveAway(mineflayer, 30)
continue
}
}
coalCount = getItemCount(mineflayer, 'cobblestone')
}
logger.log('Bot: Collected enough cobblestone.')
return true
}
// Define the valid tool types as a union type
type ToolType = 'pickaxe' | 'sword' | 'axe' | 'shovel' | 'hoe'
// Define the valid materials as a union type
type MaterialType = 'diamond' | 'golden' | 'iron' | 'stone' | 'wooden'
// Constants for crafting tools
const TOOLS_MATERIALS: MaterialType[] = [
'diamond',
'golden',
'iron',
'stone',
'wooden',
]
export function materialsForTool(tool: ToolType): number {
switch (tool) {
case 'pickaxe':
case 'axe':
return 3
case 'sword':
case 'hoe':
return 2
case 'shovel':
return 1
default:
return 0
}
}
// Helper function to ensure a specific tool, checking from best materials to wood
async function ensureTool(mineflayer: Mineflayer, toolType: ToolType, quantity: number = 1): Promise<boolean> {
logger.log(`Bot: Checking for ${quantity} ${toolType}(s)...`)
const neededMaterials = materialsForTool(toolType)
// Check how many of the tool the bot currently has
let toolCount = mineflayer.bot.inventory
.items()
.filter(item => item.name.includes(toolType))
.length
if (toolCount >= quantity) {
logger.log(`Bot: Already has ${quantity} or more ${toolType}(s).`)
return true
}
while (toolCount < quantity) {
// Iterate over the tool materials from best (diamond) to worst (wooden)
for (const material of TOOLS_MATERIALS) {
const toolRecipe = `${material}_${toolType}` // Craft tool name like diamond_pickaxe, iron_sword
const hasResources = await hasResourcesForTool(mineflayer, material, neededMaterials)
// Check if we have enough material for the current tool
if (hasResources) {
await ensureCraftingTable(mineflayer)
const sticksEnsured = await ensureSticks(mineflayer, 2)
if (!sticksEnsured) {
logger.error(
`Bot: Failed to ensure planks or sticks for wooden ${toolType}.`,
)
continue
}
// Craft the tool
const crafted = await craftRecipe(mineflayer, toolRecipe, 1)
if (crafted) {
toolCount++
mineflayer.bot.chat(
`I have crafted a ${material} ${toolType}. Total ${toolType}(s): ${toolCount}/${quantity}`,
)
logger.log(
`Bot: ${material} ${toolType} crafted. Total ${toolCount}/${quantity}`,
)
if (toolCount >= quantity)
return true
}
else {
logger.error(`Bot: Failed to craft ${material} ${toolType}.`)
}
}
else if (material === 'wooden') {
// Crafting planks if we don't have enough resources for wooden tools
logger.log(`Bot: Crafting planks for ${material} ${toolType}...`)
await ensurePlanks(mineflayer, 4)
}
}
}
return toolCount >= quantity
}
// Helper function to check if the bot has enough materials to craft a tool of a specific material
export async function hasResourcesForTool(
mineflayer: Mineflayer,
material: MaterialType,
num = 3, // Number of resources needed for most tools
): Promise<boolean> {
switch (material) {
case 'diamond':
return getItemCount(mineflayer, 'diamond') >= num
case 'golden':
return getItemCount(mineflayer, 'gold_ingot') >= num
case 'iron':
return getItemCount(mineflayer, 'iron_ingot') >= num
case 'stone':
return getItemCount(mineflayer, 'cobblestone') >= num
case 'wooden':
return getItemCount(mineflayer, 'planks') >= num
default:
return false
}
}
// Helper functions for specific tools:
// Ensure a pickaxe
export async function ensurePickaxe(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
return await ensureTool(mineflayer, 'pickaxe', quantity)
};
// Ensure a sword
export async function ensureSword(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
return await ensureTool(mineflayer, 'sword', quantity)
};
// Ensure an axe
export async function ensureAxe(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
return await ensureTool(mineflayer, 'axe', quantity)
};
// Ensure a shovel
export async function ensureShovel(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
return await ensureTool(mineflayer, 'shovel', quantity)
};
export async function ensureHoe(mineflayer: Mineflayer, quantity: number = 1): Promise<boolean> {
return await ensureTool(mineflayer, 'hoe', quantity)
};
@@ -0,0 +1,100 @@
import type { Mineflayer } from '../../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import { getNearestBlocks } from '../../composables/world'
import { sleep } from '../../utils/helper'
import { breakBlockAt } from '../blocks'
import { goToPosition, moveAway } from '../movement'
import { pickupNearbyItems } from './world-interactions'
const logger = useLogg('Action:GatherWood').useGlobalConfig()
/**
* Gather wood blocks nearby to collect logs.
*
* @param mineflayer The mineflayer instance.
* @param num The number of wood logs to gather.
* @param maxDistance The maximum distance to search for wood blocks.
* @returns Whether the wood gathering was successful.
*/
export async function gatherWood(
mineflayer: Mineflayer,
num: number,
maxDistance = 64,
): Promise<boolean> {
logger.log(`Gathering wood... I need to collect ${num} logs.`)
mineflayer.bot.chat(`Gathering wood... I need to collect ${num} logs.`)
try {
let logsCount = getLogsCount(mineflayer)
logger.log(`I currently have ${logsCount} logs.`)
while (logsCount < num) {
// Gather 1 extra log to account for any failures
logger.log(`Looking for wood blocks nearby...`, logsCount, num)
const woodBlock = mineflayer.bot.findBlock({
matching: block => block.name.includes('log'),
maxDistance,
})
if (!woodBlock) {
logger.log('No wood blocks found nearby.')
await moveAway(mineflayer, 50)
continue
}
const destinationReached = await goToPosition(
mineflayer,
woodBlock.position.x,
woodBlock.position.y,
woodBlock.position.z,
2,
)
if (!destinationReached) {
logger.log('Unable to reach the wood block.')
continue // Try finding another wood block
}
const aTree = await getNearestBlocks(mineflayer, woodBlock.name, 4, 4)
if (aTree.length === 0) {
logger.log('No wood blocks found nearby.')
await moveAway(mineflayer, 15)
continue
}
try {
for (const aLog of aTree) {
await breakBlockAt(mineflayer, aLog.position.x, aLog.position.y, aLog.position.z)
await sleep(1200) // Simulate gathering delay
}
await pickupNearbyItems(mineflayer)
await sleep(2500)
logsCount = getLogsCount(mineflayer)
logger.log(`Collected logs. Total logs now: ${logsCount}.`)
}
catch (digError) {
console.error('Failed to break the wood block:', digError)
continue // Attempt to find and break another wood block
}
}
logger.log(`Wood gathering complete! Total logs collected: ${logsCount}.`)
return true
}
catch (error) {
console.error('Failed to gather wood:', error)
return false
}
}
/**
* Helper function to count the number of logs in the inventory.
* @returns The total number of logs.
*/
export function getLogsCount(mineflayer: Mineflayer): number {
return mineflayer.bot.inventory
.items()
.filter(item => item.name.includes('log'))
.reduce((acc, item) => acc + item.count, 0)
}
@@ -0,0 +1,303 @@
import type { Item } from 'prismarine-item'
import type { Mineflayer } from '../../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import { getNearestBlock } from '../../composables/world'
import { goToPlayer, goToPosition } from '../movement'
const logger = useLogg('Action:Inventory').useGlobalConfig()
/**
* Equip an item from the bot's inventory.
* @param mineflayer The mineflayer instance.
* @param itemName The name of the item to equip.
* @returns Whether the item was successfully equipped.
*/
export async function equip(mineflayer: Mineflayer, itemName: string): Promise<boolean> {
const item = mineflayer.bot.inventory
.items()
.find(item => item.name.includes(itemName))
if (!item) {
logger.log(`You do not have any ${itemName} to equip.`)
return false
}
let destination: 'hand' | 'head' | 'torso' | 'legs' | 'feet' = 'hand'
if (itemName.includes('leggings'))
destination = 'legs'
else if (itemName.includes('boots'))
destination = 'feet'
else if (itemName.includes('helmet'))
destination = 'head'
else if (itemName.includes('chestplate'))
destination = 'torso'
await mineflayer.bot.equip(item, destination)
return true
}
/**
* Discard an item from the bot's inventory.
* @param mineflayer The mineflayer instance.
* @param itemName The name of the item to discard.
* @param num The number of items to discard. Default is -1 for all.
* @returns Whether the item was successfully discarded.
*/
export async function discard(mineflayer: Mineflayer, itemName: string, num = -1): Promise<boolean> {
let discarded = 0
while (true) {
const item = mineflayer.bot.inventory
.items()
.find(item => item.name.includes(itemName))
if (!item) {
break
}
const toDiscard
= num === -1 ? item.count : Math.min(num - discarded, item.count)
await mineflayer.bot.toss(item.type, null, toDiscard)
discarded += toDiscard
if (num !== -1 && discarded >= num) {
break
}
}
if (discarded === 0) {
logger.log(`You do not have any ${itemName} to discard.`)
return false
}
logger.log(`Successfully discarded ${discarded} ${itemName}.`)
return true
}
export async function putInChest(mineflayer: Mineflayer, itemName: string, num = -1): Promise<boolean> {
const chest = getNearestBlock(mineflayer, 'chest', 32)
if (!chest) {
logger.log(`Could not find a chest nearby.`)
return false
}
const item = mineflayer.bot.inventory
.items()
.find(item => item.name.includes(itemName))
if (!item) {
logger.log(`You do not have any ${itemName} to put in the chest.`)
return false
}
const toPut = num === -1 ? item.count : Math.min(num, item.count)
await goToPosition(mineflayer, chest.position.x, chest.position.y, chest.position.z)
const chestContainer = await mineflayer.bot.openContainer(chest)
await chestContainer.deposit(item.type, null, toPut)
await chestContainer.close()
logger.log(`Successfully put ${toPut} ${itemName} in the chest.`)
return true
}
export async function takeFromChest(
mineflayer: Mineflayer,
itemName: string,
num = -1,
): Promise<boolean> {
const chest = getNearestBlock(mineflayer, 'chest', 32)
if (!chest) {
logger.log(`Could not find a chest nearby.`)
return false
}
await goToPosition(mineflayer, chest.position.x, chest.position.y, chest.position.z)
const chestContainer = await mineflayer.bot.openContainer(chest)
const item = chestContainer
.containerItems()
.find(item => item.name.includes(itemName))
if (!item) {
logger.log(`Could not find any ${itemName} in the chest.`)
await chestContainer.close()
return false
}
const toTake = num === -1 ? item.count : Math.min(num, item.count)
await chestContainer.withdraw(item.type, null, toTake)
await chestContainer.close()
logger.log(`Successfully took ${toTake} ${itemName} from the chest.`)
return true
}
/**
* View the contents of a chest near the bot.
* @param mineflayer The mineflayer instance.
* @returns Whether the chest was successfully viewed.
*/
export async function viewChest(mineflayer: Mineflayer): Promise<boolean> {
const chest = getNearestBlock(mineflayer, 'chest', 32)
if (!chest) {
logger.log(`Could not find a chest nearby.`)
return false
}
await goToPosition(mineflayer, chest.position.x, chest.position.y, chest.position.z)
const chestContainer = await mineflayer.bot.openContainer(chest)
const items = chestContainer.containerItems()
if (items.length === 0) {
logger.log(`The chest is empty.`)
}
else {
logger.log(`The chest contains:`)
for (const item of items) {
logger.log(`${item.count} ${item.name}`)
}
}
await chestContainer.close()
return true
}
/**
* Ask to bot to eat a food item from its inventory.
* @param mineflayer The mineflayer instance.
* @param foodName The name of the food item to eat.
* @returns Whether the food was successfully eaten.
*/
export async function eat(mineflayer: Mineflayer, foodName = ''): Promise<boolean> {
let item: Item | undefined
let name: string
if (foodName) {
item = mineflayer.bot.inventory.items().find(item => item.name.includes(foodName))
name = foodName
}
else {
// @ts-expect-error -- ?
item = mineflayer.bot.inventory.items().find(item => item.foodPoints > 0)
name = 'food'
}
if (!item) {
logger.log(`You do not have any ${name} to eat.`)
return false
}
await mineflayer.bot.equip(item, 'hand')
await mineflayer.bot.consume()
logger.log(`Successfully ate ${item.name}.`)
return true
}
/**
* Give an item to a player.
* @param mineflayer The mineflayer instance.
* @param itemType The name of the item to give.
* @param username The username of the player to give the item to.
* @param num The number of items to give.
* @returns Whether the item was successfully given.
*/
export async function giveToPlayer(
mineflayer: Mineflayer,
itemType: string,
username: string,
num = 1,
): Promise<boolean> {
const player = mineflayer.bot.players[username]?.entity
if (!player) {
logger.log(`Could not find a player with username: ${username}.`)
return false
}
await goToPlayer(mineflayer, username)
await mineflayer.bot.lookAt(player.position)
await discard(mineflayer, itemType, num)
return true
}
/**
* List the items in the bot's inventory.
* @param mineflayer The mineflayer instance.
* @returns An array of items in the bot's inventory.
*/
export async function listInventory(mineflayer: Mineflayer): Promise<{ name: string, count: number }[]> {
const items = await mineflayer.bot.inventory.items()
sayItems(mineflayer, items)
return items.map(item => ({
name: item.name,
count: item.count,
}))
}
export async function checkForItem(mineflayer: Mineflayer, itemName: string): Promise<void> {
const items = await mineflayer.bot.inventory.items()
const searchableItems = items.filter(item => item.name.includes(itemName))
sayItems(mineflayer, searchableItems)
}
export async function sayItems(mineflayer: Mineflayer, items: Array<Item> | null = null) {
if (!items) {
items = mineflayer.bot.inventory.items()
if (mineflayer.bot.registry.isNewerOrEqualTo('1.9') && mineflayer.bot.inventory.slots[45])
items.push(mineflayer.bot.inventory.slots[45])
}
const output = items.map(item => `${item.name} x ${item.count}`).join(', ')
if (output) {
mineflayer.bot.chat(`My inventory contains: ${output}`)
}
else {
mineflayer.bot.chat('My inventory is empty.`')
}
}
/**
* Find the number of free slots in the bot's inventory.
* @param mineflayer The mineflayer instance.
* @returns The number of free slots in the bot's inventory.
*/
export function checkFreeSpace(mineflayer: Mineflayer): number {
const totalSlots = mineflayer.bot.inventory.slots.length
const usedSlots = mineflayer.bot.inventory.items().length
const freeSlots = totalSlots - usedSlots
logger.log(`You have ${freeSlots} free slots in your inventory.`)
return freeSlots
}
/**
* Transfer all items from the bot's inventory to a chest.
* @param mineflayer The mineflayer instance.
* @returns Whether the items were successfully transferred.
*/
export async function transferAllToChest(mineflayer: Mineflayer): Promise<boolean> {
const chest = getNearestBlock(mineflayer, 'chest', 32)
if (!chest) {
logger.log(`Could not find a chest nearby.`)
return false
}
await goToPosition(mineflayer, chest.position.x, chest.position.y, chest.position.z)
const chestContainer = await mineflayer.bot.openContainer(chest)
for (const item of mineflayer.bot.inventory.items()) {
await chestContainer.deposit(item.type, null, item.count)
logger.log(`Put ${item.count} ${item.name} in the chest.`)
}
await chestContainer.close()
return true
}
/**
* Utility function to get item count in inventory
* @param mineflayer The mineflayer instance.
* @param itemName - The name of the item to count.
* @returns number of items in inventory
*/
export function getItemCount(mineflayer: Mineflayer, itemName: string): number {
return mineflayer.bot.inventory
.items()
.filter(item => item.name.includes(itemName))
.reduce((acc, item) => acc + item.count, 0)
}
/**
* Organize the bot's inventory.
* @param mineflayer The mineflayer instance.
* @returns Whether the inventory was successfully organized.
*/
export async function organizeInventory(mineflayer: Mineflayer): Promise<void> {
const items = mineflayer.bot.inventory.items()
if (items.length === 0) {
logger.log(`Inventory is empty, nothing to organize.`)
return
}
for (const item of items) {
await mineflayer.bot.moveSlotItem(
item.slot,
mineflayer.bot.inventory.findInventoryItem(item.type, null, false)?.slot ?? item.slot,
)
}
logger.log(`Inventory has been organized.`)
}
@@ -0,0 +1,372 @@
import type { Bot } from 'mineflayer'
import type { Block } from 'prismarine-block'
import type { Mineflayer } from '../../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import pathfinder from 'mineflayer-pathfinder'
import { Vec3 } from 'vec3'
import { sleep } from '../../utils/helper'
import { getNearestBlock, makeItem } from '../../utils/mcdata'
import { goToPosition } from '../movement'
const logger = useLogg('Action:WorldInteractions').useGlobalConfig()
export async function placeBlock(
mineflayer: Mineflayer,
blockType: string,
x: number,
y: number,
z: number,
placeOn: string = 'bottom',
): Promise<boolean> {
// if (!gameData.getBlockId(blockType)) {
// logger.log(`Invalid block type: ${blockType}.`);
// return false;
// }
const targetDest = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z))
let block = mineflayer.bot.inventory
.items()
.find(item => item.name.includes(blockType))
if (!block && mineflayer.bot.game.gameMode === 'creative') {
// TODO: Rework
await mineflayer.bot.creative.setInventorySlot(36, makeItem(blockType, 1)) // 36 is first hotbar slot
block = mineflayer.bot.inventory.items().find(item => item.name.includes(blockType))
}
if (!block) {
logger.log(`Don't have any ${blockType} to place.`)
return false
}
const targetBlock = mineflayer.bot.blockAt(targetDest)
if (!targetBlock) {
logger.log(`No block found at ${targetDest}.`)
return false
}
if (targetBlock.name === blockType) {
logger.log(`${blockType} already at ${targetBlock.position}.`)
return false
}
const emptyBlocks = [
'air',
'water',
'lava',
'grass',
'tall_grass',
'snow',
'dead_bush',
'fern',
]
if (!emptyBlocks.includes(targetBlock.name)) {
logger.log(
`${targetBlock.name} is in the way at ${targetBlock.position}.`,
)
const removed = await breakBlockAt(mineflayer, x, y, z)
if (!removed) {
logger.log(
`Cannot place ${blockType} at ${targetBlock.position}: block in the way.`,
)
return false
}
await new Promise(resolve => setTimeout(resolve, 200)) // Wait for block to break
}
// Determine the build-off block and face vector
const dirMap: { [key: string]: Vec3 } = {
top: new Vec3(0, 1, 0),
bottom: new Vec3(0, -1, 0),
north: new Vec3(0, 0, -1),
south: new Vec3(0, 0, 1),
east: new Vec3(1, 0, 0),
west: new Vec3(-1, 0, 0),
}
const dirs: Vec3[] = []
if (placeOn === 'side') {
dirs.push(dirMap.north, dirMap.south, dirMap.east, dirMap.west)
}
else if (dirMap[placeOn]) {
dirs.push(dirMap[placeOn])
}
else {
dirs.push(dirMap.bottom)
logger.log(`Unknown placeOn value "${placeOn}". Defaulting to bottom.`)
}
// Add remaining directions
dirs.push(...Object.values(dirMap).filter(d => !dirs.includes(d)))
let buildOffBlock: Block | null = null
let faceVec: Vec3 | null = null
for (const d of dirs) {
const adjacentBlock = mineflayer.bot.blockAt(targetDest.plus(d))
if (adjacentBlock && !emptyBlocks.includes(adjacentBlock.name)) {
buildOffBlock = adjacentBlock
faceVec = d.scaled(-1) // Invert direction
break
}
}
if (!buildOffBlock || !faceVec) {
logger.log(
`Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`,
)
return false
}
// Move away if too close
const pos = mineflayer.bot.entity.position
const posAbove = pos.offset(0, 1, 0)
const dontMoveFor = [
'torch',
'redstone_torch',
'redstone',
'lever',
'button',
'rail',
'detector_rail',
'powered_rail',
'activator_rail',
'tripwire_hook',
'tripwire',
'water_bucket',
]
if (
!dontMoveFor.includes(blockType)
&& (pos.distanceTo(targetBlock.position) < 1
|| posAbove.distanceTo(targetBlock.position) < 1)
) {
const goal = new pathfinder.goals.GoalInvert(
new pathfinder.goals.GoalNear(
targetBlock.position.x,
targetBlock.position.y,
targetBlock.position.z,
2,
),
)
// bot.pathfinder.setMovements(new pf.Movements(bot));
await mineflayer.bot.pathfinder.goto(goal)
}
// Move closer if too far
if (mineflayer.bot.entity.position.distanceTo(targetBlock.position) > 4.5) {
await goToPosition(
mineflayer,
targetBlock.position.x,
targetBlock.position.y,
targetBlock.position.z,
4,
)
}
await mineflayer.bot.equip(block, 'hand')
await mineflayer.bot.lookAt(buildOffBlock.position)
await sleep(500)
try {
await mineflayer.bot.placeBlock(buildOffBlock, faceVec)
logger.log(`Placed ${blockType} at ${targetDest}.`)
await new Promise(resolve => setTimeout(resolve, 200))
return true
}
catch (err) {
if (err instanceof Error) {
logger.log(
`Failed to place ${blockType} at ${targetDest}: ${err.message}`,
)
}
else {
logger.log(
`Failed to place ${blockType} at ${targetDest}: ${String(err)}`,
)
}
return false
}
}
export async function breakBlockAt(
mineflayer: Mineflayer,
x: number,
y: number,
z: number,
): Promise<boolean> {
if (x == null || y == null || z == null) {
throw new Error('Invalid position to break block at.')
}
const blockPos = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z))
const block = mineflayer.bot.blockAt(blockPos)
if (!block) {
logger.log(`No block found at position ${blockPos}.`)
return false
}
if (block.name !== 'air' && block.name !== 'water' && block.name !== 'lava') {
if (mineflayer.bot.entity.position.distanceTo(block.position) > 4.5) {
await goToPosition(mineflayer, x, y, z)
}
if (mineflayer.bot.game.gameMode !== 'creative') {
await mineflayer.bot.tool.equipForBlock(block)
const itemId = mineflayer.bot.heldItem ? mineflayer.bot.heldItem.type : null
if (!block.canHarvest(itemId)) {
logger.log(`Don't have right tools to break ${block.name}.`)
return false
}
}
if (!mineflayer.bot.canDigBlock(block)) {
logger.log(`Cannot break ${block.name} at ${blockPos}.`)
return false
}
await mineflayer.bot.lookAt(block.position, true) // Ensure the bot has finished turning
await sleep(500)
try {
await mineflayer.bot.dig(block, true)
logger.log(
`Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(
1,
)}, z:${z.toFixed(1)}.`,
)
return true
}
catch (err) {
console.error(`Failed to dig the block: ${err}`)
return false
}
}
else {
logger.log(
`Skipping block at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(
1,
)} because it is ${block.name}.`,
)
return false
}
}
export async function activateNearestBlock(mineflayer: Mineflayer, type: string) {
/**
* Activate the nearest block of the given type.
* @param {string} type, the type of block to activate.
* @returns {Promise<boolean>} true if the block was activated, false otherwise.
* @example
* await skills.activateNearestBlock( "lever");
*
*/
const block = getNearestBlock(mineflayer.bot, type, 16)
if (!block) {
logger.log(`Could not find any ${type} to activate.`)
return false
}
if (mineflayer.bot.entity.position.distanceTo(block.position) > 4.5) {
const pos = block.position
// bot.pathfinder.setMovements(new pf.Movements(bot));
await mineflayer.bot.pathfinder.goto(new pathfinder.goals.GoalNear(pos.x, pos.y, pos.z, 4))
}
await mineflayer.bot.activateBlock(block)
logger.log(
`Activated ${type} at x:${block.position.x.toFixed(
1,
)}, y:${block.position.y.toFixed(1)}, z:${block.position.z.toFixed(1)}.`,
)
return true
}
export async function tillAndSow(
mineflayer: Mineflayer,
x: number,
y: number,
z: number,
seedType: string | null = null,
): Promise<boolean> {
x = Math.round(x)
y = Math.round(y)
z = Math.round(z)
const blockPos = new Vec3(x, y, z)
const block = mineflayer.bot.blockAt(blockPos)
if (!block) {
logger.log(`No block found at ${blockPos}.`)
return false
}
if (
block.name !== 'grass_block'
&& block.name !== 'dirt'
&& block.name !== 'farmland'
) {
logger.log(`Cannot till ${block.name}, must be grass_block or dirt.`)
return false
}
const above = mineflayer.bot.blockAt(blockPos.offset(0, 1, 0))
if (above && above.name !== 'air') {
logger.log(`Cannot till, there is ${above.name} above the block.`)
return false
}
// Move closer if too far
if (mineflayer.bot.entity.position.distanceTo(block.position) > 4.5) {
await goToPosition(mineflayer, x, y, z, 4)
}
if (block.name !== 'farmland') {
const hoe = mineflayer.bot.inventory.items().find(item => item.name.includes('hoe'))
if (!hoe) {
logger.log(`Cannot till, no hoes.`)
return false
}
await mineflayer.bot.equip(hoe, 'hand')
await mineflayer.bot.activateBlock(block)
logger.log(
`Tilled block x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`,
)
}
if (seedType) {
if (seedType.endsWith('seed') && !seedType.endsWith('seeds'))
seedType += 's' // Fixes common mistake
const seeds = mineflayer.bot.inventory
.items()
.find(item => item.name.includes(seedType || 'seed'))
if (!seeds) {
logger.log(`No ${seedType} to plant.`)
return false
}
await mineflayer.bot.equip(seeds, 'hand')
await mineflayer.bot.placeBlock(block, new Vec3(0, -1, 0))
logger.log(
`Planted ${seedType} at x:${x.toFixed(1)}, y:${y.toFixed(
1,
)}, z:${z.toFixed(1)}.`,
)
}
return true
}
export async function pickupNearbyItems(
mineflayer: Mineflayer,
distance = 8,
): Promise<boolean> {
const getNearestItem = (bot: Bot) =>
bot.nearestEntity(
entity =>
entity.name === 'item'
&& entity.onGround
&& bot.entity.position.distanceTo(entity.position) < distance,
)
let nearestItem = getNearestItem(mineflayer.bot)
let pickedUp = 0
while (nearestItem) {
// bot.pathfinder.setMovements(new pf.Movements(bot));
await mineflayer.bot.pathfinder.goto(
new pathfinder.goals.GoalFollow(nearestItem, 0.8),
() => {},
)
await sleep(500)
const prev = nearestItem
nearestItem = getNearestItem(mineflayer.bot)
if (prev === nearestItem) {
break
}
pickedUp++
}
logger.log(`Picked up ${pickedUp} items.`)
return true
}
+230 -168
View File
@@ -1,281 +1,343 @@
import type { Block } from 'prismarine-block'
import type { Item } from 'prismarine-item'
import type { Recipe } from 'prismarine-recipe'
import type { Mineflayer } from '../libs/mineflayer'
import { useLogg } from '@guiiai/logg'
import * as world from '../composables/world'
import { getInventoryCounts, getNearestBlock, getNearestFreeSpace } from '../composables/world'
import * as mc from '../utils/mcdata'
import { log } from './base'
import { ensureCraftingTable } from './actions/ensure'
import { collectBlock, placeBlock } from './blocks'
import { goToPosition } from './movement'
import { goToNearestBlock, goToPosition, moveAway } from './movement'
export async function craftRecipe(mineflayer: Mineflayer, itemName: string, num = 1): Promise<boolean> {
let placedTable = false
const logger = useLogg('Skill:Crafting').useGlobalConfig()
if (mc.getItemCraftingRecipes(itemName)?.length === 0) {
log(mineflayer, `${itemName} is either not an item, or it does not have a crafting recipe!`)
return false
}
/*
Possible Scenarios:
1. **Successful Craft Without Crafting Table**:
- The bot attempts to craft the item without a crafting table and succeeds. The function returns `true`.
2. **Crafting Table Nearby**:
- The bot tries to craft without a crafting table but fails.
- The bot then checks for a nearby crafting table.
- If a crafting table is found, the bot moves to it and successfully crafts the item, returning `true`.
3. **No Crafting Table Nearby, Place Crafting Table**:
- The bot fails to craft without a crafting table and does not find a nearby crafting table.
- The bot checks inventory for a crafting table, places it at a suitable location, and attempts crafting again.
- If successful, the function returns `true`. If the bot cannot find a suitable position or fails to craft, it returns `false`.
4. **Insufficient Resources**:
- At any point, if the bot does not have the required resources to craft the item, it logs an appropriate message and returns `false`.
5. **No Crafting Table and No Suitable Position**:
- If the bot does not find a crafting table and cannot find a suitable position to place one, it moves away and returns `false`.
6. **Invalid Item Name**:
- If the provided item name is invalid, the function logs the error and returns `false`.
*/
export async function craftRecipe(
mineflayer: Mineflayer,
incomingItemName: string,
num = 1,
): Promise<boolean> {
let itemName = incomingItemName.replace(' ', '_').toLowerCase()
if (itemName.endsWith('plank'))
itemName += 's' // Correct common mistakes
// Get recipes that don't require a crafting table
const itemId = mc.getItemId(itemName)
if (itemId === null) {
log(mineflayer, `Invalid item name: ${itemName}`)
logger.log(`Invalid item name: ${itemName}`)
return false
}
let recipes = mineflayer.bot.recipesFor(itemId, null, 1, null)
let craftingTable = null
const craftingTableRange = 32
if (!recipes || recipes.length === 0) {
recipes = mineflayer.bot.recipesFor(itemId, null, 1, true)
if (!recipes || recipes.length === 0) {
log(mineflayer, `You do not have the resources to craft a ${itemName}.`)
return false
}
// Look for crafting table
craftingTable = world.getNearestBlock(mineflayer, 'crafting_table', craftingTableRange)
if (!craftingTable) {
// Try to place crafting table
const inventory = world.getInventoryCounts(mineflayer)
const hasTable = inventory.crafting_table > 0
if (hasTable) {
const pos = world.getNearestFreeSpace(mineflayer, 1, 6)
if (pos) {
await placeBlock(mineflayer, 'crafting_table', pos.x, pos.y, pos.z)
craftingTable = world.getNearestBlock(mineflayer, 'crafting_table', craftingTableRange)
if (craftingTable) {
recipes = mineflayer.bot.recipesFor(itemId, null, 1, craftingTable)
placedTable = true
}
}
// Helper function to attempt crafting
async function attemptCraft(
recipes: Recipe[] | null,
craftingTable: Block | null = null,
): Promise<boolean> {
if (recipes && recipes.length > 0) {
const recipe = recipes[0]
try {
await mineflayer.bot.craft(recipe, num, craftingTable ?? undefined)
logger.log(
`Successfully crafted ${num} ${itemName}${
craftingTable ? ' using crafting table' : ''
}.`,
)
return true
}
else {
log(mineflayer, `Crafting ${itemName} requires a crafting table.`)
catch (err) {
logger.log(`Failed to craft ${itemName}: ${(err as Error).message}`)
return false
}
}
else {
recipes = mineflayer.bot.recipesFor(itemId, null, 1, craftingTable)
}
}
if (!recipes || recipes.length === 0) {
log(mineflayer, `You do not have the resources to craft a ${itemName}. It requires: ${
Object.entries(mc.getItemCraftingRecipes(itemName)?.[0] ?? {})
.map(([key, value]) => `${key}: ${value}`)
.join(', ')
}.`)
if (placedTable && craftingTable) {
await collectBlock(mineflayer, 'crafting_table', 1)
}
return false
}
if (craftingTable && mineflayer.bot.entity.position.distanceTo(craftingTable.position) > 4) {
await goToPosition(mineflayer, craftingTable.position.x, craftingTable.position.y, craftingTable.position.z, 4)
// Helper function to move to a crafting table and attempt crafting with retry logic
async function moveToAndCraft(craftingTable: Block): Promise<boolean> {
logger.log(`Crafting table found, moving to it.`)
const maxRetries = 2
let attempts = 0
let success = false
while (attempts < maxRetries && !success) {
try {
await goToPosition(
mineflayer,
craftingTable.position.x,
craftingTable.position.y,
craftingTable.position.z,
1,
)
const recipes = mineflayer.bot.recipesFor(itemId, null, 1, craftingTable)
success = await attemptCraft(recipes, craftingTable)
}
catch (err) {
logger.log(
`Attempt ${attempts + 1} to move to crafting table failed: ${
(err as Error).message
}`,
)
}
attempts++
}
return success
}
const recipe = recipes[0]
// Check that the agent has sufficient items to use the recipe `num` times
const inventory = world.getInventoryCounts(mineflayer) // Items in the agents inventory
const requiredIngredients = mc.ingredientsFromPrismarineRecipe(recipe) // Items required to use the recipe once
const craftLimit = mc.calculateLimitingResource(inventory, requiredIngredients)
// Helper function to find and use or place a crafting table
async function findAndUseCraftingTable(
craftingTableRange: number,
): Promise<boolean> {
let craftingTable = world.getNearestBlock(mineflayer, 'crafting_table', craftingTableRange)
if (craftingTable) {
return await moveToAndCraft(craftingTable)
}
await mineflayer.bot.craft(recipe, Math.min(craftLimit.num, num), craftingTable ?? undefined)
logger.log(`No crafting table nearby, attempting to place one.`)
const hasCraftingTable = await ensureCraftingTable(mineflayer)
if (!hasCraftingTable) {
logger.log(`Failed to ensure a crafting table to craft ${itemName}.`)
return false
}
if (craftLimit.num < num) {
log(mineflayer, `Not enough ${craftLimit.limitingResource} to craft ${num}, crafted ${craftLimit.num}. You now have ${world.getInventoryCounts(mineflayer)[itemName]} ${itemName}.`)
}
else {
log(mineflayer, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(mineflayer)[itemName]} ${itemName}.`)
const pos = getNearestFreeSpace(mineflayer, 1, 10)
if (pos) {
moveAway(mineflayer, 4)
logger.log(
`Placing crafting table at position (${pos.x}, ${pos.y}, ${pos.z}).`,
)
await placeBlock(mineflayer, 'crafting_table', pos.x, pos.y, pos.z)
craftingTable = getNearestBlock(mineflayer, 'crafting_table', craftingTableRange)
if (craftingTable) {
return await moveToAndCraft(craftingTable)
}
}
else {
logger.log('No suitable position found to place the crafting table.')
moveAway(mineflayer, 5)
return false
}
return false
}
if (placedTable && craftingTable) {
await collectBlock(mineflayer, 'crafting_table', 1)
// Step 1: Try to craft without a crafting table
logger.log(`Step 1: Try to craft without a crafting table`)
const recipes = mineflayer.bot.recipesFor(itemId, null, 1, null)
if (recipes && (await attemptCraft(recipes))) {
return true
}
// Equip any armor the bot may have crafted
mineflayer.bot.armorManager.equipAll()
// Step 2: Find and use a crafting table
logger.log(`Step 2: Find and use a crafting table`)
const craftingTableRange = 32
if (await findAndUseCraftingTable(craftingTableRange)) {
return true
}
return true
return false
}
export async function smeltItem(mineflayer: Mineflayer, itemName: string, num = 1): Promise<boolean> {
if (!mc.isSmeltable(itemName)) {
log(mineflayer, `Cannot smelt ${itemName}. Hint: make sure you are smelting the 'raw' item.`)
const foods = [
'beef',
'chicken',
'cod',
'mutton',
'porkchop',
'rabbit',
'salmon',
'tropical_fish',
]
if (!itemName.includes('raw') && !foods.includes(itemName)) {
logger.log(
`Cannot smelt ${itemName}, must be a "raw" item, like "raw_iron".`,
)
return false
}
} // TODO: allow cobblestone, sand, clay, etc.
let placedFurnace = false
const furnaceRange = 32
let furnaceBlock = world.getNearestBlock(mineflayer, 'furnace', furnaceRange)
let furnaceBlock = getNearestBlock(mineflayer, 'furnace', 32)
if (!furnaceBlock) {
// Try to place furnace
const inventory = world.getInventoryCounts(mineflayer)
const hasFurnace = inventory.furnace > 0
const hasFurnace = getInventoryCounts(mineflayer).furnace > 0
if (hasFurnace) {
const pos = world.getNearestFreeSpace(mineflayer, 1, furnaceRange)
const pos = getNearestFreeSpace(mineflayer, 1, 32)
if (pos) {
await placeBlock(mineflayer, 'furnace', pos.x, pos.y, pos.z)
furnaceBlock = world.getNearestBlock(mineflayer, 'furnace', furnaceRange)
placedFurnace = true
}
else {
logger.log('No suitable position found to place the furnace.')
return false
}
furnaceBlock = getNearestBlock(mineflayer, 'furnace', 32)
placedFurnace = true
}
}
if (!furnaceBlock) {
log(mineflayer, 'There is no furnace nearby and you have no furnace.')
logger.log(`There is no furnace nearby and I have no furnace.`)
return false
}
if (mineflayer.bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
await goToPosition(mineflayer, furnaceBlock.position.x, furnaceBlock.position.y, furnaceBlock.position.z, 4)
await goToNearestBlock(mineflayer, 'furnace', 4, 32)
}
await mineflayer.bot.lookAt(furnaceBlock.position)
logger.log('smelting...')
const furnace = await mineflayer.bot.openFurnace(furnaceBlock)
// Check if the furnace is already smelting something
const inputItem = furnace.inputItem()
const itemId = mc.getItemId(itemName)
if (itemId === null) {
log(mineflayer, `Invalid item name: ${itemName}`)
return false
}
if (inputItem && inputItem.type !== itemId && inputItem.count > 0) {
log(mineflayer, `The furnace is currently smelting ${mc.getItemName(inputItem.type) ?? 'unknown'}.`)
if (placedFurnace) {
if (
inputItem
&& inputItem.type !== mc.getItemId(itemName)
&& inputItem.count > 0
) {
logger.log(
`The furnace is currently smelting ${mc.getItemName(
inputItem.type,
)}.`,
)
if (placedFurnace)
await collectBlock(mineflayer, 'furnace', 1)
}
return false
}
// Check if the bot has enough items to smelt
const invCounts = world.getInventoryCounts(mineflayer)
const invCounts = getInventoryCounts(mineflayer)
if (!invCounts[itemName] || invCounts[itemName] < num) {
log(mineflayer, `You do not have enough ${itemName} to smelt.`)
if (placedFurnace) {
logger.log(`I do not have enough ${itemName} to smelt.`)
if (placedFurnace)
await collectBlock(mineflayer, 'furnace', 1)
}
return false
}
// Fuel the furnace
if (!furnace.fuelItem()) {
const fuel = mc.getSmeltingFuel(mineflayer.bot)
if (!fuel) {
log(mineflayer, `You have no fuel to smelt ${itemName}, you need coal, charcoal, or wood.`)
if (placedFurnace) {
const fuel = mineflayer.bot.inventory
.items()
.find(item => item.name === 'coal' || item.name === 'charcoal')
const putFuel = Math.ceil(num / 8)
if (!fuel || fuel.count < putFuel) {
logger.log(
`I do not have enough coal or charcoal to smelt ${num} ${itemName}, I need ${putFuel} coal or charcoal`,
)
if (placedFurnace)
await collectBlock(mineflayer, 'furnace', 1)
}
return false
}
log(mineflayer, `Using ${fuel.name} as fuel.`)
const putFuel = Math.ceil(num / mc.getFuelSmeltOutput(fuel.name))
if (fuel.count < putFuel) {
log(mineflayer, `You don't have enough ${fuel.name} to smelt ${num} ${itemName}; you need ${putFuel}.`)
if (placedFurnace) {
await collectBlock(mineflayer, 'furnace', 1)
}
return false
}
await furnace.putFuel(fuel.type, null, putFuel)
log(mineflayer, `Added ${putFuel} ${mc.getItemName(fuel.type) ?? 'unknown'} to furnace fuel.`)
logger.log(
`Added ${putFuel} ${mc.getItemName(fuel.type)} to furnace fuel.`,
)
}
// Put the items in the furnace
const itemId = mc.getItemId(itemName)
if (itemId === null) {
logger.log(`Invalid item name: ${itemName}`)
return false
}
await furnace.putInput(itemId, null, num)
// Wait for the items to smelt
let total = 0
let collectedLast = true
let smeltedItem = null
let smeltedItem: Item | null = null
await new Promise(resolve => setTimeout(resolve, 200))
mineflayer.once('interrupt', () => {
total = num // Force loop to end
})
while (total < num) {
await new Promise(resolve => setTimeout(resolve, 10000))
logger.log('checking...')
let collected = false
const outputItem = furnace.outputItem()
if (outputItem) {
if (furnace.outputItem()) {
smeltedItem = await furnace.takeOutput()
if (smeltedItem) {
total += smeltedItem.count
collected = true
}
}
if (!collected && !collectedLast) {
break // If nothing was collected this time or last time
break // if nothing was collected this time or last time
}
collectedLast = collected
}
await mineflayer.bot.closeWindow(furnace)
if (placedFurnace) {
await collectBlock(mineflayer, 'furnace', 1)
}
if (total === 0) {
log(mineflayer, `Failed to smelt ${itemName}.`)
logger.log(`Failed to smelt ${itemName}.`)
return false
}
if (total < num) {
log(mineflayer, `Only smelted ${total} ${mc.getItemName(smeltedItem?.type ?? 0) ?? 'unknown'}.`)
logger.log(
`Only smelted ${total} ${mc.getItemName(smeltedItem?.type || 0)}.`,
)
return false
}
log(mineflayer, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smeltedItem?.type ?? 0) ?? 'unknown'}.`)
logger.log(
`Successfully smelted ${itemName}, got ${total} ${mc.getItemName(
smeltedItem?.type || 0,
)}.`,
)
return true
}
export async function clearNearestFurnace(mineflayer: Mineflayer): Promise<boolean> {
const furnaceBlock = world.getNearestBlock(mineflayer, 'furnace', 32)
const furnaceBlock = getNearestBlock(mineflayer, 'furnace', 6)
if (!furnaceBlock) {
log(mineflayer, 'No furnace nearby to clear.')
logger.log(`There is no furnace nearby.`)
return false
}
if (mineflayer.bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
await goToPosition(mineflayer, furnaceBlock.position.x, furnaceBlock.position.y, furnaceBlock.position.z, 4)
}
logger.log('clearing furnace...')
const furnace = await mineflayer.bot.openFurnace(furnaceBlock)
logger.log('opened furnace...')
// Take the items out of the furnace
let smeltedItem, inputItem, fuelItem
const outputItem = furnace.outputItem()
if (outputItem) {
let smeltedItem: Item | null = null
let inputItem: Item | null = null
let fuelItem: Item | null = null
if (furnace.outputItem())
smeltedItem = await furnace.takeOutput()
}
const furnaceInput = furnace.inputItem()
if (furnaceInput) {
if (furnace.inputItem())
inputItem = await furnace.takeInput()
}
const furnaceFuel = furnace.fuelItem()
if (furnaceFuel) {
if (furnace.fuelItem())
fuelItem = await furnace.takeFuel()
}
const smeltedName = smeltedItem ? `${smeltedItem.count} ${smeltedItem.name}` : '0 smelted items'
const inputName = inputItem ? `${inputItem.count} ${inputItem.name}` : '0 input items'
const fuelName = fuelItem ? `${fuelItem.count} ${fuelItem.name}` : '0 fuel items'
log(mineflayer, `Cleared furnace, received ${smeltedName}, ${inputName}, and ${fuelName}.`)
logger.log(smeltedItem, inputItem, fuelItem)
const smeltedName = smeltedItem
? `${smeltedItem.count} ${smeltedItem.name}`
: `0 smelted items`
const inputName = inputItem
? `${inputItem.count} ${inputItem.name}`
: `0 input items`
const fuelName = fuelItem
? `${fuelItem.count} ${fuelItem.name}`
: `0 fuel items`
logger.log(
`Cleared furnace, received ${smeltedName}, ${inputName}, and ${fuelName}.`,
)
await mineflayer.bot.closeWindow(furnace)
return true
}
@@ -1,39 +1,8 @@
import type { Bot } from 'mineflayer'
import type { Mineflayer } from '../libs/mineflayer'
import pathfinderModel from 'mineflayer-pathfinder'
import * as world from '../composables/world'
import { log } from './base'
import { goToPlayer, goToPosition } from './movement'
const { goals } = pathfinderModel
export async function pickupNearbyItems(mineflayer: Mineflayer): Promise<boolean> {
const distance = 8
const getNearestItem = (bot: Bot) =>
bot.nearestEntity(entity =>
entity.name === 'item'
&& bot.entity.position.distanceTo(entity.position) < distance,
)
let nearestItem = getNearestItem(mineflayer.bot)
let pickedUp = 0
while (nearestItem) {
await mineflayer.bot.pathfinder.goto(new goals.GoalFollow(nearestItem, 0.8))
await new Promise(resolve => setTimeout(resolve, 200))
const prev = nearestItem
nearestItem = getNearestItem(mineflayer.bot)
if (prev === nearestItem) {
break
}
pickedUp++
}
log(mineflayer, `Picked up ${pickedUp} items.`)
return true
}
export async function equip(mineflayer: Mineflayer, itemName: string): Promise<boolean> {
const item = mineflayer.bot.inventory.slots.find(slot => slot && slot.name === itemName)
if (!item) {
+40 -20
View File
@@ -1,12 +1,16 @@
import type { Entity } from 'prismarine-entity'
import type { Mineflayer } from '../libs/mineflayer'
import pathfinderModel from 'mineflayer-pathfinder'
import { useLogg } from '@guiiai/logg'
import { randomInt } from 'es-toolkit'
import pathfinder from 'mineflayer-pathfinder'
import { Vec3 } from 'vec3'
import * as world from '../composables/world'
import { sleep } from '../utils/helper'
import { log } from './base'
const { goals, Movements } = pathfinderModel
const logger = useLogg('Skill:Movement').useGlobalConfig()
const { goals, Movements } = pathfinder
export async function goToPosition(
mineflayer: Mineflayer,
@@ -152,28 +156,44 @@ export async function followPlayer(
}
export async function moveAway(mineflayer: Mineflayer, distance: number): Promise<boolean> {
const pos = mineflayer.bot.entity.position
const goal = new goals.GoalNear(pos.x, pos.y, pos.z, distance)
const invertedGoal = new goals.GoalInvert(goal)
try {
const pos = mineflayer.bot.entity.position
let newX: number = 0
let newZ: number = 0
let suitableGoal = false
if (mineflayer.allowCheats) {
const move = new Movements(mineflayer.bot)
const path = await mineflayer.bot.pathfinder.getPathTo(move, invertedGoal, 10000)
const lastMove = path.path[path.path.length - 1]
while (!suitableGoal) {
const rand1 = randomInt(0, 2)
const rand2 = randomInt(0, 2)
const bigRand1 = randomInt(0, 101)
const bigRand2 = randomInt(0, 101)
if (lastMove) {
const x = Math.floor(lastMove.x)
const y = Math.floor(lastMove.y)
const z = Math.floor(lastMove.z)
mineflayer.bot.chat(`/tp @s ${x} ${y} ${z}`)
return true
newX = Math.floor(
pos.x + ((distance * bigRand1) / 100) * (rand1 ? 1 : -1),
)
newZ = Math.floor(
pos.z + ((distance * bigRand2) / 100) * (rand2 ? 1 : -1),
)
const block = mineflayer.bot.blockAt(new Vec3(newX, pos.y - 1, newZ))
if (block?.name !== 'water' && block?.name !== 'lava') {
suitableGoal = true
}
}
}
await mineflayer.bot.pathfinder.goto(invertedGoal)
const newPos = mineflayer.bot.entity.position
log(mineflayer, `Moved away from nearest entity to ${newPos}.`)
return true
const farGoal = new pathfinder.goals.GoalXZ(newX, newZ)
await mineflayer.bot.pathfinder.goto(farGoal)
const newPos = mineflayer.bot.entity.position
logger.log(`Moved away from nearest entity to ${newPos}.`)
await sleep(500)
return true
}
catch (err) {
logger.log(`Failed to move away: ${(err as Error).message}`)
return false
}
}
export async function moveAwayFromEntity(
+114 -142
View File
@@ -1,23 +1,28 @@
/**
* @source https://github.com/kolbytn/mindcraft
*/
// src/utils/minecraftData.ts
import type { Bot } from 'mineflayer'
import minecraftData from 'minecraft-data'
import prismarine_items from 'prismarine-item'
import { botConfig } from '../composables/config'
import type { Entity } from 'prismarine-entity'
import minecraftData, {
type Biome,
type ShapedRecipe,
type ShapelessRecipe,
} from 'minecraft-data'
import prismarineItem from 'prismarine-item'
const mc_version = botConfig.version!
const mcdata = minecraftData(mc_version)
const Item = prismarine_items(mc_version)
const GAME_VERSION = '1.20'
interface MinecraftRecipe {
result: { id: number, count: number }
inShape?: Array<Array<{ id: number, count: number }>>
ingredients?: Array<{ id: number, count: number }>
requiresTable?: boolean
}
export const gameData = minecraftData(GAME_VERSION)
export const Item = prismarineItem(GAME_VERSION)
export const WOOD_TYPES: string[] = [
'oak',
'spruce',
'birch',
'jungle',
'acacia',
'dark_oak',
]
export const WOOD_TYPES: string[] = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak']
export const MATCHING_WOOD_BLOCKS: string[] = [
'log',
'planks',
@@ -32,6 +37,7 @@ export const MATCHING_WOOD_BLOCKS: string[] = [
'pressure_plate',
'trapdoor',
]
export const WOOL_COLORS: string[] = [
'white',
'orange',
@@ -51,55 +57,56 @@ export const WOOL_COLORS: string[] = [
'black',
]
export function isHuntable(mob: { name?: string, metadata: any[] }): boolean {
export function isHuntable(mob: Entity): 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
const animals: string[] = [
'chicken',
'cow',
'llama',
'mooshroom',
'pig',
'rabbit',
'sheep',
]
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16] // metadata[16] indicates baby status
}
export function isHostile(mob: { name?: string, type?: string }): boolean {
export function isHostile(mob: Entity): boolean {
if (!mob || !mob.name)
return false
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem'
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 getItemId(itemName: string): number {
const item = gameData.itemsByName[itemName]
return item?.id || 0
}
export function getItemName(itemId: number): string | null {
const item = mcdata.items[itemId]
if (item) {
return item.name
}
return null
export function getItemName(itemId: number): string {
const item = gameData.items[itemId]
return item.name || ''
}
export function getBlockId(blockName: string): number | null {
const block = mcdata.blocksByName[blockName]
if (block) {
return block.id
}
return null
export function getBlockId(blockName: string): number {
const block = gameData.blocksByName?.[blockName]
return block?.id || 0
}
export function getBlockName(blockId: number): string | null {
const block = mcdata.blocks[blockId]
if (block) {
return block.name
}
return null
export function getBlockName(blockId: number): string {
const block = gameData.blocks[blockId]
return block.name || ''
}
export function getAllItems(ignore: string[] = []): any[] {
const items = []
for (const itemId in mcdata.items) {
const item = mcdata.items[itemId]
const items: any[] = []
for (const itemId in gameData.items) {
const item = gameData.items[itemId]
if (!ignore.includes(item.name)) {
items.push(item)
}
@@ -109,7 +116,7 @@ export function getAllItems(ignore: string[] = []): any[] {
export function getAllItemIds(ignore: string[] = []): number[] {
const items = getAllItems(ignore)
const itemIds = []
const itemIds: number[] = []
for (const item of items) {
itemIds.push(item.id)
}
@@ -117,9 +124,9 @@ export function getAllItemIds(ignore: string[] = []): number[] {
}
export function getAllBlocks(ignore: string[] = []): any[] {
const blocks = []
for (const blockId in mcdata.blocks) {
const block = mcdata.blocks[blockId]
const blocks: any[] = []
for (const blockId in gameData.blocks) {
const block = gameData.blocks[blockId]
if (!ignore.includes(block.name)) {
blocks.push(block)
}
@@ -129,76 +136,67 @@ export function getAllBlocks(ignore: string[] = []): any[] {
export function getAllBlockIds(ignore: string[] = []): number[] {
const blocks = getAllBlocks(ignore)
const blockIds = []
const blockIds: number[] = []
for (const block of blocks) {
blockIds.push(block.id)
}
return blockIds
}
export function getAllBiomes(): any {
return mcdata.biomes
export function getAllBiomes(): Record<number, Biome> {
return gameData.biomes
}
export function getItemCraftingRecipes(itemName: string): Record<string, number>[] | null {
export function getItemCraftingRecipes(itemName: string): any[] | null {
const itemId = getItemId(itemName)
if (!itemId || !mcdata.recipes[itemId]) {
if (!itemId || !gameData.recipes[itemId]) {
return null
}
const recipes: Record<string, number>[] = []
for (const r of mcdata.recipes[itemId] as MinecraftRecipe[]) {
for (const r of gameData.recipes[itemId]) {
const recipe: Record<string, number> = {}
let ingredients: Array<{ id: number, count: number }> = []
let ingredients: number[] = []
if (r.ingredients) {
ingredients = r.ingredients
if (isShapelessRecipe(r)) {
// Handle shapeless recipe
ingredients = r.ingredients.map((ing: any) => ing.id)
}
else if (r.inShape) {
ingredients = r.inShape.flat()
else if (isShapedRecipe(r)) {
// Handle shaped recipe
ingredients = r.inShape
.flat()
.map((ing: any) => ing?.id)
.filter(Boolean)
}
for (const ingredient of ingredients) {
const ingredientName = getItemName(ingredient.id)
for (const ingredientId of ingredients) {
const ingredientName = getItemName(ingredientId)
if (ingredientName === null)
continue
recipe[ingredientName] ??= 0
recipe[ingredientName] += ingredient.count
if (!recipe[ingredientName])
recipe[ingredientName] = 0
recipe[ingredientName]++
}
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)
// Type guards
function isShapelessRecipe(recipe: any): recipe is ShapelessRecipe {
return 'ingredients' in recipe
}
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')
function isShapedRecipe(recipe: any): recipe is ShapedRecipe {
return 'inShape' in recipe
}
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 {
export function getItemSmeltingIngredient(
itemName: string,
): string | undefined {
return {
baked_potato: 'potato',
steak: 'raw_beef',
@@ -219,8 +217,10 @@ export function getItemSmeltingIngredient(itemName: string): string | undefined
export function getItemBlockSources(itemName: string): string[] {
const itemId = getItemId(itemName)
const sources: string[] = []
if (!itemId)
return sources
for (const block of getAllBlocks()) {
if (block.drops.includes(itemId)) {
if (block.drops && block.drops.includes(itemId)) {
sources.push(block.name)
}
}
@@ -242,65 +242,37 @@ export function getItemAnimalSource(itemName: string): string | undefined {
}
export function getBlockTool(blockName: string): string | null {
const block = mcdata.blocksByName[blockName]
const block = gameData.blocksByName[blockName]
if (!block || !block.harvestTools) {
return null
}
const toolId = Number(Object.keys(block.harvestTools)[0])
return getItemName(toolId)
const toolIds = Object.keys(block.harvestTools).map(id => Number.parseInt(id))
const toolName = getItemName(toolIds[0])
return toolName || null // Assuming the first tool is the simplest
}
export function makeItem(name: string, amount: number = 1): any {
export function makeItem(name: string, amount = 1): InstanceType<typeof Item> {
const itemId = getItemId(name)
if (itemId === null)
throw new Error(`Unknown item: ${name}`)
throw new Error(`Item ${name} not found.`)
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
}
// Function to get the nearest block of a specific type using Mineflayer
export function getNearestBlock(
bot: Bot,
blockType: string,
maxDistance: number,
) {
const blocks = bot.findBlocks({
matching: block => block.name === blockType,
maxDistance,
count: 1,
})
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 }
if (blocks.length === 0)
return null
const nearestBlockPosition = blocks[0]
return bot.blockAt(nearestBlockPosition)
}