added modes, remove defend, fixed stuff

This commit is contained in:
MaxRobinsonTheGreat 2024-01-23 18:01:38 -06:00
parent 9329510c51
commit e9abae74da
9 changed files with 463 additions and 144 deletions

View file

@ -5,6 +5,7 @@ import { Examples } from './examples.js';
import { Coder } from './coder.js'; import { Coder } from './coder.js';
import { containsCommand, commandExists, executeCommand } from './commands.js'; import { containsCommand, commandExists, executeCommand } from './commands.js';
import { Events } from './events.js'; import { Events } from './events.js';
import { initModes } from './modes.js';
export class Agent { export class Agent {
@ -20,7 +21,10 @@ export class Agent {
this.bot = initBot(name); this.bot = initBot(name);
this.events = new Events(this, this.history.events) this.events = new Events(this, this.history.events);
initModes(this);
this.idle = true;
this.bot.on('login', async () => { this.bot.on('login', async () => {
@ -49,7 +53,7 @@ export class Agent {
this.bot.autoEat.options = { this.bot.autoEat.options = {
priority: 'foodPoints', priority: 'foodPoints',
startAt: 14, startAt: 14,
bannedFood: [] bannedFood: ["rotten_flesh", "spider_eye", "poisonous_potato", "pufferfish", "chicken"]
}; };
if (init_message) { if (init_message) {
@ -58,6 +62,8 @@ export class Agent {
this.bot.chat('Hello world! I am ' + this.name); this.bot.chat('Hello world! I am ' + this.name);
this.bot.emit('finished_executing'); this.bot.emit('finished_executing');
} }
this.startUpdateLoop();
}); });
} }
@ -117,4 +123,29 @@ export class Agent {
this.history.save(); this.history.save();
this.bot.emit('finished_executing'); this.bot.emit('finished_executing');
} }
startUpdateLoop() {
this.bot.on('end', () => {
console.warn('Bot disconnected! Killing agent process.')
process.exit(1);
});
this.bot.on('death', () => {
this.coder.stop();
});
this.bot.on('messagestr', async (message, _, jsonMsg) => {
if (jsonMsg.translate && jsonMsg.translate.startsWith('death') && message.startsWith(this.name)) {
console.log('Agent died: ', message);
this.handleMessage('system', `You died with the final message: '${message}'. Previous actions were stopped and you have respawned. Notify the user and perform any necessary actions.`);
}
});
this.self_defense = true;
this.defending = false;
this._pause_defending = false;
// set interval every 300ms to update the bot's state
this.update_interval = setInterval(async () => {
this.bot.modes.update();
}, 300);
}
} }

View file

@ -89,7 +89,7 @@ export class Coder {
async generateCode(agent_history) { async generateCode(agent_history) {
let system_message = "You are a minecraft bot that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world queries to write your code in a codeblock. Example response: ``` // your code here ``` You will then be given a response to your code. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem."; let system_message = "You are a minecraft mineflayer bot that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write your code in a codeblock. Example response: ``` // your code here ``` You will then be given a response to your code. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem.";
system_message += getSkillDocs(); system_message += getSkillDocs();
system_message += "\n\nExamples:\nUser zZZn98: come here \nAssistant: I am going to navigate to zZZn98. ```\nawait skills.goToPlayer(bot, 'zZZn98');```\nSystem: Code execution finished successfully.\nAssistant: Done."; system_message += "\n\nExamples:\nUser zZZn98: come here \nAssistant: I am going to navigate to zZZn98. ```\nawait skills.goToPlayer(bot, 'zZZn98');```\nSystem: Code execution finished successfully.\nAssistant: Done.";
@ -144,6 +144,9 @@ export class Coder {
role: 'system', role: 'system',
content: code_return.message content: code_return.message
}); });
if (this.agent.bot.interrupt_code)
return;
} }
return return

View file

