Skip to main content
Glama
prepareLandForFarming.ts10.9 kB
import minecraftData from 'minecraft-data'; import {Bot} from 'mineflayer'; import mineflayer_pathfinder from 'mineflayer-pathfinder'; import {ISkillServiceParams, ISkillParams} from '../../types/skillType.js'; import {navigateToLocation} from '../library/navigateToLocation.js'; import {Block} from 'prismarine-block'; import {Vec3} from 'vec3'; import {isSignalAborted, validateSkillParams} from '../index.js'; import {placeBlock} from '../library/placeBlock.js'; import {plantSeedsOnFarmland} from '../library/plantSeedsOnFarmland.js'; import {tillLandToFarmland} from '../library/tillLandToFarmland.js'; const {Movements} = mineflayer_pathfinder; /** * Prepares the land for crops, and plants crops. * * @param {Bot} bot - The Mineflayer bot instance. * @param {IFunctionCall} params - The parameters for the skill function. * @param {ISkillServiceParams} serviceParams - Additional parameters for the skill function. * @param serviceParams * * @return {Promise<boolean>} - Returns true if the bot successfully prepared the land for farming, false otherwise. */ export const prepareLandForFarming = async ( bot: Bot, params: ISkillParams, serviceParams: ISkillServiceParams, ): Promise<boolean> => { const skillName = 'prepareLandForFarming'; const requiredParams: string[] = []; const isParamsValid = validateSkillParams( params, requiredParams, skillName, ); if (!isParamsValid) { serviceParams.cancelExecution?.(); bot.emit( 'alteraBotEndObservation', `Mistake: You didn't provide all of the required parameters ${requiredParams.join(', ')} for the ${skillName} skill.`, ); return false; } const {signal, getStatsData, setStatsData} = serviceParams; const mcData = minecraftData(bot.version); // Avoid breaking blocks while building const movements = new Movements(bot); movements.canDig = false; // Prevent the bot from breaking blocks bot.pathfinder.setMovements(movements); // Remove expanding the farms for now // const placedCount = expandFarm(bot, 8); const waterBlocks = bot .findBlocks({ matching: mcData.blocksByName.water.id, maxDistance: 6, count: 100, }) .map((position) => bot.blockAt(position)); // console.log("Found water blocks: ", waterBlocks.length); const airId = mcData.blocksByName.air.id; // grassId is 1.19, shortGrassId is 1.20 const grassId = mcData.blocksByName.grass?.id; const shortGrassId = mcData.blocksByName.short_grass?.id; const tallGrassId = mcData.blocksByName.tall_grass?.id; const adjacentWaterBlocks = waterBlocks.filter((waterBlock) => hasAdjacentLand(bot, {position: waterBlock.position}), ); // console.log("Found adjacent water blocks: ", adjacentWaterBlocks.length); const queue = adjacentWaterBlocks.map((waterBlock) => ({ block: waterBlock, distance: 0, })); // const queue = waterBlocks.map(waterBlock => ({ block: waterBlock, distance: 0 })); // console.log("Found water blocks: ", queue.length) const visited = new Set(); const tillableBlocks = []; while (queue.length > 0) { const {block, distance} = queue.shift(); // Get the first element from the queue // console.log("Processing block: ", block.position.toString()); const blockKey = block.position.toString(); if (visited.has(blockKey)) continue; // Skip if already visited visited.add(blockKey); // If it's a dirt or grass block and within 4 blocks of water, mark it for tilling if ( distance > 0 && distance <= 4 && (block.type === mcData.blocksByName.dirt.id || block.type === mcData.blocksByName.grass_block.id) ) { const blockAbove = bot.blockAt(block.position.offset(0, 1, 0)); if ( blockAbove && (blockAbove.type === airId || blockAbove.type === grassId || blockAbove.type === shortGrassId || blockAbove.type === tallGrassId) ) { tillableBlocks.push(block); } } if (distance < 4) { const neighbors = getNeighborBlocks(bot, { position: block.position, yLevel: block.position.y, }); for (const neighbor of neighbors) { const neighborKey = neighbor.position.toString(); if (!visited.has(neighborKey)) { queue.push({block: neighbor, distance: distance + 1}); } } } } // Till the identified blocks for (const block of tillableBlocks) { // Navigate to the block await navigateToLocation(bot, { x: block.position.x, y: block.position.y, z: block.position.z, range: 1, }); // check for signal to cancel if (isSignalAborted(signal)) { return bot.emit( 'alteraBotEndObservation', `You decided to do something else and stop preparing land for farming.`, ); } await tillLandToFarmland(bot, { targetBlock: block, setStatsData, getStatsData, }); } await plantSeedsOnFarmland(bot, {getStatsData, setStatsData, radius: 8}); const defaultMovements = new Movements(bot); bot.pathfinder.setMovements(defaultMovements); bot.emit( 'alteraBotEndObservation', `You ` /* + `placed ${placedCount} new dirt blocks in the farm, `*/ + `tilled ${tillableBlocks.length} pieces of land, and have finished preparing land for farming.`, ); return true; }; interface IGetNeighborBlocksOptions { position: Vec3; yLevel: number; } /** * Get the neighboring blocks of the specified block. * @param {Bot} bot - The Mineflayer bot instance. * @param {IGetNeighborBlocksOptions} options - The options for getting the neighboring blocks. * @param {Vec3} options.position - The position of the block. * @param {number} options.yLevel - The y-level of the block. * * @return {Block[]} - Returns the neighboring blocks. */ const getNeighborBlocks = ( bot: Bot, options: IGetNeighborBlocksOptions, ): Block[] => { const {position, yLevel} = options; const offsets = [ {x: 1, y: 0, z: 0}, {x: -1, y: 0, z: 0}, {x: 0, y: 0, z: 1}, {x: 0, y: 0, z: -1}, // { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 }, // Check above and below too ]; return offsets .map((offset) => { const newPos = position.plus(new Vec3(offset.x, offset.y, offset.z)); return bot.blockAt(newPos); }) .filter((block) => block && block.position.y === yLevel); // Remove undefined blocks (e.g., out of the world bounds) }; interface IHasAdjacentLandOptions { position: Vec3; } /** * Check if the specified block has adjacent land. * @param {Bot} bot - The Mineflayer bot instance. * @param {IHasAdjacentLandOptions} options - The options for checking if the block has adjacent land. * * @return {boolean} - Returns true if the block has adjacent land, false otherwise. */ const hasAdjacentLand = ( bot: Bot, options: IHasAdjacentLandOptions, ): boolean => { const {position} = options; const mcData = minecraftData(bot.version); const offsets = [ {x: 1, y: 0, z: 0}, {x: -1, y: 0, z: 0}, {x: 0, y: 0, z: 1}, {x: 0, y: 0, z: -1}, ]; return offsets.some((offset) => { const newPos = position.plus(new Vec3(offset.x, offset.y, offset.z)); const block = bot.blockAt(newPos); return ( block && (block.type === mcData.blocksByName.dirt.id || block.type === mcData.blocksByName.grass_block.id) ); }); }; interface IExpandFarmOptions { getStatsData: ISkillServiceParams['getStatsData']; setStatsData: ISkillServiceParams['setStatsData']; count?: number; } /** * Expands the farm by placing dirt blocks. * @param {Bot} bot - The Mineflayer bot instance. * @param {IExpandFarmOptions} options - The options for expanding the farm. * @param {number} options.count - The number of dirt blocks to place. Default is 8. * * @return {Promise<number>} - Returns the number of dirt blocks placed. */ const expandFarm = async ( bot: Bot, options: IExpandFarmOptions, ): Promise<number> => { const defaultOptions = { count: 8, }; const {count, getStatsData, setStatsData} = {...defaultOptions, ...options}; let placedCount = 0; for (let i = 0; i < count; i++) { if (bot.inventory.items().find((item) => item.name === 'dirt')) { const expandableArea = await findExpandableWater(bot, { number: 100, radius: 8, }); if (expandableArea.length === 0) { break; } const expansionLocation = expandableArea[Math.floor(Math.random() * expandableArea.length)]; await placeBlock(bot, { name: 'dirt', x: expansionLocation.x, y: expansionLocation.y, z: expansionLocation.z, getStatsData, setStatsData, }); placedCount++; } } return placedCount; }; interface IFindExpandableWaterOptions { number: number; radius: number; } /** * Find expandable water blocks. * @param {Bot} bot - The Mineflayer bot instance. * @param {IFindExpandableWaterOptions} options - The options for finding expandable water blocks. * @param {number} options.number - The number of expandable water blocks to find. * @param {number} options.radius - The radius to search for expandable water blocks. * * @return {Promise<Vec3[]>} - Returns the expandable water blocks. */ async function findExpandableWater( bot: Bot, options: IFindExpandableWaterOptions, ): Promise<Vec3[]> { const {number, radius} = options; const mcData = minecraftData(bot.version); const water: Vec3[] = []; const positions = bot.findBlocks({ point: bot.entity.position, matching: (block) => block.type === mcData.blocksByName.water.id, maxDistance: radius, count: number, }); for (const pos of positions) { const above = bot.blockAt(pos.plus(new Vec3(0, 1, 0))); if (!above || above.type != mcData.blocksByName.air.id) { continue; } let adjacentCount = 0; const plusX = bot.blockAt(pos.plus(new Vec3(1, 0, 0))); const minusX = bot.blockAt(pos.plus(new Vec3(-1, 0, 0))); const plusZ = bot.blockAt(pos.plus(new Vec3(0, 0, 1))); const minusZ = bot.blockAt(pos.plus(new Vec3(0, 0, -1))); if ( plusX && plusX.type == mcData.blocksByName.water.id && !plusX.getProperties().level ) { adjacentCount++; } if ( minusX && minusX.type == mcData.blocksByName.water.id && !minusX.getProperties().level ) { adjacentCount++; } if ( plusZ && plusZ.type == mcData.blocksByName.water.id && !plusZ.getProperties().level ) { adjacentCount++; } if ( minusZ && minusZ.type == mcData.blocksByName.water.id && !minusZ.getProperties().level ) { adjacentCount++; } if (adjacentCount > 2 && adjacentCount < 4) { water.push(pos); } } return water; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/leo4life2/minecraft-mcp-http'

If you have feedback or need assistance with the MCP directory API, please join our Discord server