feat: skills
This commit is contained in:
@@ -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(),
|
||||
]
|
||||
@@ -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>>()
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user