From a6b1f4cb81d1293063fbc6b3519d75ee28d0acba Mon Sep 17 00:00:00 2001 From: Kolby Nottingham Date: Fri, 29 Sep 2023 12:53:56 -0700 Subject: [PATCH] finished context commands --- act.js | 10 +++--- chat.js | 41 ++++++++++------------- main.js | 8 ++--- utils/context.js | 85 ++++++++++++++++++++++++++++++++++++++++-------- utils/mcdata.js | 50 ++++++++++++++++++++++++---- utils/world.js | 32 +++++++++++------- 6 files changed, 163 insertions(+), 63 deletions(-) diff --git a/act.js b/act.js index 631dd7f..872f43e 100644 --- a/act.js +++ b/act.js @@ -86,9 +86,11 @@ export async function executeCode(bot) { await (await import('./temp.js')).main(bot); } catch (err) { console.log(err); + currentCode = ''; return false; } - + + currentCode = ''; return true; } @@ -118,13 +120,13 @@ export async function writeCode(bot, username, messages) { let code = actResponse.split('\`\`\`'); if (code.length <= 1) - return false; + return code; if (!code[1].trim()) - return false; + return code; currentCode = code[1].trim(); if (currentCode.slice(0, 10) == 'javascript') currentCode = currentCode.slice(10).trim(); - return true; + return currentCode; } diff --git a/chat.js b/chat.js index 095afd5..0e0b0b8 100644 --- a/chat.js +++ b/chat.js @@ -1,22 +1,22 @@ import { sendRequest } from './utils/gpt.js'; import { getHistory, addEvent } from './utils/history.js'; -import { getStats, getInventory, getBlocks, getNearbyPlayers, getNearbyEntities, getCraftable } from './utils/context.js'; -import { currentCode, executeCode, writeCode } from './act.js'; +import { getStats, getInventory, getBlocks, getNearbyEntities, getCraftable } from './utils/context.js'; +import { currentCode, writeCode } from './act.js'; -function buildSystemMessage(bot) { +function buildSystemMessage() { let message = 'You are a playful Minecraft bot that can communicate with players and move within and interact with the world.'; message += ' Act human-like as if you were a typical Minecraft player, rather than an AI.'; message += ' Do not give instructions unless asked, and always be brief in your responses.'; message += '\n\nYou can use the following commands followed by to query for information about the world.'; message += ' The query response will be returned between sets of "\`\`\`":'; - message += '\n!stats - get your current health and other player stats'; + message += '\n!stats - get player and world stats (e.g. current health and time of day)'; message += '\n!inventory - get your current inventory'; message += '\n!blocks - get a list of nearby blocks'; message += '\n!craftable - get a list of craftable items with your current inventory'; message += '\n!entities - get a list of nearby players and entities'; - message += '\n!action - prints the currently executing code'; - message += '\n\nYou can also execute actions in Minecraft by writing javascript code.'; + message += '\n!action - get the currently executing code'; + message += '\n\nYou can also execute actions such as moving and mining in Minecraft by writing javascript code.'; message += ' To do so, simply begin a codeblock with the "!execute" command. For example:'; message += '\n!execute\n\`\`\`\nCODE\n\`\`\`'; return message; @@ -26,7 +26,7 @@ function buildSystemMessage(bot) { export async function getChatResponse(bot, user, message) { addEvent(user, message); let turns = getHistory(user); - let systemMessage = buildSystemMessage(bot); + let systemMessage = buildSystemMessage(); let botResponse = ''; let botEvent = ''; @@ -37,36 +37,31 @@ export async function getChatResponse(bot, user, message) { console.log('received chat:', res); let queryRes = null; - if (res.trim().slice(res.length - 7) == '!stats') { - botResponse += '\n' + res.trim().slice(0, res.length - 7).trim(); + if (res.includes('!stats')) { queryRes = '\n\n!stats\n\`\`\`\n' + getStats(bot) + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 11) == '!inventory') { - botResponse += '\n' + res.trim().slice(0, res.length - 11).trim(); + } else if (res.includes('!inventory')) { queryRes = '\n\n!inventory\n\`\`\`\n' + getInventory(bot) + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 8) == '!blocks') { - botResponse += '\n' + res.trim().slice(0, res.length - 8).trim(); + } else if (res.includes('!blocks')) { queryRes = '\n\n!blocks\n\`\`\`\n' + getBlocks(bot) + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 11) == '!craftable') { - botResponse += '\n' + res.trim().slice(0, res.length - 11).trim(); + } else if (res.includes('!craftable')) { queryRes = '\n\n!craftable\n\`\`\`\n' + getCraftable(bot) + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 10) == '!entities') { - botResponse += '\n' + res.trim().slice(0, res.length - 10).trim(); - queryRes = '\n\n!entities\n\`\`\`\n' + getNearbyPlayers(bot) + '\n' + getNearbyEntities(bot) + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 8) == '!action') { - botResponse += '\n' + res.trim().slice(0, res.length - 8).trim(); + } else if (res.includes('!entities')) { + queryRes = '\n\n!entities\n\`\`\`\n' + getNearbyEntities(bot) + '\n\`\`\`'; + } else if (res.includes('!action')) { queryRes = '\n\n!action\n\`\`\`\n' + currentCode + '\n\`\`\`'; - } else if (res.trim().slice(res.length - 9) == '!execute') { - botResponse += '\n' + res.trim().slice(0, res.length - 9).trim(); + } else if (res.includes('!execute')) { queryRes = '\n\n!execute\n\`\`\`\n' + await writeCode(bot, user, turns.concat(botResponse), botResponse) + '\n\`\`\`'; } else { + botResponse += '\n' + res.trim(); break; } + console.log('query response:', queryRes); botEvent += queryRes; turns[turns.length - 1] += queryRes } - console.log('sending chat:', botResponse); + console.log('sending chat:', botResponse.trim()); addEvent('bot', botEvent); return botResponse.trim(); } diff --git a/main.js b/main.js index 04e1384..f90043a 100644 --- a/main.js +++ b/main.js @@ -3,13 +3,13 @@ import { pathfinder } from 'mineflayer-pathfinder'; import { plugin } from 'mineflayer-collectblock'; import { getChatResponse } from './chat.js'; -import { executeCode, writeCode } from './act.js'; +import { executeCode } from './act.js'; async function handleMessage(username, message) { if (username === bot.username) return; console.log('received message from', username, ':', message); - + let chat = await getChatResponse(bot, username, message); bot.chat(chat); @@ -23,13 +23,11 @@ async function handleMessage(username, message) { const bot = createBot({ host: '127.0.0.1', port: 55916, - username: 'andy' + username: 'andi' }) bot.loadPlugin(pathfinder) bot.loadPlugin(plugin) -// await writeCode(bot, 'all', ['all: Now, you should set your own personal goal.']); -// executeCode(bot); console.log('bot created') bot.on('chat', handleMessage); diff --git a/utils/context.js b/utils/context.js index 8238557..bc75e55 100644 --- a/utils/context.js +++ b/utils/context.js @@ -1,40 +1,97 @@ import { readFileSync } from 'fs'; -import { getNearbyBlocks } from './world.js'; +import { getNearbyBlocks, getNearbyBlockTypes } from './world.js'; +import { getAllItems } from './mcdata.js'; export function getStats(bot) { - return null; + let res = 'STATS'; + res += `\n- position: x:${bot.entity.position.x}, y:${bot.entity.position.y}, z:${bot.entity.position.z}`; + res += `\n- health: ${bot.health} / 20`; + if (bot.time.timeOfDay < 6000) { + res += '\n- time: Morning'; + } else if (bot.time.timeOfDay < 12000) { + res += '\n- time: Afternoon'; + } else { + res += '\n- time: Night'; + } + return res; } export function getInventory(bot) { - return null; + let res = 'INVENTORY'; + let allItems = new Map(); + for (const item of bot.inventory.slots.values()) { + if (item != null) { + if (allItems.has(item.name)) { + allItems.set(item.name, allItems.get(item.name) + item.count); + } else { + allItems.set(item.name, item.count); + } + } + } + for (const [item, count] of allItems.entries()) { + res += `\n- ${item}: ${count}`; + } + if (allItems.size == 0) { + res += ': empty'; + } + return res; } export function getBlocks(bot) { - let res = 'NEARBY_BLOCKS\n'; - let blocks = getNearbyBlocks(bot); + let res = 'NEARBY_BLOCKS'; + let blocks = getNearbyBlockTypes(bot); for (let i = 0; i < blocks.length; i++) { - res += `- ${blocks[i]}\n`; + res += `\n- ${blocks[i]}`; } - return res.trim(); + if (blocks.length == 0) { + res += ': none'; + } + return res; } export function getNearbyEntities(bot) { - return null; -} - - -export function getNearbyPlayers(bot) { - return null; + let res = 'NEARBY_ENTITIES'; + for (const entity of Object.values(bot.entities)) { + const distance = entity.position.distanceTo(bot.entity.position); + if (distance > 50) continue; + if (entity.type == 'mob') { + res += `\n- mob: ${entity.mobType}`; + } else if (entity.type == 'player' && entity.username != bot.username) { + res += `\n- player: ${entity.username}`; + } + } + if (res == 'NEARBY_ENTITIES') { + res += ': none'; + } + return res; } export function getCraftable(bot) { - return null; + const blocks = getNearbyBlocks(bot, 50); + let table = null; + for (const block of blocks) { + if (block.name == 'crafting_table') { + table = block; + break; + } + } + let res = 'CRAFTABLE_ITEMS'; + for (const item of getAllItems()) { + let recipes = bot.recipesFor(item.id, null, 1, table); + if (recipes.length > 0) { + res += `\n- ${item.name}`; + } + } + if (res == 'CRAFTABLE_ITEMS') { + res += ': none'; + } + return res; } diff --git a/utils/mcdata.js b/utils/mcdata.js index 9838138..bc8c9a9 100644 --- a/utils/mcdata.js +++ b/utils/mcdata.js @@ -1,19 +1,57 @@ import minecraftData from 'minecraft-data'; -var mcdata = minecraftData("1.19.3"); +var mcdata = minecraftData('1.19.3'); export function getItemId(item) { - return mcdata.itemsByName[item_type].id; + return mcdata.itemsByName[item].id; } -export function getAllBlockIds(ignore) { +export function getAllItems(ignore) { + if (!ignore) { + ignore = []; + } + let items = [] + for (const itemId in mcdata.items) { + const item = mcdata.items[itemId]; + if (!ignore.includes(item.name)) { + items.push(item); + } + } + return items; +} + + +export function getAllItemIds(ignore) { + const items = getAllItems(ignore); + let itemIds = []; + for (const item of items) { + itemIds.push(item.id); + } + return itemIds; +} + + +export function getAllBlocks(ignore) { + if (!ignore) { + ignore = []; + } let blocks = [] - for (let i = 0; i < mcdata.blocks.length; i++) { - if (!ignore.includes(mcdata.blocks[i].name)) { - blocks.push(mcdata.blocks[i].id); + for (const blockId in mcdata.blocks) { + const block = mcdata.blocks[blockId]; + if (!ignore.includes(block.name)) { + blocks.push(block); } } return blocks; } + +export function getAllBlockIds(ignore) { + const blocks = getAllBlocks(ignore); + let blockIds = []; + for (const block of blocks) { + blockIds.push(block.id); + } + return blockIds; +} diff --git a/utils/world.js b/utils/world.js index df3068a..bb7dcdc 100644 --- a/utils/world.js +++ b/utils/world.js @@ -1,20 +1,30 @@ import { getAllBlockIds } from './mcdata.js'; -/** - * Get a list of all nearby blocks. - * @param {Bot} bot - The bot to get nearby blocks for. - * @returns {string[]} - A list of all nearby blocks. - * @example - * let blocks = world.getNearbyBlocks(bot); - **/ -export function getNearbyBlocks(bot) { - let positions = bot.findBlocks({'matching': getAllBlockIds(['air']), 'maxDistance': 16, 'count': 4096}); +export function getNearbyBlocks(bot, distance) { + let positions = bot.findBlocks({matching: getAllBlockIds(['air']), maxDistance: distance, count: 10000}); let found = []; for (let i = 0; i < positions.length; i++) { let block = bot.blockAt(positions[i]); - if (!found.includes(block.name)) { - found.push(block.name); + found.push(block); + } + return found; +} + + +/** + * Get a list of all nearby block names. + * @param {Bot} bot - The bot to get nearby blocks for. + * @returns {string[]} - A list of all nearby blocks. + * @example + * let blocks = world.getNearbyBlockTypes(bot); + **/ +export function getNearbyBlockTypes(bot) { + let blocks = getNearbyBlocks(bot, 16); + let found = []; + for (let i = 0; i < blocks.length; i++) { + if (!found.includes(blocks[i].name)) { + found.push(blocks[i].name); } } return found;