refactor: with loads of better utils
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user