Merge pull request #2 from kolbytn/command-refactor

Refactor
This commit is contained in:
Max Robinson 2023-11-07 22:40:11 -06:00 committed by GitHub
commit f3ff79b664
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 466 additions and 434 deletions

3
.gitignore vendored
View file

@ -2,3 +2,6 @@
node_modules/
package-lock.json
temp.js
scratch.js
agent_code/**
!agent_code/template.js

134
act.js
View file

@ -1,134 +0,0 @@
import { writeFileSync } from 'fs';
import { getDetailedSkills, getWorldFunctions } from './utils/context.js';
import { sendRequest } from './utils/gpt.js';
function buildSystemMessage(bot) {
let message = 'You are a helpful Minecraft bot. Given the dialogue, reflect on what you are doing and generate javascript code to accomplish that goal. Use only functions listed below to write your code.';
message += "\n\n" + getDetailedSkills();
message += "\n\n" + getWorldFunctions();
return message;
}
function buildExamples() {
return [
`mr_steve2: Will you help me collect wood?
!blocks
\`\`\`
NEARBY_BLOCKS
- oak_log
- dirt
- cobblestone
\`\`\`
Me: I'd be glad to help you collect wood.`,
`I'm going to help mr_steve2 collect wood. The type of wood block nearby is 'oak_log'. I'll adjust my code to collect an 'oak_log' for mr_steve2.
\`\`\`
await skills.collectBlock(bot, 'oak_log');
await skills.giveToPlayer(bot, 'oak_log', 'mr_steve2');
\`\`\``,
`sally32: What are you doing?
!action
\`\`\`
await skills.equipItem(bot, 'wooden_pickaxe');
while (world.getInventory(bot).coal_ore < 10) {
await skills.collectBlock(bot, 'coal_ore');
}
\`\`\`
Me: I'm looking for coal. Have you seen any?
sally32: Yes, there's some in this cave, follow me.`,
`I'm going to follow sally32 to the cave and collect coal. I'll adjust my code to follow sally32 until I find coal_ore and then I'll mine it.
\`\`\`
while (true) {
await skills.goToPlayer(bot, 'sally32');
if (world.getNearbyBlocks(bot).includes('coal_ore')) {
break;
}
}
await skills.equipItem(bot, 'wooden_pickaxe');
while (world.getInventory(bot).coal_ore < 10) {
await skills.collectBlock(bot, 'coal_ore');
}
\`\`\``,
`user42: come here
Me: Sure! I'm on my way.`,
`I'm going to navigate to user42.
\`\`\`
await skills.goToPlayer(bot, 'user42');
\`\`\``,
]
}
export var currentCode = '';
export async function executeCode(bot) {
let src = "import * as skills from './utils/skills.js';";
src += "\nimport * as world from './utils/world.js';"
src += `\n\nexport async function main(bot) {\n`;
for (let line of currentCode.split('\n')) {
src += ` ${line}\n`;
}
src += `}\n`;
writeFileSync('./temp.js', src, (err) => {
if (err) throw err;
});
console.log('executing code...\n' + currentCode);
try {
await (await import('./temp.js')).main(bot);
} catch (err) {
console.log(err);
currentCode = '';
return false;
}
currentCode = '';
return true;
}
export async function writeCode(bot, username, messages) {
let turns = buildExamples();
// For now, get rid of the first 6 example messages
messages = messages.slice(6);
let startIndex = messages.length - 6;
if (startIndex < 0)
startIndex = 0;
turns.push('');
for (let i = startIndex; i < messages.length; i++) {
if (i % 2 == 0 && messages[i] != '') {
turns[turns.length - 1] += `\n\n${username}: ${messages[i]}`;
} else if (messages[i] != '') {
turns[turns.length - 1] += `\n\nMe: ${messages[i]}`;
}
}
turns[turns.length - 1] = turns[turns.length - 1].trim();
let systemMessage = buildSystemMessage(bot);
let actResponse = await sendRequest(turns, systemMessage);
console.log(actResponse);
let code = actResponse.split('\`\`\`');
if (code.length <= 1)
return code;
if (!code[1].trim())
return code;
currentCode = code[1].trim();
if (currentCode.slice(0, 10) == 'javascript')
currentCode = currentCode.slice(10).trim();
return currentCode;
}

76
agent.js Normal file
View file

@ -0,0 +1,76 @@
import { initBot } from './utils/mcdata.js';
import { sendRequest } from './utils/gpt.js';
import { History } from './utils/history.js';
import { Coder } from './utils/coder.js';
import { getQuery, containsQuery, getQueryDocs } from './utils/queries.js';
import { getSkillDocs, containsCodeBlock } from './utils/skill_library.js';
export class Agent {
constructor(name) {
this.name = name;
this.system_message = `You are a playful Minecraft bot named '${name}' that can communicate with players, see, move, mine, build, and interact with the world by writing and executing code.
Act human-like as if you were a typical Minecraft player, rather than an AI. Be brief in your responses, omit needless words, and do not give instructions unless asked.`;
this.system_message += getQueryDocs();
this.system_message += getSkillDocs();
this.bot = initBot(name);
this.history = new History(this);
this.coder = new Coder(this);
this.bot.on('login', () => {
this.bot.chat('Hello world! I am ' + this.name);
console.log(`${this.name} logged in.`);
});
this.bot.on('chat', (username, message) => {
if (username === this.name) return;
console.log('received message from', username, ':', message);
this.respond(username, message);
});
}
async respond(username, message) {
this.history.add(username, message);
for (let i=0; i<5; i++) {
let res = await sendRequest(this.history.getHistory(), this.system_message);
this.history.add(this.name, res);
let query_cmd = containsQuery(res);
if (query_cmd) { // contains query
let message = res.substring(0, res.indexOf(query_cmd)).trim();
if (message)
this.bot.chat(message);
console.log('Agent used query:', query_cmd);
let query = getQuery(query_cmd);
let query_res = query.perform(this);
this.history.add(this.name, query_res);
}
else if (containsCodeBlock(res)) { // contains code block
let message = res.substring(0, res.indexOf('```')).trim();
if (message)
this.bot.chat(message);
else
this.bot.chat("Executing code...");
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
if (code) {
console.log('Queuing code: ' + code);
this.coder.queueCode(code);
let code_return = await this.coder.execute();
if (code_return.success)
break;
else {
let message = "Code execution failed: " + code_return.message;
message += "\n Write code to fix the problem and try again.";
this.history.add(this.name, message);
}
}
}
else { // conversation response
this.bot.chat(res);
break;
}
}
}
}

6
agent_code/template.js Normal file
View file

@ -0,0 +1,6 @@
import * as skills from '../utils/skills.js';
import * as world from '../utils/world.js';
// this file is currently unused
export async function main(bot) {
// agent's code goes here
}

81
chat.js
View file

@ -1,81 +0,0 @@
import { sendRequest } from './utils/gpt.js';
import { getHistory, addEvent } from './utils/history.js';
import { getStats, getInventory, getBlocks, getNearbyEntities, getCraftable, getDetailedSkills, getWorldFunctions } from './utils/context.js';
import { currentCode, writeCode } from './act.js';
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 player and world stats (current health, time of day, etc.)';
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 - 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;
}
export async function getChatResponse(bot, user, message) {
addEvent(user, message);
let turns = getHistory(user);
let systemMessage = buildSystemMessage();
let lastResponse = '';
let botResponse = '';
let botEvent = '';
let res = null;
for (let i = 0; i < 5; i++) {
res = await sendRequest(turns, systemMessage, '\`\`\`');
console.log('received chat:', res);
res = '\n' + res.trim();
lastResponse = '';
if (!res.includes('\n!')) {
botResponse += res;
break;
}
while (!res.startsWith('\n!')) {
lastResponse += res.slice(0, 1);
res = res.slice(1, res.length);
}
botResponse += '\n' + lastResponse.trim();
res = res.trim();
let queryRes = null;
if (res.startsWith('!stats')) {
queryRes = '\n\n!stats\n\`\`\`\n' + getStats(bot) + '\n\`\`\`';
} else if (res.startsWith('!inventory')) {
queryRes = '\n\n!inventory\n\`\`\`\n' + getInventory(bot) + '\n\`\`\`';
} else if (res.startsWith('!blocks')) {
queryRes = '\n\n!blocks\n\`\`\`\n' + getBlocks(bot) + '\n\`\`\`';
} else if (res.startsWith('!craftable')) {
queryRes = '\n\n!craftable\n\`\`\`\n' + getCraftable(bot) + '\n\`\`\`';
} else if (res.startsWith('!entities')) {
queryRes = '\n\n!entities\n\`\`\`\n' + getNearbyEntities(bot) + '\n\`\`\`';
} else if (res.startsWith('!action')) {
queryRes = '\n\n!action\n\`\`\`\n' + currentCode + '\n\`\`\`';
} else if (res.startsWith('!execute')) {
queryRes = '\n\n!execute\n\`\`\`\n' + await writeCode(bot, user, turns.concat(lastResponse.trim())) + '\n\`\`\`';
botEvent += lastResponse + queryRes + '\n\n';
break;
}
console.log('query response:', queryRes);
botEvent += lastResponse + queryRes + '\n\n';
turns.push(lastResponse + queryRes);
turns.push('');
}
console.log('sending chat:', botResponse.trim());
addEvent('bot', botEvent.trim());
return botResponse.trim();
}

35
main.js
View file

@ -1,34 +1,3 @@
import { createBot } from 'mineflayer';
import { pathfinder } from 'mineflayer-pathfinder';
import { plugin } from 'mineflayer-collectblock';
import { Agent } from './agent.js';
import { getChatResponse } from './chat.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);
let actResult = await executeCode(bot);
if (actResult) {
console.log('completed action');
}
}
const bot = createBot({
host: '127.0.0.1',
port: 55916,
username: 'andi'
})
bot.loadPlugin(pathfinder)
bot.loadPlugin(plugin)
console.log('bot created')
bot.on('chat', handleMessage);
bot.on('whisper', handleMessage);
new Agent('andy');

View file

@ -1,6 +1,7 @@
{
"type": "module",
"dependencies": {
"minecraft-data": "^3.46.2",
"mineflayer": "^4.14.0",
"mineflayer-collectblock": "^1.4.1",
"mineflayer-pathfinder": "^2.4.4",

80
utils/coder.js Normal file
View file

@ -0,0 +1,80 @@
import { writeFile, unlink } from 'fs';
export class Coder {
constructor(agent) {
this.agent = agent;
this.current_code = '';
this.file_counter = 0;
this.fp = './agent_code/';
}
queueCode(code) {
if (code.startsWith('javascript'))
code = code.slice(10);
this.current_code = code;
}
hasCode() {
return this.current_code.length > 0;
}
writeFilePromise(filename, src) {
// makes it so we can await this function
return new Promise((resolve, reject) => {
writeFile(filename, src, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async execute() {
if (!this.current_code) return {success: false, message: "No code to execute."};
let src = "import * as skills from '../utils/skills.js';";
src += "\nimport * as world from '../utils/world.js';"
src += `\n\nexport async function main(bot) {\n`;
for (let line of this.current_code.split('\n')) {
src += ` ${line}\n`;
}
src += ` return true;\n}\n`; // potentially redundant return statement, in case agent doesn't return anything
console.log("writing to file...", src)
let filename = this.fp + this.file_counter + '.js';
if (this.file_counter > 0) {
let prev_filename = this.fp + (this.file_counter-1) + '.js';
unlink(prev_filename, (err) => {
console.log("deleted file " + prev_filename);
if (err) console.error(err);
});
}
this.file_counter++;
let result = await this.writeFilePromise(filename, src);
if (result) {
console.error('Error writing code execution file: ' + result);
return {success: false, message: result};
}
try {
console.log('executing code...\n');
let execution_file = await import('.'+filename);
this.clear();
let success = await execution_file.main(this.agent.bot);
return {success, message: ""};
} catch (err) {
console.error("Problem executing code:" + err);
this.clear();
return {success: false, message: err};
}
}
clear() {
this.current_code = '';
this.agent.bot.pathfinder.setGoal(null);
}
}

View file

@ -15,21 +15,14 @@ if (process.env.OPENAI_ORG_ID) {
const openai = new OpenAIApi(openAiConfig);
export async function sendRequest(turns, systemMessage, stop_seq) {
export async function sendRequest(turns, systemMessage, stop_seq='***') {
let messages = [{'role': 'system', 'content': systemMessage}];
for (let i = 0; i < turns.length; i++) {
if (i % 2 == 0) {
messages.push({'role': 'user', 'content': turns[i]});
} else {
messages.push({'role': 'assistant', 'content': turns[i]});
}
}
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
let res = null;
try {
let completion = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
model: 'gpt-4',
messages: messages,
stop: stop_seq,
});
@ -37,7 +30,7 @@ export async function sendRequest(turns, systemMessage, stop_seq) {
}
catch (err) {
console.log(err);
res = 'I am sorry, I do not know how to respond to that.';
res = 'My brain disconnected, try again.';
}
return res;
}

View file

@ -1,60 +1,46 @@
var messages = [
{'source': 'all', 'message': 'Hey! What are you up to?'},
{'source': 'bot', 'message': `!action
\`\`\`
await skills.ExploreToFind(bot, 'coal_ore');
await skills.EquipItem(bot, 'wooden_pickaxe');
await skills.CollectBlock(bot, 'coal_ore', 10);
\`\`\`
let history_examples = [
{'role': 'user', 'content': 'miner_32: Hey! What are you up to?'},
{'role': 'assistant', 'content': 'Nothing much miner_32, what do you need?'},
I'm looking for coal. Have you seen any?`},
{'source': 'all', 'message': 'No, but can you help me collect wood?'},
{'source': 'bot', 'message': `!blocks
\`\`\`
NEARBY_BLOCKS
- oak_log
- dirt
- cobblestone
- birch_log
\`\`\`
{'role': 'user', 'content': 'grombo_Xx: What do you see?'},
{'role': 'assistant', 'content': 'Let me see... !blocks'},
{'role': 'assistant', 'content': 'NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone'},
{'role': 'assistant', 'content': 'I see some oak logs, dirt, and cobblestone.'},
Sure, do you want oak or birch?`},
{'source': 'all', 'message': 'Thanks! Either is fine.'},
{'source': 'bot', 'message': `I'll do that now.
{'role': 'user', 'content': 'zZZn98: come here'},
{'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nreturn await skills.goToPlayer(bot, "zZZn98");```'},
!execute
\`\`\`
while (true) {
await skills.CollectBlock(bot, 'oak_log', 1);
await skills.goToPlayer(bot, 'username');
await skills.DropItem(bot, 'oak_log', 1);
}
\`\`\``}
];
{'role': 'user', 'content': 'hanky: collect some sand for me please'},
{'role': 'assistant', 'content': 'Collecting sand...```// I am going to collect 3 sand and give to hanky.\n\
await skills.collectBlock(bot, "sand");\nreturn await skills.giveToPlayer(bot, "sand", "hanky");```'},
{'role': 'user', 'content': 'sarah_O.o: can you do a dance for me?'},
{'role': 'assistant', 'content': "I don't know how to do that."},
export function addEvent(source, message) {
messages.push({source, message});
}
{'role': 'user', 'content': 'hanky: kill that zombie!'},
{'role': 'assistant', 'content': "I'm attacking! ```//I'm going to attack the nearest zombie.\n\
return await skills.attackMob(bot, 'zombie');```"},
{'role': 'user', 'content': 'billybob: stop what you are doing'},
{'role': 'assistant', 'content': '```// I am going to write nothing to clear my code\n return true;```'},
]
export function getHistory(source) {
let res = [];
let lastSource = null;
for (let i = 0; i < messages.length; i++) {
if (lastSource != source && (messages[i].source == source || messages[i].source == 'all')) {
res.push(messages[i].message);
lastSource = source;
} else if (lastSource == source && (messages[i].source == source || messages[i].source == 'all')) {
res[-1] += '\n\n' + messages[i].message;
} else if (lastSource == source && messages[i].source == 'bot') {
res.push(messages[i].message);
lastSource = 'bot';
} else if (lastSource == 'bot' && messages[i].source == 'bot') {
res[-1] += '\n\n' + messages[i].message;
} else {
lastSource = null;
}
export class History {
constructor(agent) {
this.agent = agent;
this.turns = history_examples;
}
return res;
}
getHistory() {
return this.turns;
}
add(name, content) {
let role = 'assistant';
if (name !== this.agent.name) {
role = 'user';
content = `${name}: ${content}`;
}
this.turns.push({role, content});
}
}

View file

@ -1,6 +1,23 @@
import minecraftData from 'minecraft-data';
var mcdata = minecraftData('1.19.3');
import { createBot } from 'mineflayer';
import { pathfinder } from 'mineflayer-pathfinder';
import { plugin } from 'mineflayer-collectblock';
const mc_version = '1.19.3'
let mcdata = minecraftData(mc_version);
export function initBot(username) {
let bot = createBot({
host: 'localhost',
port: 55916,
username: username,
version: mc_version,
});
bot.loadPlugin(pathfinder)
bot.loadPlugin(plugin)
return bot;
}
export function getItemId(item) {
return mcdata.itemsByName[item].id;

77
utils/queries.js Normal file
View file

@ -0,0 +1,77 @@
import { getStats, getInventory, getBlocks, getNearbyEntities, getCraftable } from './context.js';
const pad = (str) => {
return '\n' + str + '\n';
}
const queryList = [
{
name: "!stats",
description: "Get your bot's stats",
perform: function (agent) {
return pad(getStats(agent.bot));
}
},
{
name: "!inventory",
description: "Get your bot's inventory.",
perform: function (agent) {
return pad(getInventory(agent.bot));
}
},
{
name: "!blocks",
description: "Get the blocks near the bot.",
perform: function (agent) {
return pad(getBlocks(agent.bot));
}
},
{
name: "!craftable",
description: "Get the craftable items with the bot's inventory.",
perform: function (agent) {
return pad(getCraftable(agent.bot));
}
},
{
name: "!entities",
description: "Get the nearby players and entities.",
perform: function (agent) {
return pad(getNearbyEntities(agent.bot));
}
},
{
name: "!action",
description: "Get the currently executing code.",
perform: function (agent) {
return pad("Current code:\n`" + agent.coder.current_code +"`");
}
},
];
const queryMap = {};
for (let query of queryList) {
queryMap[query.name] = query;
}
export function getQuery(name) {
return queryMap[name];
}
export function containsQuery(message) {
for (let query of queryList) {
if (message.includes(query.name)) {
return query.name;
}
}
return null;
}
export function getQueryDocs() {
let docs = `\n*QUERY DOCS\n You can use the following commands to query for information about the world.
Use the query name in your response and the next input will have the requested information.\n`;
for (let query of queryList) {
docs += query.name + ': ' + query.description + '\n';
}
return docs + '*\n';
}

19
utils/skill_library.js Normal file
View file

@ -0,0 +1,19 @@
import * as skills from './skills.js';
import * as world from './world.js';
export function getSkillDocs() {
let docstring = "\n*SKILL DOCS\nThese skills are javascript functions that can be called with a js function by writing a code block. Ex: '```// write description comment and code here```' \n\
Your code block should return a bool indicating if the task was completed successfully. It will return true if you don't write a return statement.\n";
for (let skillFunc of Object.values(world).concat(Object.values(skills))) {
let str = skillFunc.toString();
if (str.includes('/**')){
docstring += skillFunc.name;
docstring += str.substring(str.indexOf('/**')+3, str.indexOf('**/')) + '\n';
}
}
return docstring + '*\n';
}
export function containsCodeBlock(message) {
return message.indexOf('```') !== -1;
}

