mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-09-10 12:02:59 +02:00
Merge branch 'main' into default-behaviors
This commit is contained in:
commit
4ae9ccdcc2
9 changed files with 141 additions and 59 deletions
|
@ -75,6 +75,10 @@ export class Agent {
|
|||
|
||||
const user_command_name = containsCommand(message);
|
||||
if (user_command_name) {
|
||||
if (!commandExists(user_command_name)) {
|
||||
this.bot.chat(`Command '${user_command_name}' does not exist.`);
|
||||
return;
|
||||
}
|
||||
this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`);
|
||||
let execute_res = await executeCommand(this, message);
|
||||
if (user_command_name === '!newAction') {
|
||||
|
@ -164,15 +168,23 @@ export class Agent {
|
|||
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.bot.on('idle', () => {
|
||||
this.coder.executeDefault();
|
||||
this.bot.modes.unPauseAll();
|
||||
this.coder.executeResume();
|
||||
});
|
||||
|
||||
// set interval every 300ms to update the bot's state
|
||||
this.update_interval = setInterval(async () => {
|
||||
this.bot.modes.update();
|
||||
}, 300);
|
||||
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
|
||||
const INTERVAL = 300;
|
||||
setTimeout(async () => {
|
||||
while (true) {
|
||||
let start = Date.now();
|
||||
await this.bot.modes.update();
|
||||
let remaining = INTERVAL - (Date.now() - start);
|
||||
if (remaining > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, remaining));
|
||||
}
|
||||
}
|
||||
}, INTERVAL);
|
||||
}
|
||||
|
||||
isIdle() {
|
||||
|
|
|
@ -13,8 +13,8 @@ export class Coder {
|
|||
this.generating = false;
|
||||
this.code_template = '';
|
||||
this.timedout = false;
|
||||
this.default_func = null;
|
||||
this.default_name = null;
|
||||
this.resume_func = null;
|
||||
this.resume_name = null;
|
||||
this.interruptible = false;
|
||||
}
|
||||
|
||||
|
@ -160,14 +160,15 @@ export class Coder {
|
|||
return {success: false, message: null, interrupted: false, timedout: true};
|
||||
}
|
||||
|
||||
async executeDefault(func=null, name=null, timeout=10) {
|
||||
async executeResume(func=null, name=null, timeout=10) {
|
||||
console.log('resuming code...')
|
||||
if (func != null) {
|
||||
this.default_func = func;
|
||||
this.default_name = name;
|
||||
this.resume_func = func;
|
||||
this.resume_name = name;
|
||||
}
|
||||
if (this.default_func != null) {
|
||||
if (this.resume_func != null) {
|
||||
this.interruptible = true;
|
||||
let res = await this.execute(this.default_func, timeout);
|
||||
let res = await this.execute(this.resume_func, timeout);
|
||||
this.interruptible = false;
|
||||
return res;
|
||||
} else {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import * as skills from '../library/skills.js';
|
||||
|
||||
|
||||
function wrapExecution(func, timeout=-1, default_name=null) {
|
||||
function wrapExecution(func, timeout=-1, resume_name=null) {
|
||||
return async function (agent, ...args) {
|
||||
let code_return;
|
||||
if (default_name != null) {
|
||||
code_return = await agent.coder.executeDefault(async () => {
|
||||
if (resume_name != null) {
|
||||
code_return = await agent.coder.executeResume(async () => {
|
||||
await func(agent, ...args);
|
||||
}, default_name, timeout);
|
||||
}, resume_name, timeout);
|
||||
} else {
|
||||
code_return = await agent.coder.execute(async () => {
|
||||
await func(agent, ...args);
|
||||
|
@ -33,11 +33,26 @@ export const actionsList = [
|
|||
perform: async function (agent) {
|
||||
await agent.coder.stop();
|
||||
agent.coder.clear();
|
||||
agent.coder.default_func = null;
|
||||
agent.coder.default_name = null;
|
||||
agent.coder.resume_func = null;
|
||||
agent.coder.resume_name = null;
|
||||
return 'Agent stopped.';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '!restart',
|
||||
description: 'Restart the agent process.',
|
||||
perform: async function (agent) {
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '!clear',
|
||||
description: 'Clear the chat history.',
|
||||
perform: async function (agent) {
|
||||
agent.history.clear();
|
||||
return agent.name + "'s chat history was cleared, starting new conversation from scratch.";
|
||||
}
|
||||
},
|
||||
{
|
||||
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)',
|
||||
|
|
|
@ -112,4 +112,9 @@ export class History {
|
|||
console.error(`No file for profile '${load_path}' for agent ${this.name}.`);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.turns = [];
|
||||
this.memory = '';
|
||||
}
|
||||
}
|
|
@ -331,8 +331,12 @@ export async function collectBlock(bot, blockType, num=1) {
|
|||
log(bot, `Invalid number of blocks to collect: ${num}.`);
|
||||
return false;
|
||||
}
|
||||
let blocktypes = [blockType];
|
||||
if (blockType.endsWith('ore'))
|
||||
blocktypes.push('deepslate_'+blockType);
|
||||
|
||||
let collected = 0;
|
||||
const blocks = world.getNearestBlocks(bot, blockType, 64, num);
|
||||
const blocks = world.getNearestBlocks(bot, blocktypes, 64, num);
|
||||
if (blocks.length === 0) {
|
||||
log(bot, `Could not find any ${blockType} to collect.`);
|
||||
return false;
|
||||
|
@ -369,7 +373,7 @@ export async function collectBlock(bot, blockType, num=1) {
|
|||
return true;
|
||||
}
|
||||
|
||||
export async function pickupNearbyItem(bot) {
|
||||
export async function pickupNearbyItems(bot) {
|
||||
/**
|
||||
* Pick up all nearby items.
|
||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||
|
@ -377,16 +381,22 @@ export async function pickupNearbyItem(bot) {
|
|||
* @example
|
||||
* await skills.pickupNearbyItem(bot);
|
||||
**/
|
||||
const distance = 10;
|
||||
let nearestItem = bot.nearestEntity(entity => entity.name === 'item' && bot.entity.position.distanceTo(entity.position) < distance);
|
||||
|
||||
if (!nearestItem) {
|
||||
log(bot, `Didn't pick up items.`);
|
||||
return false;
|
||||
const distance = 8;
|
||||
const getNearestItem = bot => bot.nearestEntity(entity => entity.name === 'item' && bot.entity.position.distanceTo(entity.position) < distance);
|
||||
let nearestItem = getNearestItem(bot);
|
||||
let pickedUp = 0;
|
||||
while (nearestItem) {
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalFollow(nearestItem, 0.8), true);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
let prev = nearestItem;
|
||||
nearestItem = getNearestItem(bot);
|
||||
if (prev === nearestItem) {
|
||||
break;
|
||||
}
|
||||
pickedUp++;
|
||||
}
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalFollow(nearestItem, 0.8), true);
|
||||
log(bot, `Successfully picked up a dropped item.`);
|
||||
log(bot, `Picked up ${pickedUp} items.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import pf from 'mineflayer-pathfinder';
|
||||
import { getAllBlockIds } from '../../utils/mcdata.js';
|
||||
|
||||
|
||||
|
@ -48,9 +49,12 @@ export function getNearestBlocks(bot, block_types, distance=16, count=1) {
|
|||
* @example
|
||||
* let woodBlocks = world.getNearestBlocks(bot, ['oak_log', 'birch_log'], 16, 1);
|
||||
**/
|
||||
// if blocktypes is not a list, make it a list
|
||||
if (!Array.isArray(block_types))
|
||||
block_types = [block_types];
|
||||
let block_locs = bot.findBlocks({
|
||||
matching: (block) => {
|
||||
return block && block_types.includes(block.name);
|
||||
return block && block_types.some(name => name === block.name);
|
||||
},
|
||||
maxDistance: distance,
|
||||
count: count
|
||||
|
@ -243,6 +247,20 @@ export function getNearbyBlockTypes(bot, distance=16) {
|
|||
return found;
|
||||
}
|
||||
|
||||
export async function isClearPath(bot, target) {
|
||||
/**
|
||||
* Check if there is a path to the target that requires no digging or placing blocks.
|
||||
* @param {Bot} bot - The bot to get the path for.
|
||||
* @param {Entity} target - The target to path to.
|
||||
* @returns {boolean} - True if there is a clear path, false otherwise.
|
||||
*/
|
||||
let movements = new pf.Movements(bot)
|
||||
movements.canDig = false;
|
||||
movements.canPlaceOn = false;
|
||||
let goal = new pf.goals.GoalNear(target.position.x, target.position.y, target.position.z, 1);
|
||||
let path = await bot.pathfinder.getPathTo(movements, goal, 100);
|
||||
return path.status === 'success';
|
||||
}
|
||||
|
||||
export function getBiomeName(bot) {
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,10 @@ import * as mc from '../utils/mcdata.js';
|
|||
// 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
|
||||
// while update functions are async, they should *not* be awaited longer than ~100ms as it will block the update loop
|
||||
// to perform longer actions, use the execute function which won't block the update loop
|
||||
const modes = [
|
||||
{
|
||||
name: 'self_defense',
|
||||
|
@ -18,12 +21,12 @@ const modes = [
|
|||
interrupts: ['all'],
|
||||
on: true,
|
||||
active: false,
|
||||
update: function (agent) {
|
||||
const enemy = world.getNearestEntityWhere(agent.bot, entity => mc.isHostile(entity), 8);
|
||||
if (enemy) {
|
||||
update: async function (agent) {
|
||||
const enemy = world.getNearestEntityWhere(agent.bot, entity => mc.isHostile(entity), 9);
|
||||
if (enemy && await world.isClearPath(agent.bot, enemy)) {
|
||||
agent.bot.chat(`Fighting ${enemy.name}!`);
|
||||
execute(this, agent, async () => {
|
||||
await skills.defendSelf(agent.bot, 8);
|
||||
await skills.defendSelf(agent.bot, 9);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +37,9 @@ const modes = [
|
|||
interrupts: ['defaults'],
|
||||
on: true,
|
||||
active: false,
|
||||
update: function (agent) {
|
||||
update: async function (agent) {
|
||||
const huntable = world.getNearestEntityWhere(agent.bot, entity => mc.isHuntable(entity), 8);
|
||||
if (huntable) {
|
||||
if (huntable && await world.isClearPath(agent.bot, huntable)) {
|
||||
execute(this, agent, async () => {
|
||||
agent.bot.chat(`Hunting ${huntable.name}!`);
|
||||
await skills.attackEntity(agent.bot, huntable);
|
||||
|
@ -50,14 +53,24 @@ const modes = [
|
|||
interrupts: ['followPlayer'],
|
||||
on: true,
|
||||
active: false,
|
||||
update: function (agent) {
|
||||
|
||||
wait: 2, // number of seconds to wait after noticing an item to pick it up
|
||||
noticedAt: -1,
|
||||
update: async function (agent) {
|
||||
let item = world.getNearestEntityWhere(agent.bot, entity => entity.name === 'item', 8);
|
||||
if (item) {
|
||||
execute(this, agent, async () => {
|
||||
// wait 2 seconds for the item to settle
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
await skills.pickupNearbyItem(agent.bot);
|
||||
});
|
||||
if (item && await world.isClearPath(agent.bot, item)) {
|
||||
if (this.noticedAt === -1) {
|
||||
this.noticedAt = Date.now();
|
||||
}
|
||||
if (Date.now() - this.noticedAt > this.wait * 1000) {
|
||||
agent.bot.chat(`Picking up ${item.name}!`);
|
||||
execute(this, agent, async () => {
|
||||
await skills.pickupNearbyItems(agent.bot);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.noticedAt = -1;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -165,20 +178,22 @@ class ModeController {
|
|||
return res;
|
||||
}
|
||||
|
||||
update() {
|
||||
unPauseAll() {
|
||||
for (let mode of this.modes_list) {
|
||||
if (mode.paused) console.log(`Unpausing mode ${mode.name}`);
|
||||
mode.paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
if (this.agent.isIdle()) {
|
||||
// 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;
|
||||
}
|
||||
this.unPauseAll();
|
||||
}
|
||||
for (let mode of this.modes_list) {
|
||||
let available = mode.interrupts.includes('all') || this.agent.isIdle();
|
||||
let interruptible = this.agent.coder.interruptible && (mode.interrupts.includes('defaults') || mode.interrupts.includes(this.agent.coder.default_name));
|
||||
let interruptible = this.agent.coder.interruptible && (mode.interrupts.includes('defaults') || mode.interrupts.includes(this.agent.coder.resume_name));
|
||||
if (mode.on && !mode.paused && !mode.active && (available || interruptible)) {
|
||||
mode.update(this.agent);
|
||||
await mode.update(this.agent);
|
||||
}
|
||||
if (mode.active) break;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,9 @@
|
|||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "reter: place a crafting table"},
|
||||
{"role": "assistant", "content": "Okay! !placeHere('crafting_table')"}
|
||||
{"role": "user", "content": "umpire_man: follow me without hunting"},
|
||||
{"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"},
|
||||
{"role": "system", "content": "'hunting' mode disabled."},
|
||||
{"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"}
|
||||
]
|
||||
]
|
|
@ -6,10 +6,10 @@
|
|||
{"role": "assistant", "content": "I collected 9 oak logs, what next?"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "bobby: Craft a plank"},
|
||||
{"role": "assistant", "content": "```\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"},
|
||||
{"role": "system", "content": "Successfully crafted 4 spruce_planks."},
|
||||
{"role": "assistant", "content": "I crafted 4 spruce planks!"}
|
||||
{"role": "user", "content": "bobby: cook some chicken"},
|
||||
{"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
|
||||
{"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
|
||||
{"role": "assistant", "content": "I have cooked 8 chicken."}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "zZZn98: come here"},
|
||||
|
@ -30,5 +30,9 @@
|
|||
{"role": "assistant", "content": "```let pos = bot.entity.position;\nfor (let i = 0; i < 5; i++) {\n await skills.placeBlock(bot, 'dirt', pos.x, pos.y + i, pos.z);\n}\n```"},
|
||||
{"role": "system", "content": "Successfully placed 5 dirt."},
|
||||
{"role": "assistant", "content": "I built a little tower!"}
|
||||
],
|
||||
[
|
||||
{"role": "user", "content": "brug: build a dirt house"},
|
||||
{"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n// place a torch in the middle of the room\nawait skills.placeBlock(bot, 'torch', startX + width / 2, startY + height / 2, startZ + 1);```"}
|
||||
]
|
||||
]
|
Loading…
Add table
Reference in a new issue