mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-22 14:12:07 +02:00
added modes, remove defend, fixed stuff
This commit is contained in:
parent
9329510c51
commit
e9abae74da
9 changed files with 463 additions and 144 deletions
|
@ -5,6 +5,7 @@ import { Examples } from './examples.js';
|
|||
import { Coder } from './coder.js';
|
||||
import { containsCommand, commandExists, executeCommand } from './commands.js';
|
||||
import { Events } from './events.js';
|
||||
import { initModes } from './modes.js';
|
||||
|
||||
|
||||
export class Agent {
|
||||
|
@ -20,7 +21,10 @@ export class Agent {
|
|||
|
||||
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 () => {
|
||||
|
||||
|
@ -49,7 +53,7 @@ export class Agent {
|
|||
this.bot.autoEat.options = {
|
||||
priority: 'foodPoints',
|
||||
startAt: 14,
|
||||
bannedFood: []
|
||||
bannedFood: ["rotten_flesh", "spider_eye", "poisonous_potato", "pufferfish", "chicken"]
|
||||
};
|
||||
|
||||
if (init_message) {
|
||||
|
@ -58,6 +62,8 @@ export class Agent {
|
|||
this.bot.chat('Hello world! I am ' + this.name);
|
||||
this.bot.emit('finished_executing');
|
||||
}
|
||||
|
||||
this.startUpdateLoop();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -117,4 +123,29 @@ export class Agent {
|
|||
this.history.save();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ export class Coder {
|
|||
|
||||
|
||||
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 += "\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',
|
||||
content: code_return.message
|
||||
});
|
||||
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return;
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as skills from '../skills.js';
|
||||
import * as world from '../world.js';
|
||||
|
||||
function wrapExecution(func) {
|
||||
function wrapExecution(func, timeout=-1) {
|
||||
return async function (agent, ...args) {
|
||||
agent.idle = false;
|
||||
let code_return = await agent.coder.execute(async () => {
|
||||
await func(agent, ...args);
|
||||
}, -1); // no timeout
|
||||
}, timeout);
|
||||
if (code_return.interrupted && !code_return.timedout)
|
||||
return;
|
||||
agent.idle = true;
|
||||
return code_return.message;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +19,9 @@ export const actionsList = [
|
|||
name: '!newAction',
|
||||
description: 'Perform new and unknown custom behaviors that are not available as a command by writing code.',
|
||||
perform: async function (agent) {
|
||||
agent.idle = false;
|
||||
let res = await agent.coder.generateCode(agent.history);
|
||||
agent.idle = true;
|
||||
if (res)
|
||||
return '\n' + res + '\n';
|
||||
}
|
||||
|
@ -27,9 +31,28 @@ export const actionsList = [
|
|||
description: 'Force stop all actions and commands that are currently executing.',
|
||||
perform: async function (agent) {
|
||||
await agent.coder.stop();
|
||||
agent.coder.clear();
|
||||
agent.idle = true;
|
||||
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',
|
||||
description: 'Go to the given player. Ex: !goToPlayer("steve")',
|
||||
|
@ -40,7 +63,7 @@ export const actionsList = [
|
|||
},
|
||||
{
|
||||
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.'},
|
||||
perform: wrapExecution(async (agent, player_name) => {
|
||||
await skills.followPlayer(agent.bot, player_name);
|
||||
|
@ -55,7 +78,7 @@ export const actionsList = [
|
|||
},
|
||||
perform: wrapExecution(async (agent, type, num) => {
|
||||
await skills.collectBlock(agent.bot, type, num);
|
||||
})
|
||||
}, 10) // 10 minute timeout
|
||||
},
|
||||
{
|
||||
name: '!craftRecipe',
|
||||
|
@ -87,14 +110,6 @@ export const actionsList = [
|
|||
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',
|
||||
description: 'Go to the nearest bed and sleep.',
|
||||
|
|
|
@ -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';
|
||||
|
||||
const pad = (str) => {
|
||||
|
@ -19,6 +19,7 @@ export const queryList = [
|
|||
res += `\n- Health: ${Math.round(bot.health)} / 20`;
|
||||
res += `\n- Hunger: ${Math.round(bot.food)} / 20`;
|
||||
res += `\n- Biome: ${getBiomeName(bot)}`;
|
||||
res += `\n- Weather: ${bot.weather}`;
|
||||
// let block = bot.blockAt(pos);
|
||||
// res += `\n- Artficial light: ${block.skyLight}`;
|
||||
// res += `\n- Sky light: ${block.light}`;
|
||||
|
@ -95,7 +96,7 @@ export const queryList = [
|
|||
for (const entity of getNearbyPlayerNames(bot)) {
|
||||
res += `\n- player: ${entity}`;
|
||||
}
|
||||
for (const entity of getNearbyMobTypes(bot)) {
|
||||
for (const entity of getNearbyEntityTypes(bot)) {
|
||||
res += `\n- mob: ${entity}`;
|
||||
}
|
||||
if (res == 'NEARBY_ENTITIES') {
|
||||
|
@ -104,11 +105,18 @@ export const queryList = [
|
|||
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",
|
||||
description: "Get the currently executing code.",
|
||||
perform: function (agent) {
|
||||
return pad("Current code:\n`" + agent.coder.current_code +"`");
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
198
src/agent/modes.js
Normal file
198
src/agent/modes.js
Normal 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);
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
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 Vec3 from 'vec3';
|
||||
|
||||
export function log(bot, message) {
|
||||
export function log(bot, message, chat=false) {
|
||||
bot.output += message + '\n';
|
||||
if (chat)
|
||||
bot.chat(message);
|
||||
}
|
||||
|
||||
|
||||
|
@ -175,7 +177,8 @@ function equipHighestAttack(bot) {
|
|||
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.
|
||||
* @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());
|
||||
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))
|
||||
|
||||
equipHighestAttack(bot)
|
||||
|
@ -198,26 +217,59 @@ export async function attackMob(bot, mobType, kill=true) {
|
|||
await goToPosition(bot, pos.x, pos.y, pos.z);
|
||||
}
|
||||
console.log('attacking mob...')
|
||||
await bot.attack(mob);
|
||||
await bot.attack(entity);
|
||||
}
|
||||
else {
|
||||
bot.pvp.attack(mob);
|
||||
while (getNearbyMobs(bot, 16).includes(mob)) {
|
||||
bot.pvp.attack(entity);
|
||||
while (getNearbyEntities(bot, 16).includes(entity)) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (bot.interrupt_code) {
|
||||
bot.pvp.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
log(bot, `Successfully killed ${mobType}.`);
|
||||
log(bot, `Successfully killed ${entity.name}.`);
|
||||
await pickupNearbyItem(bot);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
@ -252,6 +304,7 @@ export async function collectBlock(bot, blockType, num=1) {
|
|||
try {
|
||||
await bot.collectBlock.collect(block);
|
||||
collected++;
|
||||
autoLight(bot);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.name === 'NoChests') {
|
||||
|
@ -263,6 +316,7 @@ export async function collectBlock(bot, blockType, num=1) {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.interrupt_code)
|
||||
break;
|
||||
}
|
||||
|
@ -286,7 +340,7 @@ export async function pickupNearbyItem(bot) {
|
|||
return false;
|
||||
}
|
||||
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.`);
|
||||
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.`);
|
||||
return false;
|
||||
}
|
||||
console.log("Placing on: ", buildOffBlock.position, buildOffBlock.name)
|
||||
|
||||
let block = bot.inventory.items().find(item => item.name === blockType);
|
||||
if (!block) {
|
||||
log(bot, `Don't have any ${blockType} to place.`);
|
||||
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
|
||||
let blockAbove = bot.blockAt(targetBlock.position.plus(Vec3(0,1,0)))
|
||||
if (bot.entity.position.distanceTo(targetBlock.position) < 1 || bot.entity.position.distanceTo(blockAbove.position) < 1) {
|
||||
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')
|
||||
let goal = new pf.goals.GoalNear(targetBlock.position.x, targetBlock.position.y, targetBlock.position.z, 2);
|
||||
let inverted_goal = new pf.goals.GoalInvert(goal);
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 1.2));
|
||||
found = true;
|
||||
break;
|
||||
await bot.pathfinder.goto(inverted_goal);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// move close until it is within 6 blocks
|
||||
console.log('moving closer to block...')
|
||||
// too far
|
||||
let pos = targetBlock.position;
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
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.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
|
||||
try {
|
||||
await bot.placeBlock(buildOffBlock, faceVec);
|
||||
|
@ -507,6 +541,7 @@ export async function giveToPlayer(bot, itemType, username, num=1) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
export async function goToPosition(bot, x, y, z, min_distance=2) {
|
||||
/**
|
||||
* Navigate to the given position.
|
||||
|
@ -540,6 +575,7 @@ export async function goToPlayer(bot, username) {
|
|||
* @example
|
||||
* await skills.goToPlayer(bot, "player");
|
||||
**/
|
||||
bot.modes.pause('self_defense');
|
||||
let player = bot.players[username].entity
|
||||
if (!player) {
|
||||
log(bot, `Could not find ${username}.`);
|
||||
|
@ -547,7 +583,7 @@ export async function goToPlayer(bot, username) {
|
|||
}
|
||||
|
||||
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}.`);
|
||||
}
|
||||
|
@ -562,68 +598,45 @@ export async function followPlayer(bot, username) {
|
|||
* @example
|
||||
* await skills.followPlayer(bot, "player");
|
||||
**/
|
||||
bot.modes.pause('self_defense');
|
||||
bot.modes.pause('hunting');
|
||||
|
||||
let player = bot.players[username].entity
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 2), true);
|
||||
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;
|
||||
const follow_distance = 4;
|
||||
const attack_distance = 8;
|
||||
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
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) {
|
||||
if (bot.entity.position.distanceTo(player.position) < return_distance) {
|
||||
const mobs = getNearbyMobs(bot, attack_distance).filter(mob => mob.type === 'mob' || mob.type === 'hostile');
|
||||
const mob = mobs.sort((a, b) => a.position.distanceTo(player.position) - b.position.distanceTo(player.position))[0]; // get closest to player
|
||||
if (mob) {
|
||||
bot.pathfinder.stop();
|
||||
log(bot, `Found ${mob.name}, attacking!`);
|
||||
bot.chat(`Found ${mob.name}, attacking!`);
|
||||
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;
|
||||
let acted = false;
|
||||
if (bot.modes.isOn('self_defense')) {
|
||||
const enemy = getNearestEntityWhere(bot, entity => isHostile(entity), attack_distance);
|
||||
if (enemy) {
|
||||
log(bot, `Found ${enemy.name}, attacking!`, true);
|
||||
await defendSelf(bot, 8);
|
||||
acted = true;
|
||||
}
|
||||
}
|
||||
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.setGoal(new pf.goals.GoalFollow(player, 5), true);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
}
|
||||
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, follow_distance), true);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
@ -631,6 +644,7 @@ export async function defendPlayer(bot, username) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
export async function goToBed(bot) {
|
||||
/**
|
||||
* Sleep in the nearest bed.
|
||||
|
@ -661,3 +675,32 @@ export async function goToBed(bot) {
|
|||
log(bot, `You have woken up.`);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -63,8 +63,7 @@ export function getNearbyBlocks(bot, maxDistance) {
|
|||
}
|
||||
|
||||
|
||||
export function getNearbyMobs(bot, maxDistance) {
|
||||
if (maxDistance == null) maxDistance = 16;
|
||||
export function getNearbyEntities(bot, maxDistance=16) {
|
||||
let entities = [];
|
||||
for (const entity of Object.values(bot.entities)) {
|
||||
const distance = entity.position.distanceTo(bot.entity.position);
|
||||
|
@ -79,6 +78,10 @@ export function getNearbyMobs(bot, maxDistance) {
|
|||
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) {
|
||||
if (maxDistance == null) maxDistance = 16;
|
||||
|
@ -121,13 +124,15 @@ export function getInventoryCounts(bot) {
|
|||
* let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0;
|
||||
**/
|
||||
let inventory = {};
|
||||
for (const item of getInventoryStacks(bot)) {
|
||||
if (inventory.hasOwnProperty(item.name)) {
|
||||
inventory[item.name] = inventory[item.name] + item.count;
|
||||
} else {
|
||||
inventory[item.name] = item.count;
|
||||
for (const item of bot.inventory.items()) {
|
||||
if (item != null) {
|
||||
if (inventory[item.name] == null) {
|
||||
inventory[item.name] = 0;
|
||||
}
|
||||
inventory[item.name] += item.count;
|
||||
}
|
||||
}
|
||||
console.log(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.
|
||||
* @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 = world.getNearbyEntityTypes(bot);
|
||||
**/
|
||||
let mobs = getNearbyMobs(bot, 16);
|
||||
let mobs = getNearbyEntities(bot, 16);
|
||||
let found = [];
|
||||
for (let i = 0; i < mobs.length; i++) {
|
||||
if (!found.includes(mobs[i].name)) {
|
||||
|
|
|
@ -70,5 +70,15 @@
|
|||
{"role": "assistant", "content": "!craftRecipe('stick', 4)"},
|
||||
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
|
||||
{"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')"}
|
||||
]
|
||||
]
|
|
@ -7,15 +7,21 @@ import { plugin as autoEat } from 'mineflayer-auto-eat';
|
|||
import plugin from 'mineflayer-armor-manager';
|
||||
const armorManager = plugin;
|
||||
|
||||
const mc_version = '1.19.3'
|
||||
const mc_version = '1.20.1'
|
||||
const mcdata = minecraftData(mc_version);
|
||||
|
||||
|
||||
export function initBot(username) {
|
||||
let bot = createBot({
|
||||
username: username,
|
||||
|
||||
host: 'localhost',
|
||||
port: 55916,
|
||||
username: username,
|
||||
|
||||
// host: '000.111.222.333',
|
||||
// port: 55920,
|
||||
// auth: 'microsoft',
|
||||
|
||||
version: mc_version,
|
||||
});
|
||||
bot.loadPlugin(pathfinder);
|
||||
|
|
Loading…
Add table
Reference in a new issue