feat: skills

This commit is contained in:
RainbowBird
2025-01-07 01:09:51 +08:00
parent fa3a0fef53
commit b0dd132fbf
10 changed files with 1918 additions and 1 deletions
+583
View File
@@ -0,0 +1,583 @@
import type { BotContext } from 'src/composables/bot'
import { z } from 'zod'
import * as skills from '../skills'
function runAsAction(actionFn, resume = false, timeout = -1) {
let actionLabel = null // Will be set on first use
const wrappedAction = async function (agent, ...args) {
// Set actionLabel only once, when the action is first created
if (!actionLabel) {
const actionObj = actionsList.find(a => a.perform === wrappedAction)
actionLabel = actionObj.name.substring(1) // Remove the ! prefix
}
const actionFnWithAgent = async () => {
await actionFn(agent, ...args)
}
const code_return = await agent.actions.runAction(`action:${actionLabel}`, actionFnWithAgent, { timeout, resume })
if (code_return.interrupted && !code_return.timedout)
return
return code_return.message
}
return wrappedAction
}
type ActionResult = string | Promise<string>
interface Action {
readonly name: string
readonly description: string
readonly schema: z.ZodObject<any>
readonly perform: (ctx: BotContext) => () => ActionResult
}
function getNewAction(): Action {
return {
name: '!newAction',
description: 'Perform new and unknown custom behaviors that are not available as a command.',
schema: z.object({
prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'),
}),
perform: (agent: BotContext) => async () => {
if (!settings.allow_insecure_coding)
return 'newAction not allowed! Code writing is disabled in settings. Notify the user.'
return await agent.coder.generateCode(agent.history)
},
}
}
function getStopAction(): Action {
return {
name: '!stop',
description: 'Force stop all actions and commands that are currently executing.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
await agent.actions.stop()
agent.clearBotLogs()
agent.actions.cancelResume()
agent.bot.emit('idle')
let msg = 'Agent stopped.'
if (agent.self_prompter.on)
msg += ' Self-prompting still active.'
return msg
},
}
}
function getStfuAction(): Action {
return {
name: '!stfu',
description: 'Stop all chatting and self prompting, but continue current action.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.openChat('Shutting up.')
agent.shutUp()
},
}
}
function getRestartAction(): Action {
return {
name: '!restart',
description: 'Restart the agent process.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.cleanKill()
},
}
}
function getClearChatAction(): Action {
return {
name: '!clearChat',
description: 'Clear the chat history.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.history.clear()
return `${agent.name}'s chat history was cleared, starting new conversation from scratch.`
},
}
}
function getGoToPlayerAction(): Action {
return {
name: '!goToPlayer',
description: 'Go to the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to go to.'),
closeness: z.number().describe('How close to get to the player.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, closeness) => {
return await skills.goToPlayer(agent.bot, player_name, closeness)
}),
}
}
function getFollowPlayerAction(): Action {
return {
name: '!followPlayer',
description: 'Endlessly follow the given player.',
schema: z.object({
player_name: z.string().describe('name of the player to follow.'),
follow_dist: z.number().describe('The distance to follow from.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, follow_dist) => {
await skills.followPlayer(agent.bot, player_name, follow_dist)
}, true),
}
}
function getGoToCoordinatesAction(): Action {
return {
name: '!goToCoordinates',
description: 'Go to the given x, y, z location.',
schema: z.object({
x: z.number().describe('The x coordinate.'),
y: z.number().describe('The y coordinate.').min(-64).max(320),
z: z.number().describe('The z coordinate.'),
closeness: z.number().describe('How close to get to the location.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, x, y, z, closeness) => {
await skills.goToPosition(agent.bot, x, y, z, closeness)
}),
}
}
function getSearchForBlockAction(): Action {
return {
name: '!searchForBlock',
description: 'Find and go to the nearest block of a given type in a given range.',
schema: z.object({
type: z.string().describe('The block type to go to.'),
search_range: z.number().describe('The range to search for the block.').min(32).max(512),
}),
perform: (agent: BotContext) => runAsAction(async (agent, block_type, range) => {
await skills.goToNearestBlock(agent.bot, block_type, 4, range)
}),
}
}
function getSearchForEntityAction(): Action {
return {
name: '!searchForEntity',
description: 'Find and go to the nearest entity of a given type in a given range.',
schema: z.object({
type: z.string().describe('The type of entity to go to.'),
search_range: z.number().describe('The range to search for the entity.').min(32).max(512),
}),
perform: (agent: BotContext) => runAsAction(async (agent, entity_type, range) => {
await skills.goToNearestEntity(agent.bot, entity_type, 4, range)
}),
}
}
function getMoveAwayAction(): Action {
return {
name: '!moveAway',
description: 'Move away from the current location in any direction by a given distance.',
schema: z.object({
distance: z.number().describe('The distance to move away.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, distance) => {
await skills.moveAway(agent.bot, distance)
}),
}
}
function getRememberHereAction(): Action {
return {
name: '!rememberHere',
description: 'Save the current location with a given name.',
schema: z.object({
name: z.string().describe('The name to remember the location as.'),
}),
perform: (agent: BotContext) => async (name: string) => {
const pos = agent.bot.entity.position
agent.memory_bank.rememberPlace(name, pos.x, pos.y, pos.z)
return `Location saved as "${name}".`
},
}
}
function getGoToRememberedPlaceAction(): Action {
return {
name: '!goToRememberedPlace',
description: 'Go to a saved location.',
schema: z.object({
name: z.string().describe('The name of the location to go to.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, name) => {
const pos = agent.memory_bank.recallPlace(name)
if (!pos) {
skills.log(agent.bot, `No location named "${name}" saved.`)
return
}
await skills.goToPosition(agent.bot, pos[0], pos[1], pos[2], 1)
}),
}
}
function getGivePlayerAction(): Action {
return {
name: '!givePlayer',
description: 'Give the specified item to the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to give the item to.'),
item_name: z.string().describe('The name of the item to give.'),
num: z.number().int().describe('The number of items to give.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, item_name, num) => {
await skills.giveToPlayer(agent.bot, item_name, player_name, num)
}),
}
}
function getConsumeAction(): Action {
return {
name: '!consume',
description: 'Eat/drink the given item.',
schema: z.object({
item_name: z.string().describe('The name of the item to consume.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name) => {
await skills.consume(agent.bot, item_name)
}),
}
}
function getEquipAction(): Action {
return {
name: '!equip',
description: 'Equip the given item.',
schema: z.object({
item_name: z.string().describe('The name of the item to equip.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name) => {
await skills.equip(agent.bot, item_name)
}),
}
}
function getPutInChestAction(): Action {
return {
name: '!putInChest',
description: 'Put the given item in the nearest chest.',
schema: z.object({
item_name: z.string().describe('The name of the item to put in the chest.'),
num: z.number().int().describe('The number of items to put in the chest.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
await skills.putInChest(agent.bot, item_name, num)
}),
}
}
function getTakeFromChestAction(): Action {
return {
name: '!takeFromChest',
description: 'Take the given items from the nearest chest.',
schema: z.object({
item_name: z.string().describe('The name of the item to take.'),
num: z.number().int().describe('The number of items to take.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
await skills.takeFromChest(agent.bot, item_name, num)
}),
}
}
function getViewChestAction(): Action {
return {
name: '!viewChest',
description: 'View the items/counts of the nearest chest.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.viewChest(agent.bot)
}),
}
}
function getDiscardAction(): Action {
return {
name: '!discard',
description: 'Discard the given item from the inventory.',
schema: z.object({
item_name: z.string().describe('The name of the item to discard.'),
num: z.number().int().describe('The number of items to discard.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
const start_loc = agent.bot.entity.position
await skills.moveAway(agent.bot, 5)
await skills.discard(agent.bot, item_name, num)
await skills.goToPosition(agent.bot, start_loc.x, start_loc.y, start_loc.z, 0)
}),
}
}
function getCollectBlocksAction(): Action {
return {
name: '!collectBlocks',
description: 'Collect the nearest blocks of a given type.',
schema: z.object({
type: z.string().describe('The block type to collect.'),
num: z.number().int().describe('The number of blocks to collect.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type, num) => {
await skills.collectBlock(agent.bot, type, num)
}, false, 10), // 10 minute timeout
}
}
function getCraftRecipeAction(): Action {
return {
name: '!craftRecipe',
description: 'Craft the given recipe a given number of times.',
schema: z.object({
recipe_name: z.string().describe('The name of the output item to craft.'),
num: z.number().int().describe('The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, recipe_name, num) => {
await skills.craftRecipe(agent.bot, recipe_name, num)
}),
}
}
function getSmeltItemAction(): Action {
return {
name: '!smeltItem',
description: 'Smelt the given item the given number of times.',
schema: z.object({
item_name: z.string().describe('The name of the input item to smelt.'),
num: z.number().int().describe('The number of times to smelt the item.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
const success = await skills.smeltItem(agent.bot, item_name, num)
if (success) {
setTimeout(() => {
agent.cleanKill('Safely restarting to update inventory.')
}, 500)
}
}),
}
}
function getClearFurnaceAction(): Action {
return {
name: '!clearFurnace',
description: 'Take all items out of the nearest furnace.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.clearNearestFurnace(agent.bot)
}),
}
}
function getPlaceHereAction(): Action {
return {
name: '!placeHere',
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.',
schema: z.object({
type: z.string().describe('The block type to place.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
const pos = agent.bot.entity.position
await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z)
}),
}
}
function getAttackAction(): Action {
return {
name: '!attack',
description: 'Attack and kill the nearest entity of a given type.',
schema: z.object({
type: z.string().describe('The type of entity to attack.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
await skills.attackNearest(agent.bot, type, true)
}),
}
}
function getAttackPlayerAction(): Action {
return {
name: '!attackPlayer',
description: 'Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.',
schema: z.object({
player_name: z.string().describe('The name of the player to attack.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name) => {
const player = agent.bot.players[player_name]?.entity
if (!player) {
skills.log(agent.bot, `Could not find player ${player_name}.`)
return false
}
await skills.attackEntity(agent.bot, player, true)
}),
}
}
function getGoToBedAction(): Action {
return {
name: '!goToBed',
description: 'Go to the nearest bed and sleep.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.goToBed(agent.bot)
}),
}
}
function getActivateAction(): Action {
return {
name: '!activate',
description: 'Activate the nearest object of a given type.',
schema: z.object({
type: z.string().describe('The type of object to activate.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
await skills.activateNearestBlock(agent.bot, type)
}),
}
}
function getStayAction(): Action {
return {
name: '!stay',
description: 'Stay in the current location no matter what. Pauses all modes.',
schema: z.object({
type: z.number().int().describe('The number of seconds to stay. -1 for forever.').min(-1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, seconds) => {
await skills.stay(agent.bot, seconds)
}),
}
}
function getSetModeAction(): Action {
return {
name: '!setMode',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.',
schema: z.object({
mode_name: z.string().describe('The name of the mode to enable.'),
on: z.boolean().describe('Whether to enable or disable the mode.'),
}),
perform: (agent: BotContext) => async (mode_name: string, on: boolean) => {
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'}.`
},
}
}
function getGoalAction(): Action {
return {
name: '!goal',
description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.',
schema: z.object({
selfPrompt: z.string().describe('The goal prompt.'),
}),
perform: (agent: BotContext) => async (prompt: string) => {
if (convoManager.inConversation()) {
agent.self_prompter.setPrompt(prompt)
convoManager.scheduleSelfPrompter()
}
else {
agent.self_prompter.start(prompt)
}
},
}
}
function getEndGoalAction(): Action {
return {
name: '!endGoal',
description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.self_prompter.stop()
convoManager.cancelSelfPrompter()
return 'Self-prompting stopped.'
},
}
}
function getStartConversationAction(): Action {
return {
name: '!startConversation',
description: 'Start a conversation with a player. Use for bots only.',
schema: z.object({
player_name: z.string().describe('The name of the player to send the message to.'),
message: z.string().describe('The message to send.'),
}),
perform: (agent: BotContext) => async (player_name: string, message: string) => {
if (!convoManager.isOtherAgent(player_name))
return `${player_name} is not a bot, cannot start conversation.`
if (convoManager.inConversation() && !convoManager.inConversation(player_name))
convoManager.forceEndCurrentConversation()
else if (convoManager.inConversation(player_name))
agent.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`)
convoManager.startConversation(player_name, message)
},
}
}
function getEndConversationAction(): Action {
return {
name: '!endConversation',
description: 'End the conversation with the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to end the conversation with.'),
}),
perform: (agent: BotContext) => async (player_name: string) => {
if (!convoManager.inConversation(player_name))
return `Not in conversation with ${player_name}.`
convoManager.endConversation(player_name)
return `Converstaion with ${player_name} ended.`
},
}
}
export const actionsList = [
getNewAction(),
getStopAction(),
getStfuAction(),
getRestartAction(),
getClearChatAction(),
getGoToPlayerAction(),
getFollowPlayerAction(),
getGoToCoordinatesAction(),
getSearchForBlockAction(),
getSearchForEntityAction(),
getMoveAwayAction(),
getRememberHereAction(),
getGoToRememberedPlaceAction(),
getGivePlayerAction(),
getConsumeAction(),
getEquipAction(),
getPutInChestAction(),
getTakeFromChestAction(),
getViewChestAction(),
getDiscardAction(),
getCollectBlocksAction(),
getCraftRecipeAction(),
getSmeltItemAction(),
getClearFurnaceAction(),
getPlaceHereAction(),
getAttackAction(),
getAttackPlayerAction(),
getGoToBedAction(),
getActivateAction(),
getStayAction(),
getSetModeAction(),
getGoalAction(),
getEndGoalAction(),
getStartConversationAction(),
getEndConversationAction(),
]
+1 -1
View File
@@ -3,7 +3,7 @@ import type { BotContext } from '../composables/bot'
import { useLogg } from '@guiiai/logg'
import { agent, neuri } from 'neuri'
import { openaiConfig } from '../composables/config'
import { queryList } from './query'
import { queryList } from './queries'
const agents = new Set<Agent | Promise<Agent>>()
+22
View File
@@ -0,0 +1,22 @@
import type { Bot } from 'mineflayer'
/**
* Log a message to the bot's output
*/
export function log(bot: Bot, message: string): void {
bot.chat(`${message}`)
}
/**
* Type definition for a position in the world
*/
export interface Position {
x: number
y: number
z: number
}
/**
* Type definition for a block face direction
*/
export type BlockFace = 'top' | 'bottom' | 'north' | 'south' | 'east' | 'west' | 'side'
+396
View File
@@ -0,0 +1,396 @@
import type { Bot } from 'mineflayer'
import type { BlockFace } from './base'
import Vec3 from 'vec3'
import * as world from '../composables/world'
import * as mc from '../utils/mcdata'
import { log } from './base'
import { goToPosition } from './movement'
/**
* Place a torch if needed
*/
async function autoLight(bot: Bot): Promise<boolean> {
if (world.shouldPlaceTorch(bot)) {
try {
const pos = world.getPosition(bot)
return await placeBlock(bot, 'torch', pos.x, pos.y, pos.z, 'bottom', true)
}
catch {
return false
}
}
return false
}
/**
* Break a block at the specified position
*/
export async function breakBlockAt(
bot: Bot,
x: number,
y: number,
z: number,
): Promise<boolean> {
if (x == null || y == null || z == null) {
throw new Error('Invalid position to break block at.')
}
const block = bot.blockAt(new Vec3(x, y, z))
if (block.name === 'air' || block.name === 'water' || block.name === 'lava') {
return false
}
if (bot.modes.isOn('cheat')) {
bot.chat(`/setblock ${Math.floor(x)} ${Math.floor(y)} ${Math.floor(z)} air`)
log(bot, `Used /setblock to break block at ${x}, ${y}, ${z}.`)
return true
}
if (bot.entity.position.distanceTo(block.position) > 4.5) {
const pos = block.position
const movements = new bot.pathfinder.Movements(bot)
movements.allowParkour = false
movements.allowSprinting = false
bot.pathfinder.setMovements(movements)
await bot.pathfinder.goto(bot.pathfinder.goals.GoalNear(pos.x, pos.y, pos.z, 4))
}
if (bot.game.gameMode !== 'creative') {
await bot.tool.equipForBlock(block)
const itemId = bot.heldItem?.type
if (!block.canHarvest(itemId)) {
log(bot, `Don't have right tools to break ${block.name}.`)
return false
}
}
await bot.dig(block, true)
log(bot, `Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`)
return true
}
/**
* Place a block at the specified position
*/
export async function placeBlock(
bot: Bot,
blockType: string,
x: number,
y: number,
z: number,
placeOn: BlockFace = 'bottom',
dontCheat = false,
): Promise<boolean> {
if (!mc.getBlockId(blockType)) {
log(bot, `Invalid block type: ${blockType}.`)
return false
}
const targetDest = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z))
if (bot.modes.isOn('cheat') && !dontCheat) {
// Invert the facing direction
const face = placeOn === 'north'
? 'south'
: placeOn === 'south'
? 'north'
: placeOn === 'east'
? 'west'
: placeOn === 'west'
? 'east'
: placeOn
let blockState = blockType
if (blockType.includes('torch') && placeOn !== 'bottom') {
blockState = blockType.replace('torch', 'wall_torch')
if (placeOn !== 'side' && placeOn !== 'top') {
blockState += `[facing=${face}]`
}
}
if (blockType.includes('button') || blockType === 'lever') {
if (placeOn === 'top') {
blockState += '[face=ceiling]'
}
else if (placeOn === 'bottom') {
blockState += '[face=floor]'
}
else {
blockState += `[facing=${face}]`
}
}
if (blockType === 'ladder' || blockType === 'repeater' || blockType === 'comparator') {
blockState += `[facing=${face}]`
}
if (blockType.includes('stairs')) {
blockState += `[facing=${face}]`
}
bot.chat(`/setblock ${Math.floor(x)} ${Math.floor(y)} ${Math.floor(z)} ${blockState}`)
if (blockType.includes('door')) {
bot.chat(`/setblock ${Math.floor(x)} ${Math.floor(y + 1)} ${Math.floor(z)} ${blockState}[half=upper]`)
}
if (blockType.includes('bed')) {
bot.chat(`/setblock ${Math.floor(x)} ${Math.floor(y)} ${Math.floor(z - 1)} ${blockState}[part=head]`)
}
log(bot, `Used /setblock to place ${blockType} at ${targetDest}.`)
return true
}
let itemName = blockType
if (itemName === 'redstone_wire') {
itemName = 'redstone'
}
let block = bot.inventory.items().find(item => item.name === itemName)
if (!block && bot.game.gameMode === 'creative') {
await bot.creative.setInventorySlot(36, mc.makeItem(itemName, 1))
block = bot.inventory.items().find(item => item.name === itemName)
}
if (!block) {
log(bot, `Don't have any ${blockType} to place.`)
return false
}
const targetBlock = bot.blockAt(targetDest)
if (targetBlock.name === blockType) {
log(bot, `${blockType} already at ${targetBlock.position}.`)
return false
}
const emptyBlocks = ['air', 'water', 'lava', 'grass', 'short_grass', 'tall_grass', 'snow', 'dead_bush', 'fern']
if (!emptyBlocks.includes(targetBlock.name)) {
log(bot, `${blockType} in the way at ${targetBlock.position}.`)
const removed = await breakBlockAt(bot, x, y, z)
if (!removed) {
log(bot, `Cannot place ${blockType} at ${targetBlock.position}: block in the way.`)
return false
}
await new Promise(resolve => setTimeout(resolve, 200))
}
const dirMap = {
top: new Vec3(0, 1, 0),
bottom: new Vec3(0, -1, 0),
north: new Vec3(0, 0, -1),
south: new Vec3(0, 0, 1),
east: new Vec3(1, 0, 0),
west: new Vec3(-1, 0, 0),
}
const dirs = []
if (placeOn === 'side') {
dirs.push(dirMap.north, dirMap.south, dirMap.east, dirMap.west)
}
else if (dirMap[placeOn]) {
dirs.push(dirMap[placeOn])
}
else {
dirs.push(dirMap.bottom)
log(bot, `Unknown placeOn value "${placeOn}". Defaulting to bottom.`)
}
dirs.push(...Object.values(dirMap).filter(d => !dirs.includes(d)))
let buildOffBlock = null
let faceVec = null
for (const d of dirs) {
const block = bot.blockAt(targetDest.plus(d))
if (!emptyBlocks.includes(block.name)) {
buildOffBlock = block
faceVec = new Vec3(-d.x, -d.y, -d.z)
break
}
}
if (!buildOffBlock) {
log(bot, `Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`)
return false
}
const pos = bot.entity.position
const posAbove = pos.plus(new Vec3(0, 1, 0))
const dontMoveFor = [
'torch',
'redstone_torch',
'redstone_wire',
'lever',
'button',
'rail',
'detector_rail',
'powered_rail',
'activator_rail',
'tripwire_hook',
'tripwire',
'water_bucket',
]
if (!dontMoveFor.includes(blockType)
&& (pos.distanceTo(targetBlock.position) < 1
|| posAbove.distanceTo(targetBlock.position) < 1)) {
const goal = bot.pathfinder.goals.GoalNear(targetBlock.position.x, targetBlock.position.y, targetBlock.position.z, 2)
const invertedGoal = bot.pathfinder.goals.GoalInvert(goal)
bot.pathfinder.setMovements(new bot.pathfinder.Movements(bot))
await bot.pathfinder.goto(invertedGoal)
}
if (bot.entity.position.distanceTo(targetBlock.position) > 4.5) {
const pos = targetBlock.position
const movements = new bot.pathfinder.Movements(bot)
bot.pathfinder.setMovements(movements)
await bot.pathfinder.goto(bot.pathfinder.goals.GoalNear(pos.x, pos.y, pos.z, 4))
}
await bot.equip(block, 'hand')
await bot.lookAt(buildOffBlock.position)
try {
await bot.placeBlock(buildOffBlock, faceVec)
log(bot, `Placed ${blockType} at ${targetDest}.`)
await new Promise(resolve => setTimeout(resolve, 200))
return true
}
catch {
log(bot, `Failed to place ${blockType} at ${targetDest}.`)
return false
}
}
/**
* Use a door at the specified position
*/
export async function useDoor(bot: Bot, doorPos: Vec3 | null = null): Promise<boolean> {
if (!doorPos) {
const doorTypes = [
'oak_door',
'spruce_door',
'birch_door',
'jungle_door',
'acacia_door',
'dark_oak_door',
'mangrove_door',
'cherry_door',
'bamboo_door',
'crimson_door',
'warped_door',
]
for (const doorType of doorTypes) {
const block = world.getNearestBlock(bot, doorType, 16)
if (block) {
doorPos = block.position
break
}
}
}
if (!doorPos) {
log(bot, 'Could not find a door to use.')
return false
}
await goToPosition(bot, doorPos.x, doorPos.y, doorPos.z, 1)
while (bot.pathfinder.isMoving()) {
await new Promise(resolve => setTimeout(resolve, 100))
}
const doorBlock = bot.blockAt(doorPos)
await bot.lookAt(doorPos)
if (!doorBlock._properties.open) {
await bot.activateBlock(doorBlock)
}
bot.setControlState('forward', true)
await new Promise(resolve => setTimeout(resolve, 600))
bot.setControlState('forward', false)
await bot.activateBlock(doorBlock)
log(bot, `Used door at ${doorPos}.`)
return true
}
/**
* Till and sow a block at the specified position
*/
export async function tillAndSow(
bot: Bot,
x: number,
y: number,
z: number,
seedType: string | null = null,
): Promise<boolean> {
x = Math.round(x)
y = Math.round(y)
z = Math.round(z)
const block = bot.blockAt(new Vec3(x, y, z))
if (block.name !== 'grass_block' && block.name !== 'dirt' && block.name !== 'farmland') {
log(bot, `Cannot till ${block.name}, must be grass_block or dirt.`)
return false
}
const above = bot.blockAt(new Vec3(x, y + 1, z))
if (above.name !== 'air') {
log(bot, `Cannot till, there is ${above.name} above the block.`)
return false
}
if (bot.entity.position.distanceTo(block.position) > 4.5) {
await goToPosition(bot, block.position.x, block.position.y, block.position.z, 4)
}
if (block.name !== 'farmland') {
const hoe = bot.inventory.items().find(item => item.name.includes('hoe'))
if (!hoe) {
log(bot, 'Cannot till, no hoes.')
return false
}
await bot.equip(hoe, 'hand')
await bot.activateBlock(block)
log(bot, `Tilled block x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`)
}
if (seedType) {
if (seedType.endsWith('seed') && !seedType.endsWith('seeds')) {
seedType += 's' // Fix common mistake
}
const seeds = bot.inventory.items().find(item => item.name === seedType)
if (!seeds) {
log(bot, `No ${seedType} to plant.`)
return false
}
await bot.equip(seeds, 'hand')
await bot.placeBlock(block, new Vec3(0, -1, 0))
log(bot, `Planted ${seedType} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`)
}
return true
}
/**
* Activate the nearest block of a specific type
*/
export async function activateNearestBlock(bot: Bot, type: string): Promise<boolean> {
const block = world.getNearestBlock(bot, type, 16)
if (!block) {
log(bot, `Could not find any ${type} to activate.`)
return false
}
if (bot.entity.position.distanceTo(block.position) > 4.5) {
await goToPosition(bot, block.position.x, block.position.y, block.position.z, 4)
}
await bot.activateBlock(block)
log(bot, `Activated ${type} at x:${block.position.x.toFixed(1)}, y:${block.position.y.toFixed(1)}, z:${block.position.z.toFixed(1)}.`)
return true
}
+133
View File
@@ -0,0 +1,133 @@
import type { Bot } from 'mineflayer'
import type { Entity } from 'prismarine-entity'
import * as mc from '../../../utils/mcdata.js'
import * as world from '../world.js'
import { log } from './base.js'
/**
* Equip the item with highest attack damage
*/
async function equipHighestAttack(bot: Bot): Promise<void> {
const weapons = bot.inventory.items().filter(item =>
item.name.includes('sword')
|| (item.name.includes('axe') && !item.name.includes('pickaxe')),
)
if (weapons.length === 0) {
const tools = bot.inventory.items().filter(item =>
item.name.includes('pickaxe')
|| item.name.includes('shovel'),
)
if (tools.length === 0)
return
tools.sort((a, b) => b.attackDamage - a.attackDamage)
const tool = tools[0]
if (tool)
await bot.equip(tool, 'hand')
return
}
weapons.sort((a, b) => b.attackDamage - a.attackDamage)
const weapon = weapons[0]
if (weapon)
await bot.equip(weapon, 'hand')
}
/**
* Attack the nearest mob of the given type
*/
export async function attackNearest(bot: Bot, mobType: string, kill = true): Promise<boolean> {
bot.modes.pause('cowardice')
if (mobType === 'drowned' || mobType === 'cod' || mobType === 'salmon'
|| mobType === 'tropical_fish' || mobType === 'squid') {
bot.modes.pause('self_preservation')
}
const mob = world.getNearbyEntities(bot, 24).find(entity => entity.name === mobType)
if (mob) {
return await attackEntity(bot, mob, kill)
}
log(bot, `Could not find any ${mobType} to attack.`)
return false
}
/**
* Attack a specific entity
*/
export async function attackEntity(bot: Bot, entity: Entity, kill = true): Promise<boolean> {
const pos = entity.position
await equipHighestAttack(bot)
if (!kill) {
if (bot.entity.position.distanceTo(pos) > 5) {
await bot.pathfinder.goto(bot.pathfinder.goals.GoalNear(pos.x, pos.y, pos.z, 4))
}
await bot.attack(entity)
}
else {
bot.pvp.attack(entity)
while (world.getNearbyEntities(bot, 24).includes(entity)) {
await new Promise(resolve => setTimeout(resolve, 1000))
if (bot.interrupt_code) {
bot.pvp.stop()
return false
}
}
log(bot, `Successfully killed ${entity.name}.`)
return true
}
return true
}
/**
* Defend against nearby hostile mobs
*/
export async function defendSelf(bot: Bot, range = 9): Promise<boolean> {
bot.modes.pause('self_defense')
bot.modes.pause('cowardice')
let attacked = false
let enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range)
while (enemy) {
await equipHighestAttack(bot)
if (bot.entity.position.distanceTo(enemy.position) >= 4
&& enemy.name !== 'creeper' && enemy.name !== 'phantom') {
try {
await bot.pathfinder.goto(bot.pathfinder.goals.GoalFollow(enemy, 3.5), true)
}
catch { /* might error if entity dies, ignore */ }
}
if (bot.entity.position.distanceTo(enemy.position) <= 2) {
try {
const inverted_goal = bot.pathfinder.goals.GoalInvert(
bot.pathfinder.goals.GoalFollow(enemy, 2),
)
await bot.pathfinder.goto(inverted_goal, true)
}
catch { /* might error if entity dies, ignore */ }
}
bot.pvp.attack(enemy)
attacked = true
await new Promise(resolve => setTimeout(resolve, 500))
enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range)
if (bot.interrupt_code) {
bot.pvp.stop()
return false
}
}
bot.pvp.stop()
if (attacked) {
log(bot, `Successfully defended self.`)
}
else {
log(bot, `No enemies nearby to defend self from.`)
}
return attacked
}
+268
View File
@@ -0,0 +1,268 @@
import type { Bot } from 'mineflayer'
import * as world from '../composables/world'
import * as mc from '../utils/mcdata'
import { log } from './base'
import { collectBlock } from './blocks'
import { goToPosition } from './movement'
/**
* Craft items from a recipe
*/
export async function craftRecipe(bot: Bot, itemName: string, num = 1): Promise<boolean> {
let placedTable = false
if (mc.getItemCraftingRecipes(itemName).length === 0) {
log(bot, `${itemName} is either not an item, or it does not have a crafting recipe!`)
return false
}
// Get recipes that don't require a crafting table
let recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, null)
let craftingTable = null
const craftingTableRange = 32
if (!recipes || recipes.length === 0) {
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, true)
if (!recipes || recipes.length === 0) {
log(bot, `You do not have the resources to craft a ${itemName}.`)
return false
}
// Look for crafting table
craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange)
if (!craftingTable) {
// Try to place crafting table
const hasTable = world.getInventoryCounts(bot).crafting_table > 0
if (hasTable) {
const pos = world.getNearestFreeSpace(bot, 1, 6)
await placeBlock(bot, 'crafting_table', pos.x, pos.y, pos.z)
craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange)
if (craftingTable) {
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable)
placedTable = true
}
}
else {
log(bot, `Crafting ${itemName} requires a crafting table.`)
return false
}
}
else {
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable)
}
}
if (!recipes || recipes.length === 0) {
log(bot, `You do not have the resources to craft a ${itemName}. It requires: ${
Object.entries(mc.getItemCraftingRecipes(itemName)[0])
.map(([key, value]) => `${key}: ${value}`)
.join(', ')
}.`)
if (placedTable) {
await collectBlock(bot, 'crafting_table', 1)
}
return false
}
if (craftingTable && bot.entity.position.distanceTo(craftingTable.position) > 4) {
await goToPosition(bot, craftingTable.position.x, craftingTable.position.y, craftingTable.position.z, 4)
}
const recipe = recipes[0]
// Check that the agent has sufficient items to use the recipe `num` times
const inventory = world.getInventoryCounts(bot) // Items in the agents inventory
const requiredIngredients = mc.ingredientsFromPrismarineRecipe(recipe) // Items required to use the recipe once
const craftLimit = mc.calculateLimitingResource(inventory, requiredIngredients)
await bot.craft(recipe, Math.min(craftLimit.num, num), craftingTable)
if (craftLimit.num < num) {
log(bot, `Not enough ${craftLimit.limitingResource} to craft ${num}, crafted ${craftLimit.num}. You now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`)
}
else {
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`)
}
if (placedTable) {
await collectBlock(bot, 'crafting_table', 1)
}
// Equip any armor the bot may have crafted
bot.armorManager.equipAll()
return true
}
/**
* Smelt items in a furnace
*/
export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<boolean> {
if (!mc.isSmeltable(itemName)) {
log(bot, `Cannot smelt ${itemName}. Hint: make sure you are smelting the 'raw' item.`)
return false
}
let placedFurnace = false
const furnaceRange = 32
let furnaceBlock = world.getNearestBlock(bot, 'furnace', furnaceRange)
if (!furnaceBlock) {
// Try to place furnace
const hasFurnace = world.getInventoryCounts(bot).furnace > 0
if (hasFurnace) {
const pos = world.getNearestFreeSpace(bot, 1, furnaceRange)
await placeBlock(bot, 'furnace', pos.x, pos.y, pos.z)
furnaceBlock = world.getNearestBlock(bot, 'furnace', furnaceRange)
placedFurnace = true
}
}
if (!furnaceBlock) {
log(bot, 'There is no furnace nearby and you have no furnace.')
return false
}
if (bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
await goToPosition(bot, furnaceBlock.position.x, furnaceBlock.position.y, furnaceBlock.position.z, 4)
}
bot.modes.pause('unstuck')
await bot.lookAt(furnaceBlock.position)
const furnace = await bot.openFurnace(furnaceBlock)
// Check if the furnace is already smelting something
const inputItem = furnace.inputItem()
if (inputItem && inputItem.type !== mc.getItemId(itemName) && inputItem.count > 0) {
log(bot, `The furnace is currently smelting ${mc.getItemName(inputItem.type)}.`)
if (placedFurnace) {
await collectBlock(bot, 'furnace', 1)
}
return false
}
// Check if the bot has enough items to smelt
const invCounts = world.getInventoryCounts(bot)
if (!invCounts[itemName] || invCounts[itemName] < num) {
log(bot, `You do not have enough ${itemName} to smelt.`)
if (placedFurnace) {
await collectBlock(bot, 'furnace', 1)
}
return false
}
// Fuel the furnace
if (!furnace.fuelItem()) {
const fuel = mc.getSmeltingFuel(bot)
if (!fuel) {
log(bot, `You have no fuel to smelt ${itemName}, you need coal, charcoal, or wood.`)
if (placedFurnace) {
await collectBlock(bot, 'furnace', 1)
}
return false
}
log(bot, `Using ${fuel.name} as fuel.`)
const putFuel = Math.ceil(num / mc.getFuelSmeltOutput(fuel.name))
if (fuel.count < putFuel) {
log(bot, `You don't have enough ${fuel.name} to smelt ${num} ${itemName}; you need ${putFuel}.`)
if (placedFurnace) {
await collectBlock(bot, 'furnace', 1)
}
return false
}
await furnace.putFuel(fuel.type, null, putFuel)
log(bot, `Added ${putFuel} ${mc.getItemName(fuel.type)} to furnace fuel.`)
}
// Put the items in the furnace
await furnace.putInput(mc.getItemId(itemName), null, num)
// Wait for the items to smelt
let total = 0
let collectedLast = true
let smeltedItem = null
await new Promise(resolve => setTimeout(resolve, 200))
while (total < num) {
await new Promise(resolve => setTimeout(resolve, 10000))
let collected = false
if (furnace.outputItem()) {
smeltedItem = await furnace.takeOutput()
if (smeltedItem) {
total += smeltedItem.count
collected = true
}
}
if (!collected && !collectedLast) {
break // If nothing was collected this time or last time
}
collectedLast = collected
if (bot.interrupt_code) {
break
}
}
await bot.closeWindow(furnace)
if (placedFurnace) {
await collectBlock(bot, 'furnace', 1)
}
if (total === 0) {
log(bot, `Failed to smelt ${itemName}.`)
return false
}
if (total < num) {
log(bot, `Only smelted ${total} ${mc.getItemName(smeltedItem.type)}.`)
return false
}
log(bot, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smeltedItem.type)}.`)
return true
}
/**
* Clear the nearest furnace
*/
export async function clearNearestFurnace(bot: Bot): Promise<boolean> {
const furnaceBlock = world.getNearestBlock(bot, 'furnace', 32)
if (!furnaceBlock) {
log(bot, 'No furnace nearby to clear.')
return false
}
if (bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
await goToPosition(bot, furnaceBlock.position.x, furnaceBlock.position.y, furnaceBlock.position.z, 4)
}
const furnace = await bot.openFurnace(furnaceBlock)
// Take the items out of the furnace
let smeltedItem, inputItem, fuelItem
if (furnace.outputItem()) {
smeltedItem = await furnace.takeOutput()
}
if (furnace.inputItem()) {
inputItem = await furnace.takeInput()
}
if (furnace.fuelItem()) {
fuelItem = await furnace.takeFuel()
}
const smeltedName = smeltedItem ? `${smeltedItem.count} ${smeltedItem.name}` : '0 smelted items'
const inputName = inputItem ? `${inputItem.count} ${inputItem.name}` : '0 input items'
const fuelName = fuelItem ? `${fuelItem.count} ${fuelItem.name}` : '0 fuel items'
log(bot, `Cleared furnace, received ${smeltedName}, ${inputName}, and ${fuelName}.`)
return true
}
+50
View File
@@ -0,0 +1,50 @@
// Base utilities
export { log } from './base'
export type { BlockFace, Position } from './base'
// Block interaction functions
export {
activateNearestBlock,
breakBlockAt,
placeBlock,
tillAndSow,
useDoor,
} from './blocks.js'
// Combat related functions
export {
attackEntity,
attackNearest,
defendSelf,
} from './combat.js'
// Crafting and smelting functions
export {
clearNearestFurnace,
craftRecipe,
smeltItem,
} from './crafting.js'
// Inventory management functions
export {
consume,
discard,
equip,
giveToPlayer,
pickupNearbyItems,
putInChest,
takeFromChest,
viewChest,
} from './inventory.js'
// Movement related functions
export {
followPlayer,
goToNearestBlock,
goToNearestEntity,
goToPlayer,
goToPosition,
moveAway,
moveAwayFromEntity,
stay,
} from './movement.js'
+257
View File
@@ -0,0 +1,257 @@
import type { Bot } from 'mineflayer'
import * as world from '../composables/world'
import { log } from './base'
import { goToPosition } from './movement'
/**
* Pick up nearby items
*/
export async function pickupNearbyItems(bot: Bot): Promise<boolean> {
const distance = 8
const getNearestItem = (bot: Bot) =>
bot.nearestEntity(entity =>
entity.name === 'item'
&& bot.entity.position.distanceTo(entity.position) < distance,
)
let nearestItem = getNearestItem(bot)
let pickedUp = 0
while (nearestItem) {
await bot.pathfinder.goto(bot.pathfinder.goals.GoalFollow(nearestItem, 0.8), true)
await new Promise(resolve => setTimeout(resolve, 200))
const prev = nearestItem
nearestItem = getNearestItem(bot)
if (prev === nearestItem) {
break
}
pickedUp++
}
log(bot, `Picked up ${pickedUp} items.`)
return true
}
/**
* Equip an item
*/
export async function equip(bot: Bot, itemName: string): Promise<boolean> {
const item = bot.inventory.slots.find(slot => slot && slot.name === itemName)
if (!item) {
log(bot, `You do not have any ${itemName} to equip.`)
return false
}
if (itemName.includes('leggings')) {
await bot.equip(item, 'legs')
}
else if (itemName.includes('boots')) {
await bot.equip(item, 'feet')
}
else if (itemName.includes('helmet')) {
await bot.equip(item, 'head')
}
else if (itemName.includes('chestplate') || itemName.includes('elytra')) {
await bot.equip(item, 'torso')
}
else if (itemName.includes('shield')) {
await bot.equip(item, 'off-hand')
}
else {
await bot.equip(item, 'hand')
}
log(bot, `Equipped ${itemName}.`)
return true
}
/**
* Discard items
*/
export async function discard(bot: Bot, itemName: string, num = -1): Promise<boolean> {
let discarded = 0
while (true) {
const item = bot.inventory.items().find(item => item.name === itemName)
if (!item) {
break
}
const toDiscard = num === -1 ? item.count : Math.min(num - discarded, item.count)
await bot.toss(item.type, null, toDiscard)
discarded += toDiscard
if (num !== -1 && discarded >= num) {
break
}
}
if (discarded === 0) {
log(bot, `You do not have any ${itemName} to discard.`)
return false
}
log(bot, `Discarded ${discarded} ${itemName}.`)
return true
}
/**
* Put items in a chest
*/
export async function putInChest(bot: Bot, itemName: string, num = -1): Promise<boolean> {
const chest = world.getNearestBlock(bot, 'chest', 32)
if (!chest) {
log(bot, 'Could not find a chest nearby.')
return false
}
const item = bot.inventory.items().find(item => item.name === itemName)
if (!item) {
log(bot, `You do not have any ${itemName} to put in the chest.`)
return false
}
const toPut = num === -1 ? item.count : Math.min(num, item.count)
await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2)
const chestContainer = await bot.openContainer(chest)
await chestContainer.deposit(item.type, null, toPut)
await chestContainer.close()
log(bot, `Successfully put ${toPut} ${itemName} in the chest.`)
return true
}
/**
* Take items from a chest
*/
export async function takeFromChest(bot: Bot, itemName: string, num = -1): Promise<boolean> {
const chest = world.getNearestBlock(bot, 'chest', 32)
if (!chest) {
log(bot, 'Could not find a chest nearby.')
return false
}
await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2)
const chestContainer = await bot.openContainer(chest)
const item = chestContainer.containerItems().find(item => item.name === itemName)
if (!item) {
log(bot, `Could not find any ${itemName} in the chest.`)
await chestContainer.close()
return false
}
const toTake = num === -1 ? item.count : Math.min(num, item.count)
await chestContainer.withdraw(item.type, null, toTake)
await chestContainer.close()
log(bot, `Successfully took ${toTake} ${itemName} from the chest.`)
return true
}
/**
* View contents of a chest
*/
export async function viewChest(bot: Bot): Promise<boolean> {
const chest = world.getNearestBlock(bot, 'chest', 32)
if (!chest) {
log(bot, 'Could not find a chest nearby.')
return false
}
await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2)
const chestContainer = await bot.openContainer(chest)
const items = chestContainer.containerItems()
if (items.length === 0) {
log(bot, 'The chest is empty.')
}
else {
log(bot, 'The chest contains:')
for (const item of items) {
log(bot, `${item.count} ${item.name}`)
}
}
await chestContainer.close()
return true
}
/**
* Consume (eat/drink) an item
*/
export async function consume(bot: Bot, itemName = ''): Promise<boolean> {
let item
let name
if (itemName) {
item = bot.inventory.items().find(item => item.name === itemName)
name = itemName
}
if (!item) {
log(bot, `You do not have any ${name} to eat.`)
return false
}
await bot.equip(item, 'hand')
await bot.consume()
log(bot, `Consumed ${item.name}.`)
return true
}
/**
* Give items to a player
*/
export async function giveToPlayer(
bot: Bot,
itemType: string,
username: string,
num = 1,
): Promise<boolean> {
const player = bot.players[username]?.entity
if (!player) {
log(bot, `Could not find ${username}.`)
return false
}
await goToPosition(bot, player.position.x, player.position.y, player.position.z, 3)
if (bot.entity.position.y < player.position.y - 1) {
await goToPosition(bot, player.position.x, player.position.y, player.position.z, 1)
}
if (bot.entity.position.distanceTo(player.position) < 2) {
const goal = bot.pathfinder.goals.GoalNear(player.position.x, player.position.y, player.position.z, 2)
const invertedGoal = bot.pathfinder.goals.GoalInvert(goal)
await bot.pathfinder.goto(invertedGoal)
}
await bot.lookAt(player.position)
if (await discard(bot, itemType, num)) {
let given = false
bot.once('playerCollect', (collector, collected) => {
if (collector.username === username) {
log(bot, `${username} received ${itemType}.`)
given = true
}
})
const start = Date.now()
while (!given && !bot.interrupt_code) {
await new Promise(resolve => setTimeout(resolve, 500))
if (given) {
return true
}
if (Date.now() - start > 3000) {
break
}
}
}
log(bot, `Failed to give ${itemType} to ${username}, it was never received.`)
return false
}
+208
View File
@@ -0,0 +1,208 @@
import type { Bot } from 'mineflayer'
import type { Entity } from 'prismarine-entity'
import * as world from '../composables/world'
import { log } from './base'
/**
* Navigate to a specific position
*/
export async function goToPosition(
bot: Bot,
x: number,
y: number,
z: number,
minDistance = 2,
): Promise<boolean> {
if (x == null || y == null || z == null) {
log(bot, `Missing coordinates, given x:${x} y:${y} z:${z}`)
return false
}
if (bot.modes.isOn('cheat')) {
bot.chat(`/tp @s ${x} ${y} ${z}`)
log(bot, `Teleported to ${x}, ${y}, ${z}.`)
return true
}
await bot.pathfinder.goto(bot.pathfinder.goals.GoalNear(x, y, z, minDistance))
log(bot, `You have reached ${x}, ${y}, ${z}.`)
return true
}
/**
* Navigate to the nearest block of a specific type
*/
export async function goToNearestBlock(
bot: Bot,
blockType: string,
minDistance = 2,
range = 64,
): Promise<boolean> {
const MAX_RANGE = 512
if (range > MAX_RANGE) {
log(bot, `Maximum search range capped at ${MAX_RANGE}.`)
range = MAX_RANGE
}
const block = world.getNearestBlock(bot, blockType, range)
if (!block) {
log(bot, `Could not find any ${blockType} in ${range} blocks.`)
return false
}
log(bot, `Found ${blockType} at ${block.position}.`)
await goToPosition(bot, block.position.x, block.position.y, block.position.z, minDistance)
return true
}
/**
* Navigate to the nearest entity of a specific type
*/
export async function goToNearestEntity(
bot: Bot,
entityType: string,
minDistance = 2,
range = 64,
): Promise<boolean> {
const entity = world.getNearestEntityWhere(
bot,
entity => entity.name === entityType,
range,
)
if (!entity) {
log(bot, `Could not find any ${entityType} in ${range} blocks.`)
return false
}
const distance = bot.entity.position.distanceTo(entity.position)
log(bot, `Found ${entityType} ${distance} blocks away.`)
await goToPosition(
bot,
entity.position.x,
entity.position.y,
entity.position.z,
minDistance,
)
return true
}
/**
* Navigate to a specific player
*/
export async function goToPlayer(bot: Bot, username: string, distance = 3): Promise<boolean> {
if (bot.modes.isOn('cheat')) {
bot.chat(`/tp @s ${username}`)
log(bot, `Teleported to ${username}.`)
return true
}
bot.modes.pause('self_defense')
bot.modes.pause('cowardice')
const player = bot.players[username]?.entity
if (!player) {
log(bot, `Could not find ${username}.`)
return false
}
await bot.pathfinder.goto(bot.pathfinder.goals.GoalFollow(player, distance), true)
log(bot, `You have reached ${username}.`)
return true
}
/**
* Follow a player continuously
*/
export async function followPlayer(bot: Bot, username: string, distance = 4): Promise<boolean> {
const player = bot.players[username]?.entity
if (!player)
return false
bot.pathfinder.setGoal(bot.pathfinder.goals.GoalFollow(player, distance), true)
log(bot, `You are now actively following player ${username}.`)
while (!bot.interrupt_code) {
await new Promise(resolve => setTimeout(resolve, 500))
if (bot.modes.isOn('cheat')
&& bot.entity.position.distanceTo(player.position) > 100
&& player.isOnGround) {
await goToPlayer(bot, username)
}
if (bot.modes.isOn('unstuck')) {
const isNearby = bot.entity.position.distanceTo(player.position) <= distance + 1
if (isNearby) {
bot.modes.pause('unstuck')
}
else {
bot.modes.unpause('unstuck')
}
}
}
return true
}
/**
* Move away from current position
*/
export async function moveAway(bot: Bot, distance: number): Promise<boolean> {
const pos = bot.entity.position
const goal = bot.pathfinder.goals.GoalNear(pos.x, pos.y, pos.z, distance)
const invertedGoal = bot.pathfinder.goals.GoalInvert(goal)
if (bot.modes.isOn('cheat')) {
const move = new bot.pathfinder.Movements(bot)
const path = await bot.pathfinder.getPathTo(move, invertedGoal, 10000)
const lastMove = path.path[path.path.length - 1]
if (lastMove) {
const x = Math.floor(lastMove.x)
const y = Math.floor(lastMove.y)
const z = Math.floor(lastMove.z)
bot.chat(`/tp @s ${x} ${y} ${z}`)
return true
}
}
await bot.pathfinder.goto(invertedGoal)
const newPos = bot.entity.position
log(bot, `Moved away from nearest entity to ${newPos}.`)
return true
}
/**
* Move away from a specific entity
*/
export async function moveAwayFromEntity(
bot: Bot,
entity: Entity,
distance = 16,
): Promise<boolean> {
const goal = bot.pathfinder.goals.GoalFollow(entity, distance)
const invertedGoal = bot.pathfinder.goals.GoalInvert(goal)
await bot.pathfinder.goto(invertedGoal)
return true
}
/**
* Stay in current position
*/
export async function stay(bot: Bot, seconds = 30): Promise<boolean> {
bot.modes.pause('self_preservation')
bot.modes.pause('unstuck')
bot.modes.pause('cowardice')
bot.modes.pause('self_defense')
bot.modes.pause('hunting')
bot.modes.pause('torch_placing')
bot.modes.pause('item_collecting')
const start = Date.now()
while (!bot.interrupt_code && (seconds === -1 || Date.now() - start < seconds * 1000)) {
await new Promise(resolve => setTimeout(resolve, 500))
}
log(bot, `Stayed for ${(Date.now() - start) / 1000} seconds.`)
return true
}