View file

@ -3,15 +3,15 @@ import { getCraftingTable, getInventoryCounts, getInventoryStacks, getNearbyMobs
import pf from 'mineflayer-pathfinder';
/**
* Attempt to craft the given item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} item_name, the item name to craft.
* @returns {Promise<boolean>} true if the item was crafted, false otherwise.
* @example
* await skills.craftItem(bot, "wooden_pickaxe");
**/
export async function craftItem(bot, itemName) {
/**
* Attempt to craft the given item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} item_name, the item name to craft.
* @returns {Promise<boolean>} true if the item was crafted, false otherwise.
* @example
* await skills.craftItem(bot, "wooden_pickaxe");
**/
const table = getCraftingTable(bot);
let recipes = bot.recipesFor(getItemId(itemName), null, 1, table);
await bot.craft(recipes[0], 1, null);
@ -19,15 +19,15 @@ export async function craftItem(bot, itemName) {
}
/**
* Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} mobType, the type of mob to attack.
* @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
* @example
* await skills.attackMob(bot, "zombie");
**/
export async function attackMob(bot, mobType) {
/**
* Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} mobType, the type of mob to attack.
* @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
* @example
* await skills.attackMob(bot, "zombie");
**/
const mobs = getNearbyMobs(bot);
for (let i = 0; i < mobs.length; i++) {
if (mobs[i].mobType == mobType) {
@ -39,15 +39,15 @@ export async function attackMob(bot, mobType) {
}
/**
* Collect one of the given block type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to collect.
* @returns {Promise<boolean>} true if the block was collected, false if the block type was not found.
* @example
* await skills.collectBlock(bot, "oak_log");
**/
export async function collectBlock(bot, blockType) {
/**
* Collect one of the given block type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to collect.
* @returns {Promise<boolean>} true if the block was collected, false if the block type was not found.
* @example
* await skills.collectBlock(bot, "oak_log");
**/
const blocks = getNearbyBlocks(bot);
for (let i = 0; i < blocks.length; i++) {
if (blocks[i].name == blockType) {
@ -59,18 +59,18 @@ export async function collectBlock(bot, blockType) {
}
/**
* Break the block at the given position. Will use the bot's equipped item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate of the block to break.
* @param {number} y, the y coordinate of the block to break.
* @param {number} z, the z coordinate of the block to break.
* @returns {Promise<boolean>} true if the block was broken, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.breakBlockAt(bot, position.x, position.y - 1, position.x);
**/
export async function breakBlockAt(bot, x, y, z) {
/**
* Break the block at the given position. Will use the bot's equipped item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate of the block to break.
* @param {number} y, the y coordinate of the block to break.
* @param {number} z, the z coordinate of the block to break.
* @returns {Promise<boolean>} true if the block was broken, false otherwise.
* @example
* let position = world.getPosition(bot);
* await skills.breakBlockAt(bot, position.x, position.y - 1, position.x);
**/
let current = bot.blockAt({ x: x, y: y, z: z });
if (current.name != 'air')
await bot.dig(current, true);
@ -78,20 +78,19 @@ export async function breakBlockAt(bot, x, y, z) {
}
/**
* Place the given block type at the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to place.
* @param {number} x, the x coordinate to place the block at.
* @param {number} y, the y coordinate to place the block at.
* @param {number} z, the z coordinate to place the block at.
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y, position.x);
**/
export async function placeBlock(bot, blockType, x, y, z) {
/**
* Place the given block type at the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to place.
* @param {number} x, the x coordinate to place the block at.
* @param {number} y, the y coordinate to place the block at.
* @param {number} z, the z coordinate to place the block at.
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
* @example
* let position = world.getPosition(bot);
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y, position.x);
**/
let referenceBlock = null;
let refVec = null;
if (bot.blockAt({ x: x + 1, y: y, z: z }).name != "air") {
@ -133,15 +132,15 @@ export async function placeBlock(bot, blockType, x, y, z) {
}
/**
* Equip the given item or block.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemName, the item or block name to equip.
* @returns {Promise<boolean>} true if the item was equipped, false otherwise.
* @example
* await skills.equipItem(bot, "wooden_pickaxe");
**/
export async function equipItem(bot, itemName) {
/**
* Equip the given item or block.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemName, the item or block name to equip.
* @returns {Promise<boolean>} true if the item was equipped, false otherwise.
* @example
* await skills.equipItem(bot, "wooden_pickaxe");
**/
let item = null;
for (let stack of getInventoryStacks(bot)) {
if (stack.name == itemName) {
@ -156,18 +155,18 @@ export async function equipItem(bot, itemName) {
}
/**
* Navigate to the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate to navigate to. If null, the bot's current x coordinate will be used.
* @param {number} y, the y coordinate to navigate to. If null, the bot's current y coordinate will be used.
* @param {number} z, the z coordinate to navigate to. If null, the bot's current z coordinate will be used.
* @returns {Promise<boolean>} true if the position was reached, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.goToPosition(bot, position.x, position.y, position.x + 20);
**/
export async function goToPosition(bot, x, y, z) {
/**
* Navigate to the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate to navigate to. If null, the bot's current x coordinate will be used.
* @param {number} y, the y coordinate to navigate to. If null, the bot's current y coordinate will be used.
* @param {number} z, the z coordinate to navigate to. If null, the bot's current z coordinate will be used.
* @returns {Promise<boolean>} true if the position was reached, false otherwise.
* @example
* let position = world.getPosition(bot);
* await skills.goToPosition(bot, position.x, position.y, position.x + 20);
**/
if (x == null) x = bot.entity.position.x;
if (y == null) y = bot.entity.position.y;
if (z == null) z = bot.entity.position.z;
@ -178,16 +177,16 @@ export async function goToPosition(bot, x, y, z) {
}
/**
* Give one of the specified item to the specified player
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemType, the name of the item to give.
* @param {string} username, the username of the player to give the item to.
* @returns {Promise<boolean>} true if the item was given, false otherwise.
* @example
* await skills.giveToPlayer(bot, "oak_log", "player1");
**/
export async function giveToPlayer(bot, itemType, username) {
/**
* Give one of the specified item to the specified player
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemType, the name of the item to give.
* @param {string} username, the username of the player to give the item to.
* @returns {Promise<boolean>} true if the item was given, false otherwise.
* @example
* await skills.giveToPlayer(bot, "oak_log", "player1");
**/
let player = bot.players[username].entity
if (!player)
return false;
@ -201,15 +200,15 @@ export async function giveToPlayer(bot, itemType, username) {
}
/**
* Navigate to the given player.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to navigate to.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.goToPlayer(bot, "player");
**/
export async function goToPlayer(bot, username) {
/**
* Navigate to the given player.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to navigate to.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.goToPlayer(bot, "player");
**/
let player = bot.players[username].entity
if (!player)
return false;
@ -219,3 +218,24 @@ export async function goToPlayer(bot, username) {
bot.pathfinder.setGoal(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 3));
return true;
}
export async function followPlayer(bot, username) {
/**
* Follow the given player endlessly.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to follow.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.followPlayer(bot, "player");
**/
let player = bot.players[username].entity
if (!player)
return false;
bot.pathfinder.setMovements(new pf.Movements(bot));
let pos = player.position;
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 3), true);
return true;
}

View file

@ -81,16 +81,16 @@ export function getInventoryStacks(bot) {
}
/**
* Get an object representing the bot's inventory.
* @param {Bot} bot - The bot to get the inventory for.
* @returns {object} - An object with item names as keys and counts as values.
* @example
* let inventory = world.getInventoryCounts(bot);
* let oakLogCount = inventory['oak_log'];
* let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0;
**/
export function getInventoryCounts(bot) {
/**
* Get an object representing the bot's inventory.
* @param {Bot} bot - The bot to get the inventory for.
* @returns {object} - An object with item names as keys and counts as values.
* @example
* let inventory = world.getInventoryCounts(bot);
* let oakLogCount = inventory['oak_log'];
* let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0;
**/
let inventory = {};
for (const item of getInventoryStacks(bot)) {
if (inventory.hasOwnProperty(item.name)) {
@ -113,27 +113,27 @@ export function getInventoryCounts(bot) {
}
/**
* Get your position in the world (Note that y is vertical).
* @param {Bot} bot - The bot to get the position for.
* @returns {Vec3} - An object with x, y, and x attributes representing the position of the bot.
* @example
* let position = world.getPosition(bot);
* let x = position.x;
**/
export function getPosition(bot) {
/**
* Get your position in the world (Note that y is vertical).
* @param {Bot} bot - The bot to get the position for.
* @returns {Vec3} - An object with x, y, and x attributes representing the position of the bot.
* @example
* let position = world.getPosition(bot);
* let x = position.x;
**/
return bot.entity.position;
}
/**
* Get a list of all nearby mob types.
* @param {Bot} bot - The bot to get nearby mobs for.
* @returns {string[]} - A list of all nearby mobs.
* @example
* let mobs = world.getNearbyMobTypes(bot);
**/
export function getNearbyMobTypes(bot) {
/**
* Get a list of all nearby mob types.
* @param {Bot} bot - The bot to get nearby mobs for.
* @returns {string[]} - A list of all nearby mobs.
* @example
* let mobs = world.getNearbyMobTypes(bot);
**/
let mobs = getNearbyMobs(bot, 16);
let found = [];
for (let i = 0; i < mobs.length; i++) {
@ -145,14 +145,14 @@ export function getNearbyMobTypes(bot) {
}
/**
* Get a list of all nearby player names.
* @param {Bot} bot - The bot to get nearby players for.
* @returns {string[]} - A list of all nearby players.
* @example
* let players = world.getNearbyPlayerNames(bot);
**/
export function getNearbyPlayerNames(bot) {
/**
* Get a list of all nearby player names.
* @param {Bot} bot - The bot to get nearby players for.
* @returns {string[]} - A list of all nearby players.
* @example
* let players = world.getNearbyPlayerNames(bot);
**/
let players = getNearbyPlayers(bot, 16);
let found = [];
for (let i = 0; i < players.length; i++) {
@ -164,14 +164,14 @@ export function getNearbyPlayerNames(bot) {
}
/**
* 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) {
/**
* 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);
**/
let blocks = getNearbyBlocks(bot, 16);
let found = [];
for (let i = 0; i < blocks.length; i++) {