Skip to main content
Glama
lookAround.ts10.9 kB
import { Bot } from 'mineflayer'; import { Vec3 } from 'vec3'; import { Block } from 'prismarine-block'; import { Entity } from 'prismarine-entity'; import { Item } from 'prismarine-item'; import minecraftData from 'minecraft-data'; import { ISkillServiceParams, ISkillParams } from '../../types/skillType.js'; import { validateSkillParams } from '../index.js'; interface SkillParams { // No parameters needed for this skill } interface ServiceParams { signal: AbortSignal; cancelExecution: () => void; resetTimeout: () => void; getStatsData: () => any; setStatsData: (data: any) => void; } // Helper function to check if a block is exposed to air function canSeeBlock(bot: Bot, position: Vec3): boolean { 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 }, ]; return offsets.some((offset) => { const newPos = position.plus(new Vec3(offset.x, offset.y, offset.z)); const block = bot.blockAt(newPos); return block && block.transparent; }); } // Helper function to parse block info function parseBlockInfo(bot: Bot, block: Block): string { let retVal = block.name; const mcData = minecraftData(bot.version); try { // Check if it's a fully grown crop if (isFullyGrownCrop(block)) { retVal = 'fully grown ' + block.name; } // Check if it's farmland if (block.type === mcData.blocksByName.farmland?.id) { const blockAbove = bot.blockAt(block.position.offset(0, 1, 0)); if (blockAbove && blockAbove.type === mcData.blocksByName.air?.id) { retVal = 'empty ' + block.name; } else { retVal = 'planted ' + block.name; } } } catch (err) { // Ignore parsing errors } return retVal; } // Helper function to check if a crop is fully grown function isFullyGrownCrop(block: Block): boolean { if (!block) return false; const blockName = block.name; const properties = block.getProperties(); switch (blockName) { case 'wheat': return properties.age === 7; case 'carrots': return properties.age === 7; case 'potatoes': return properties.age === 7; case 'beetroots': return properties.age === 3; case 'nether_wart': return properties.age === 3; default: return false; } } // Get nearby blocks within radius function getNearbyBlocks(bot: Bot, radius: number = 16): string[] { const blockTypes: Set<string> = new Set(); if (!bot.entity) return []; const position = bot.entity.position.floored(); const maxDistanceXZ = radius; const maxDistanceY = Math.min(radius, 10); // Limit vertical scanning for (let x = -maxDistanceXZ; x <= maxDistanceXZ; x++) { for (let y = -maxDistanceY; y <= maxDistanceY; y++) { for (let z = -maxDistanceXZ; z <= maxDistanceXZ; z++) { const blockPos = position.offset(x, y, z); const block = bot.blockAt(blockPos); if (block && block.type !== 0 && canSeeBlock(bot, block.position)) { blockTypes.add(parseBlockInfo(bot, block)); } } } } return Array.from(blockTypes); } // Get nearby entities function getNearbyEntities(bot: Bot, radius: number = 16): string[] { if (!bot.entities || !bot.entity) return []; const allEntities = Object.values(bot.entities); const mcData = minecraftData(bot.version); const nearbyEntities = allEntities.filter((e: Entity) => { if (e.id !== bot.entity.id && e.position.distanceTo(bot.entity.position) < radius) { const block = bot.blockAt(e.position); return block && bot.canSeeBlock(block); } return false; }); // Sort by distance nearbyEntities.sort((a, b) => a.position.distanceTo(bot.entity.position) - b.position.distanceTo(bot.entity.position) ); return nearbyEntities.map((entity: Entity) => { let name = entity.name || ''; // Handle item entities if ((name === 'item' || name === 'Item') && entity.metadata) { const metadata = entity.metadata as any[]; if (metadata.length > 8 && metadata[8]) { const itemCount = metadata[8].itemCount; const itemId = metadata[8].itemId; if (mcData.items[itemId]?.displayName) { name = itemCount > 0 ? `${itemCount} ${mcData.items[itemId].displayName}` : mcData.items[itemId].displayName; } } } // Handle player entities if (name === 'player' && entity.username) { name = entity.username; } const distance = Math.round(entity.position.distanceTo(bot.entity.position)); return `${name} (${distance} blocks away)`; }).filter(item => item !== ''); } // Get time of day as string function getTimeOfDay(bot: Bot): string { if (!bot.time) return 'unknown'; const timeOfDay = bot.time.timeOfDay / 24000; if (timeOfDay < 0.25) return 'morning'; else if (timeOfDay < 0.5) return 'noon'; else if (timeOfDay < 0.75) return 'evening'; else return 'night'; } // Get weather observation function getWeather(bot: Bot): string { if (bot.thunderState > 0) return 'thunderstorm'; else if (bot.isRaining) return 'raining'; else return 'clear'; } // Get inventory summary function getInventorySummary(bot: Bot): Record<string, number> { const inventory = bot.inventory; const items: Record<string, number> = {}; inventory.items().forEach((item: Item) => { const itemName = item.name; items[itemName] = (items[itemName] || 0) + item.count; }); return items; } export const lookAround = async ( bot: Bot, params: ISkillParams, serviceParams: ISkillServiceParams, ): Promise<boolean> => { const skillName = 'lookAround'; const requiredParams: string[] = []; const isParamsValid = validateSkillParams( { ...serviceParams }, 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; } bot.emit( 'alteraBotStartObservation', `🔍 SCANNING ENVIRONMENT 🔍`, ); const mcData = minecraftData(bot.version); try { bot.emit('alteraBotStartObservation', 'Looking around to observe the environment...'); // Gather all observations const observations: string[] = []; // Location const pos = bot.entity.position; observations.push(`You are at coordinates X:${Math.floor(pos.x)}, Y:${Math.floor(pos.y)}, Z:${Math.floor(pos.z)}.`); // Health and food observations.push(`Your health is ${bot.health}/20 and hunger is ${bot.food}/20.`); // Time and weather const timeOfDay = getTimeOfDay(bot); const weather = getWeather(bot); observations.push(`It is ${timeOfDay} and the weather is ${weather}.`); // Biome const block = bot.blockAt(bot.entity.position); if (block && block.biome) { const biomeName = mcData.biomes[block.biome.id]?.name || 'unknown'; observations.push(`You are in a ${biomeName} biome.`); } // Held item const heldItem = bot.heldItem; if (heldItem) { observations.push(`You are holding ${heldItem.name}.`); } else { observations.push(`You are not holding anything.`); } // Nearby blocks const nearbyBlocks = getNearbyBlocks(bot, 16); if (nearbyBlocks.length > 0) { observations.push(`\nYou see these blocks around you:`); // Group similar blocks const blockCounts: Record<string, number> = {}; nearbyBlocks.forEach(block => { blockCounts[block] = (blockCounts[block] || 0) + 1; }); // Sort by count (most common first) const sortedBlocks = Object.entries(blockCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 15); // Limit to top 15 for readability sortedBlocks.forEach(([block, count]) => { observations.push(`- ${count} ${block}`); }); if (nearbyBlocks.length > 15) { observations.push(`... and ${nearbyBlocks.length - 15} other block types`); } } // Nearby entities const nearbyEntities = getNearbyEntities(bot, 16); if (nearbyEntities.length > 0) { observations.push(`\nYou see these entities nearby:`); nearbyEntities.forEach(entity => { observations.push(`- ${entity}`); }); } // Inventory summary const inventory = getInventorySummary(bot); const itemCount = Object.keys(inventory).length; if (itemCount > 0) { observations.push(`\nYour inventory contains ${itemCount} different items:`); const sortedInventory = Object.entries(inventory) .sort((a, b) => b[1] - a[1]) .slice(0, 10); // Show top 10 items sortedInventory.forEach(([item, count]) => { observations.push(`- ${item}: ${count}`); }); if (itemCount > 10) { observations.push(`... and ${itemCount - 10} other item types`); } } else { observations.push(`\nYour inventory is empty.`); } // Check if drowning if (bot.oxygenLevel && bot.oxygenLevel < 20) { observations.push(`\nWARNING: You are underwater! Oxygen level: ${bot.oxygenLevel}/20`); } // Current interface if ((bot as any).currentInterface) { const currentInterface = (bot as any).currentInterface; observations.push(`\nYou have a ${currentInterface.title || 'interface'} open.`); } // Combine all observations const fullObservation = observations.join('\n'); bot.emit('alteraBotEndObservation', fullObservation); return true; } catch (error) { console.error(`Error in lookAround skill: ${error}`); bot.emit('alteraBotEndObservation', `Failed to look around: ${error}`); return false; } }

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