@ -1,13 +1,15 @@
import * as skills from '../skills.js'; import * as skills from '../skills.js';
import * as world from '../world.js'; import * as world from '../world.js';
function wrapExecution(func) { function wrapExecution(func, timeout=-1) {
return async function (agent, ...args) { return async function (agent, ...args) {
agent.idle = false;
let code_return = await agent.coder.execute(async () => { let code_return = await agent.coder.execute(async () => {
await func(agent, ...args); await func(agent, ...args);
}, -1); // no timeout }, timeout);
if (code_return.interrupted && !code_return.timedout) if (code_return.interrupted && !code_return.timedout)
return; return;
agent.idle = true;
return code_return.message; return code_return.message;
} }
} }
@ -17,7 +19,9 @@ export const actionsList = [
name: '!newAction', name: '!newAction',
description: 'Perform new and unknown custom behaviors that are not available as a command by writing code.', description: 'Perform new and unknown custom behaviors that are not available as a command by writing code.',
perform: async function (agent) { perform: async function (agent) {
agent.idle = false;
let res = await agent.coder.generateCode(agent.history); let res = await agent.coder.generateCode(agent.history);
agent.idle = true;
if (res) if (res)
return '\n' + res + '\n'; return '\n' + res + '\n';
} }
@ -27,9 +31,28 @@ export const actionsList = [
description: 'Force stop all actions and commands that are currently executing.', description: 'Force stop all actions and commands that are currently executing.',
perform: async function (agent) { perform: async function (agent) {
await agent.coder.stop(); await agent.coder.stop();
agent.coder.clear();
agent.idle = true;
return 'Agent stopped.'; return 'Agent stopped.';
} }
}, },
{
name: '!setMode',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment. Ex: !setMode("hunting", true)',
params: {
'mode_name': '(string) The name of the mode to enable.',
'on': '(bool) Whether to enable or disable the mode.'
},
perform: async function (agent, mode_name, on) {
const modes = agent.bot.modes;
if (!modes.exists(mode_name))
return `Mode ${mode_name} does not exist.` + modes.getDocs();
if (modes.isOn(mode_name) === on)
return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`;
modes.setOn(mode_name, on);
return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.`;
}
},
{ {
name: '!goToPlayer', name: '!goToPlayer',
description: 'Go to the given player. Ex: !goToPlayer("steve")', description: 'Go to the given player. Ex: !goToPlayer("steve")',
@ -40,7 +63,7 @@ export const actionsList = [
}, },
{ {
name: '!followPlayer', name: '!followPlayer',
description: 'Endlessly follow the given player. Ex: !followPlayer("stevie")', description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on. Ex: !followPlayer("stevie")',
params: {'player_name': '(string) The name of the player to follow.'}, params: {'player_name': '(string) The name of the player to follow.'},
perform: wrapExecution(async (agent, player_name) => { perform: wrapExecution(async (agent, player_name) => {
await skills.followPlayer(agent.bot, player_name); await skills.followPlayer(agent.bot, player_name);
@ -55,7 +78,7 @@ export const actionsList = [
}, },
perform: wrapExecution(async (agent, type, num) => { perform: wrapExecution(async (agent, type, num) => {
await skills.collectBlock(agent.bot, type, num); await skills.collectBlock(agent.bot, type, num);
}) }, 10) // 10 minute timeout
}, },
{ {
name: '!craftRecipe', name: '!craftRecipe',
@ -87,14 +110,6 @@ export const actionsList = [
await skills.attackMob(agent.bot, type, true); await skills.attackMob(agent.bot, type, true);
}) })
}, },
{
name: '!defend',
description: 'Follow the given player and attack any nearby monsters.',
params: {'player_name': '(string) The name of the player to defend.'},
perform: wrapExecution(async (agent, player_name) => {
await skills.defendPlayer(agent.bot, player_name);
})
},
{ {
name: '!goToBed', name: '!goToBed',
description: 'Go to the nearest bed and sleep.', description: 'Go to the nearest bed and sleep.',

View file

@ -1,4 +1,4 @@
import { getNearestBlock, getNearbyMobTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from '../world.js'; import { getNearestBlock, getNearbyEntityTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from '../world.js';
import { getAllItems, getBiomeName } from '../../utils/mcdata.js'; import { getAllItems, getBiomeName } from '../../utils/mcdata.js';
const pad = (str) => { const pad = (str) => {
@ -19,6 +19,7 @@ export const queryList = [
res += `\n- Health: ${Math.round(bot.health)} / 20`; res += `\n- Health: ${Math.round(bot.health)} / 20`;
res += `\n- Hunger: ${Math.round(bot.food)} / 20`; res += `\n- Hunger: ${Math.round(bot.food)} / 20`;
res += `\n- Biome: ${getBiomeName(bot)}`; res += `\n- Biome: ${getBiomeName(bot)}`;
res += `\n- Weather: ${bot.weather}`;
// let block = bot.blockAt(pos); // let block = bot.blockAt(pos);
// res += `\n- Artficial light: ${block.skyLight}`; // res += `\n- Artficial light: ${block.skyLight}`;
// res += `\n- Sky light: ${block.light}`; // res += `\n- Sky light: ${block.light}`;
@ -95,7 +96,7 @@ export const queryList = [
for (const entity of getNearbyPlayerNames(bot)) { for (const entity of getNearbyPlayerNames(bot)) {
res += `\n- player: ${entity}`; res += `\n- player: ${entity}`;
} }
for (const entity of getNearbyMobTypes(bot)) { for (const entity of getNearbyEntityTypes(bot)) {
res += `\n- mob: ${entity}`; res += `\n- mob: ${entity}`;
} }
if (res == 'NEARBY_ENTITIES') { if (res == 'NEARBY_ENTITIES') {
@ -104,11 +105,18 @@ export const queryList = [
return pad(res); return pad(res);
} }
}, },
{
name: "!modes",
description: "Get all available modes and see which are on/off.",
perform: function (agent) {
return agent.bot.modes.getDocs();
}
},
{ {
name: "!currentAction", name: "!currentAction",
description: "Get the currently executing code.", description: "Get the currently executing code.",
perform: function (agent) { perform: function (agent) {
return pad("Current code:\n`" + agent.coder.current_code +"`"); return pad("Current code:\n`" + agent.coder.current_code +"`");
} }
} },
]; ];

198
src/agent/modes.js Normal file
View file

@ -0,0 +1,198 @@
import * as skills from './skills.js';
import * as world from './world.js';
// a mode is a function that is called every tick to respond immediately to the world
// it has the following fields:
// on: whether 'update' is called every tick
// active: whether an action has been triggered by the mode and hasn't yet finished
// paused: whether the mode is paused by another action that overrides the behavior (eg followplayer implements its own self defense)
// update: the function that is called every tick (if on is true)
// when a mode is active, it will trigger an action to be performed but won't wait for it to return output
// the order of this list matters! first modes will be prioritized
const modes = [
{
name: 'self_defense',
description: 'Automatically attack nearby enemies. Interrupts other actions.',
on: true,
active: false,
update: function (agent) {
if (this.active) return;
const enemy = world.getNearestEntityWhere(agent.bot, entity => skills.isHostile(entity), 8);
if (enemy) {
agent.bot.chat(`Fighting ${enemy.name}!`);
execute(this, agent, async () => {
await skills.defendSelf(agent.bot, 8);
});
}
}
},
{
name: 'hunting',
description: 'Automatically hunt nearby animals when idle.',
on: true,
active: false,
update: function (agent) {
if (agent.idle) {
const huntable = world.getNearestEntityWhere(agent.bot, entity => skills.isHuntable(entity), 8);
if (huntable) {
execute(this, agent, async () => {
agent.bot.chat(`Hunting ${huntable.name}!`);
await skills.attackEntity(agent.bot, huntable);
});
}
}
}
},
{
name: 'item_collecting',
description: 'Automatically collect nearby items when idle.',
on: true,
active: false,
update: function (agent) {
if (agent.idle) {
let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8);
if (item) {
execute(this, agent, async () => {
await skills.pickupNearbyItem(agent.bot);
});
}
}
}
},
{
name: 'torch_placing',
description: 'Automatically place torches when idle and there are no torches nearby.',
on: true,
active: false,
update: function (agent) {
if (this.active) return;
if (agent.idle) {
// TODO: check light level instead of nearby torches, block.light is broken
const near_torch = world.getNearestBlock(agent.bot, 'torch', 8);
if (!near_torch) {
let torches = agent.bot.inventory.items().filter(item => item.name.includes('torch'));
if (torches.length > 0) {
const torch = torches[0];
const pos = agent.bot.entity.position;
execute(this, agent, async () => {
await skills.placeBlock(agent.bot, torch.name, pos.x, pos.y, pos.z);
});
}
}
}
}
},
{
name: 'idle_staring',
description: 'Non-functional animation to look around at entities when idle.',
on: true,
active: false,
staring: false,
last_entity: null,
next_change: 0,
update: function (agent) {
if (agent.idle) {
this.active = true;
const entity = agent.bot.nearestEntity();
let entity_in_view = entity && entity.position.distanceTo(agent.bot.entity.position) < 10 && entity.name !== 'enderman';
if (entity_in_view && entity !== this.last_entity) {
this.staring = true;
this.last_entity = entity;
this.next_change = Date.now() + Math.random() * 1000 + 4000;
}
if (entity_in_view && this.staring) {
let isbaby = entity.type !== 'player' && entity.metadata[16];
let height = isbaby ? entity.height/2 : entity.height;
agent.bot.lookAt(entity.position.offset(0, height, 0));
}
if (!entity_in_view)
this.last_entity = null;
if (Date.now() > this.next_change) {
// look in random direction
this.staring = Math.random() < 0.3;
if (!this.staring) {
const yaw = Math.random() * Math.PI * 2;
const pitch = (Math.random() * Math.PI/2) - Math.PI/4;
agent.bot.look(yaw, pitch, false);
}
this.next_change = Date.now() + Math.random() * 10000 + 2000;
}
}
else
this.active = false;
}
},
];
async function execute(mode, agent, func, timeout=-1) {
mode.active = true;
await agent.coder.stop();
agent.idle = false;
let code_return = await agent.coder.execute(async () => {
await func();
}, timeout);
mode.active = false;
agent.idle = true;
console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`);
}
class ModeController {
constructor(agent) {
this.agent = agent;
this.modes_list = modes;
this.modes_map = {};
for (let mode of this.modes_list) {
this.modes_map[mode.name] = mode;
}
}
exists(mode_name) {
return this.modes_map[mode_name] != null;
}
setOn(mode_name, on) {
this.modes_map[mode_name].on = on;
}
isOn(mode_name) {
return this.modes_map[mode_name].on;
}
pause(mode_name) {
this.modes_map[mode_name].paused = true;
}
getDocs() {
let res = 'Available Modes:';
for (let mode of this.modes_list) {
let on = mode.on ? 'ON' : 'OFF';
res += `\n- ${mode.name}(${on}): ${mode.description}`;
}
return res;
}
update() {
if (this.agent.idle) {
// other actions might pause a mode to override it
// when idle, unpause all modes
for (let mode of this.modes_list) {
if (mode.paused) console.log(`Unpausing mode ${mode.name}`);
mode.paused = false;
}
}
for (let mode of this.modes_list) {
if (mode.on && !mode.paused) {
mode.update(this.agent);
if (mode.active) {
break;
}
}
}
}
}
export function initModes(agent) {
// the mode controller is added to the bot object so it is accessible from anywhere the bot is used
agent.bot.modes = new ModeController(agent);
}

View file

@ -1,10 +1,12 @@
import { getItemId, getItemName } from "../utils/mcdata.js"; import { getItemId, getItemName } from "../utils/mcdata.js";
import { getNearestBlocks, getNearestBlock, getInventoryCounts, getInventoryStacks, getNearbyMobs, getNearbyBlocks } from "./world.js"; import { getNearestBlocks, getNearestBlock, getInventoryCounts, getNearestEntityWhere, getNearbyEntities, getNearbyBlocks } from "./world.js";
import pf from 'mineflayer-pathfinder'; import pf from 'mineflayer-pathfinder';
import Vec3 from 'vec3'; import Vec3 from 'vec3';
export function log(bot, message) { export function log(bot, message, chat=false) {
bot.output += message + '\n'; bot.output += message + '\n';
if (chat)
bot.chat(message);
} }
@ -175,7 +177,8 @@ function equipHighestAttack(bot) {
bot.equip(weapon, 'hand'); bot.equip(weapon, 'hand');
} }
export async function attackMob(bot, mobType, kill=true) {
export async function attackNearest(bot, mobType, kill=true) {
/** /**
* Attack mob of the given type. * Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot. * @param {MinecraftBot} bot, reference to the minecraft bot.
@ -187,7 +190,23 @@ export async function attackMob(bot, mobType, kill=true) {
**/ **/
const mob = bot.nearestEntity(entity => entity.name && entity.name.toLowerCase() === mobType.toLowerCase()); const mob = bot.nearestEntity(entity => entity.name && entity.name.toLowerCase() === mobType.toLowerCase());
if (mob) { if (mob) {
let pos = mob.position; return await attackEntity(bot, mob, kill);
}
log(bot, 'Could not find any '+mobType+' to attack.');
return false;
}
export async function attackEntity(bot, entity, kill=true) {
/**
* Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {Entity} entity, the entity to attack.
* @returns {Promise<boolean>} true if the entity was attacked, false if interrupted
* @example
* await skills.attackEntity(bot, entity);
**/
let pos = entity.position;
console.log(bot.entity.position.distanceTo(pos)) console.log(bot.entity.position.distanceTo(pos))
equipHighestAttack(bot) equipHighestAttack(bot)
@ -198,26 +217,59 @@ export async function attackMob(bot, mobType, kill=true) {
await goToPosition(bot, pos.x, pos.y, pos.z); await goToPosition(bot, pos.x, pos.y, pos.z);
} }
console.log('attacking mob...') console.log('attacking mob...')
await bot.attack(mob); await bot.attack(entity);
} }
else { else {
bot.pvp.attack(mob); bot.pvp.attack(entity);
while (getNearbyMobs(bot, 16).includes(mob)) { while (getNearbyEntities(bot, 16).includes(entity)) {
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
if (bot.interrupt_code) { if (bot.interrupt_code) {
bot.pvp.stop(); bot.pvp.stop();
return false; return false;
} }
} }
log(bot, `Successfully killed ${mobType}.`); log(bot, `Successfully killed ${entity.name}.`);
await pickupNearbyItem(bot); await pickupNearbyItem(bot);
return true; return true;
} }
} }
log(bot, 'Could not find any '+mobType+' to attack.');
export async function defendSelf(bot, range=8) {
/**
* Defend yourself from all nearby hostile mobs until there are no more.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} range, the range to look for mobs. Defaults to 8.
* @returns {Promise<boolean>} true if the bot found any enemies and has killed them, false if no entities were found.
* @example
* await skills.defendSelf(bot);
* **/
bot.modes.pause('self_defense');
let attacked = false;
let enemy = getNearestEntityWhere(bot, entity => isHostile(entity), range);
while (enemy) {
equipHighestAttack(bot);
if (bot.entity.position.distanceTo(enemy.position) > 4 && enemy.name !== 'creeper') {
try {
bot.pathfinder.setMovements(new pf.Movements(bot));
await bot.pathfinder.goto(new pf.goals.GoalFollow(enemy, 2), true);
} catch (err) {/* might error if entity dies, ignore */}
}
bot.pvp.attack(enemy);
attacked = true;
await new Promise(resolve => setTimeout(resolve, 500));
enemy = getNearestEntityWhere(bot, entity => isHostile(entity), range);
if (bot.interrupt_code) {
bot.pvp.stop();
return false; return false;
} }
}
if (attacked)
log(bot, `Successfully defended self.`);
else
log(bot, `No enemies nearby to defend self from.`);
return attacked;
}
export async function collectBlock(bot, blockType, num=1) { export async function collectBlock(bot, blockType, num=1) {
@ -252,6 +304,7 @@ export async function collectBlock(bot, blockType, num=1) {
try { try {
await bot.collectBlock.collect(block); await bot.collectBlock.collect(block);
collected++; collected++;
autoLight(bot);
} }
catch (err) { catch (err) {
if (err.name === 'NoChests') { if (err.name === 'NoChests') {
@ -263,6 +316,7 @@ export async function collectBlock(bot, blockType, num=1) {
continue; continue;
} }
} }
if (bot.interrupt_code) if (bot.interrupt_code)
break; break;
} }
@ -286,7 +340,7 @@ export async function pickupNearbyItem(bot) {
return false; return false;
} }
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
await bot.pathfinder.goto(new pf.goals.GoalNear(nearestItem.position.x, nearestItem.position.y, nearestItem.position.z, 1)); await bot.pathfinder.goto(new pf.goals.GoalFollow(nearestItem, 0.8), true);
log(bot, `Successfully picked up a dropped item.`); log(bot, `Successfully picked up a dropped item.`);
return true; return true;
} }
@ -347,41 +401,24 @@ export async function placeBlock(bot, blockType, x, y, z) {
log(bot, `Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`); log(bot, `Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`);
return false; return false;
} }
console.log("Placing on: ", buildOffBlock.position, buildOffBlock.name)
let block = bot.inventory.items().find(item => item.name === blockType); let block = bot.inventory.items().find(item => item.name === blockType);
if (!block) { if (!block) {
log(bot, `Don't have any ${blockType} to place.`); log(bot, `Don't have any ${blockType} to place.`);
return false; return false;
} }
const pos = bot.entity.position;
const pos_above = pos.plus(Vec3(0,1,0));
const dont_move_for = ['torch', 'redstone_torch', 'redstone', 'lever', 'button', 'rail', 'detector_rail', 'powered_rail', 'activator_rail', 'tripwire_hook', 'tripwire'];
if (!dont_move_for.includes(blockType) && (pos.distanceTo(targetBlock.position) < 1 || pos_above.distanceTo(targetBlock.position) < 1)) {
// too close // too close
let blockAbove = bot.blockAt(targetBlock.position.plus(Vec3(0,1,0))) let goal = new pf.goals.GoalNear(targetBlock.position.x, targetBlock.position.y, targetBlock.position.z, 2);
if (bot.entity.position.distanceTo(targetBlock.position) < 1 || bot.entity.position.distanceTo(blockAbove.position) < 1) { let inverted_goal = new pf.goals.GoalInvert(goal);
console.log('moving away from block...')
let found = false;
for(let i = 0; i < 10; i++) {
console.log('looking for block...')
const randomDirection = new Vec3((Math.random() > 0.5 ? 1 : -1), 0, (Math.random() > 0.5 ? 1 : -1));
const pos = targetBlock.position.add(randomDirection.scale(1.2));
if (bot.blockAt(pos).name === 'air') {
console.log('found good position')
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 1.2)); await bot.pathfinder.goto(inverted_goal);
found = true;
break;
} }
}
if (!found) {
console.log('could not find good position')
log(bot, `Was too close to place ${blockType} at ${targetBlock.position}.`)
return false;
}
}
// too far
if (bot.entity.position.distanceTo(targetBlock.position) > 4.5) { if (bot.entity.position.distanceTo(targetBlock.position) > 4.5) {
// move close until it is within 6 blocks // too far
console.log('moving closer to block...')
let pos = targetBlock.position; let pos = targetBlock.position;
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4)); await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
@ -390,9 +427,6 @@ export async function placeBlock(bot, blockType, x, y, z) {
await bot.equip(block, 'hand'); await bot.equip(block, 'hand');
await bot.lookAt(buildOffBlock.position); await bot.lookAt(buildOffBlock.position);
console.log("placing block...")
console.log('entities:', buildOffBlock.blockEntity, targetBlock.blockEntity)
// will throw error if an entity is in the way, and sometimes even if the block was placed // will throw error if an entity is in the way, and sometimes even if the block was placed
try { try {
await bot.placeBlock(buildOffBlock, faceVec); await bot.placeBlock(buildOffBlock, faceVec);
@ -507,6 +541,7 @@ export async function giveToPlayer(bot, itemType, username, num=1) {
return true; return true;
} }
export async function goToPosition(bot, x, y, z, min_distance=2) { export async function goToPosition(bot, x, y, z, min_distance=2) {
/** /**
* Navigate to the given position. * Navigate to the given position.
@ -540,6 +575,7 @@ export async function goToPlayer(bot, username) {
* @example * @example
* await skills.goToPlayer(bot, "player"); * await skills.goToPlayer(bot, "player");
**/ **/
bot.modes.pause('self_defense');
let player = bot.players[username].entity let player = bot.players[username].entity
if (!player) { if (!player) {
log(bot, `Could not find ${username}.`); log(bot, `Could not find ${username}.`);
@ -547,7 +583,7 @@ export async function goToPlayer(bot, username) {
} }
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
await bot.pathfinder.goto(new pf.goals.GoalFollow(player, 2), true); await bot.pathfinder.goto(new pf.goals.GoalFollow(player, 3), true);
log(bot, `You have reached ${username}.`); log(bot, `You have reached ${username}.`);
} }
@ -562,68 +598,45 @@ export async function followPlayer(bot, username) {
* @example * @example
* await skills.followPlayer(bot, "player"); * await skills.followPlayer(bot, "player");
**/ **/
bot.modes.pause('self_defense');
bot.modes.pause('hunting');
let player = bot.players[username].entity let player = bot.players[username].entity
if (!player) if (!player)
return false; return false;
bot.pathfinder.setMovements(new pf.Movements(bot)); const follow_distance = 4;
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 2), true); const attack_distance = 8;
log(bot, `You are now actively following player ${username}.`);
while (!bot.interrupt_code) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
return true;
}
export async function defendPlayer(bot, username) {
/**
* Defend the given player endlessly, attacking any nearby monsters. Will not return until the code is manually stopped.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to defend.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.defendPlayer(bot, "bob");
**/
let player = bot.players[username].entity
if (!player)
return false;
const follow_distance = 3;
const attack_distance = 12;
const return_distance = 16;
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, follow_distance), true); bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, follow_distance), true);
log(bot, `Actively defending player ${username}.`); log(bot, `You are now actively following player ${username}.`);
while (!bot.interrupt_code) { while (!bot.interrupt_code) {
if (bot.entity.position.distanceTo(player.position) < return_distance) { let acted = false;
const mobs = getNearbyMobs(bot, attack_distance).filter(mob => mob.type === 'mob' || mob.type === 'hostile'); if (bot.modes.isOn('self_defense')) {
const mob = mobs.sort((a, b) => a.position.distanceTo(player.position) - b.position.distanceTo(player.position))[0]; // get closest to player const enemy = getNearestEntityWhere(bot, entity => isHostile(entity), attack_distance);
if (mob) { if (enemy) {
bot.pathfinder.stop(); log(bot, `Found ${enemy.name}, attacking!`, true);
log(bot, `Found ${mob.name}, attacking!`); await defendSelf(bot, 8);
bot.chat(`Found ${mob.name}, attacking!`); acted = true;
equipHighestAttack(bot);
bot.pvp.attack(mob);
while (getNearbyMobs(bot, attack_distance).includes(mob)) {
await new Promise(resolve => setTimeout(resolve, 500));
console.log('attacking...')
if (bot.interrupt_code)
return;
if (bot.entity.position.distanceTo(player.position) > return_distance) {
console.log('stopping pvp...');
bot.pvp.stop();
break;
} }
} }
console.log('resuming pathfinder...') if (bot.modes.isOn('hunting')) {
const animal = getNearestEntityWhere(bot, entity => isHuntable(entity), attack_distance);
if (animal) {
log(bot, `Hunting ${animal.name}!`, true);
await attackEntity(bot, animal, true);
acted = true;
}
}
if (bot.entity.position.distanceTo(player.position) < follow_distance) {
acted = autoLight(bot);
}
if (acted) { // if we did something then resume following
bot.pathfinder.setMovements(new pf.Movements(bot)); bot.pathfinder.setMovements(new pf.Movements(bot));
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 5), true); bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, follow_distance), true);
await new Promise(resolve => setTimeout(resolve, 3000));
}
} }
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
} }
@ -631,6 +644,7 @@ export async function defendPlayer(bot, username) {
return true; return true;
} }
export async function goToBed(bot) { export async function goToBed(bot) {
/** /**
* Sleep in the nearest bed. * Sleep in the nearest bed.
@ -661,3 +675,32 @@ export async function goToBed(bot) {
log(bot, `You have woken up.`); log(bot, `You have woken up.`);
return true; return true;
} }
export function isHuntable(mob) {
const animals = ['chicken', 'cod', 'cow', 'llama', 'mooshroom', 'pig', 'pufferfish', 'rabbit', 'salmon', 'sheep', 'squid', 'tropical_fish', 'turtle'];
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
}
export function isHostile(mob) {
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
}
async function autoLight(bot) {
if (bot.modes.isOn('torch_placing') && !bot.interrupt_code) {
let nearest_torch = getNearestBlock(bot, 'torch', 8);
if (!nearest_torch) {
let has_torch = bot.inventory.items().find(item => item.name === 'torch');
if (has_torch) {
try {
log(bot, `Placing torch at ${bot.entity.position}.`);
await placeBlock(bot, 'torch', bot.entity.position.x, bot.entity.position.y, bot.entity.position.z);
return true;
} catch (err) {return true;}
}
}
}
return false;
}

View file

@ -63,8 +63,7 @@ export function getNearbyBlocks(bot, maxDistance) {
} }
export function getNearbyMobs(bot, maxDistance) { export function getNearbyEntities(bot, maxDistance=16) {
if (maxDistance == null) maxDistance = 16;
let entities = []; let entities = [];
for (const entity of Object.values(bot.entities)) { for (const entity of Object.values(bot.entities)) {
const distance = entity.position.distanceTo(bot.entity.position); const distance = entity.position.distanceTo(bot.entity.position);
@ -79,6 +78,10 @@ export function getNearbyMobs(bot, maxDistance) {
return res; return res;
} }
export function getNearestEntityWhere(bot, predicate, maxDistance=16) {
return bot.nearestEntity(entity => predicate(entity) && bot.entity.position.distanceTo(entity.position) < maxDistance);
}
export function getNearbyPlayers(bot, maxDistance) { export function getNearbyPlayers(bot, maxDistance) {
if (maxDistance == null) maxDistance = 16; if (maxDistance == null) maxDistance = 16;
@ -121,13 +124,15 @@ export function getInventoryCounts(bot) {
* let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0; * let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0;
**/ **/
let inventory = {}; let inventory = {};
for (const item of getInventoryStacks(bot)) { for (const item of bot.inventory.items()) {
if (inventory.hasOwnProperty(item.name)) { if (item != null) {
inventory[item.name] = inventory[item.name] + item.count; if (inventory[item.name] == null) {
} else { inventory[item.name] = 0;
inventory[item.name] = item.count; }
inventory[item.name] += item.count;
} }
} }
console.log(inventory)
return inventory; return inventory;
} }
@ -145,15 +150,15 @@ export function getPosition(bot) {
} }
export function getNearbyMobTypes(bot) { export function getNearbyEntityTypes(bot) {
/** /**
* Get a list of all nearby mob types. * Get a list of all nearby mob types.
* @param {Bot} bot - The bot to get nearby mobs for. * @param {Bot} bot - The bot to get nearby mobs for.
* @returns {string[]} - A list of all nearby mobs. * @returns {string[]} - A list of all nearby mobs.
* @example * @example
* let mobs = world.getNearbyMobTypes(bot); * let mobs = world.getNearbyEntityTypes(bot);
**/ **/
let mobs = getNearbyMobs(bot, 16); let mobs = getNearbyEntities(bot, 16);
let found = []; let found = [];
for (let i = 0; i < mobs.length; i++) { for (let i = 0; i < mobs.length; i++) {
if (!found.includes(mobs[i].name)) { if (!found.includes(mobs[i].name)) {

View file

@ -70,5 +70,15 @@
{"role": "assistant", "content": "!craftRecipe('stick', 4)"}, {"role": "assistant", "content": "!craftRecipe('stick', 4)"},
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."}, {"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 16 sticks!"} {"role": "assistant", "content": "I've crafted 16 sticks!"}
],
[
{"role": "user", "content": "brung00: build a house"},
{"role": "assistant", "content": "Sure, I'll try to build a house where I am. !newAction"}
],
[
{"role": "user", "content": "reter: place a crafting table"},
{"role": "assistant", "content": "Okay! !placeHere('crafting_table')"}
] ]
] ]

View file

@ -7,15 +7,21 @@ import { plugin as autoEat } from 'mineflayer-auto-eat';
import plugin from 'mineflayer-armor-manager'; import plugin from 'mineflayer-armor-manager';
const armorManager = plugin; const armorManager = plugin;
const mc_version = '1.19.3' const mc_version = '1.20.1'
const mcdata = minecraftData(mc_version); const mcdata = minecraftData(mc_version);
export function initBot(username) { export function initBot(username) {
let bot = createBot({ let bot = createBot({
username: username,
host: 'localhost', host: 'localhost',
port: 55916, port: 55916,
username: username,
// host: '000.111.222.333',
// port: 55920,
// auth: 'microsoft',
version: mc_version, version: mc_version,
}); });
bot.loadPlugin(pathfinder); bot.loadPlugin(pathfinder);