refactor: skill ctx
This commit is contained in:
@@ -1,14 +1,42 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
|
||||
/**
|
||||
* Log a message to the bot's output
|
||||
* Context for skill execution
|
||||
*/
|
||||
export function log(bot: Bot, message: string): void {
|
||||
bot.chat(`${message}`)
|
||||
export interface SkillContext {
|
||||
bot: Bot
|
||||
// Whether the bot is in creative mode
|
||||
isCreative: boolean
|
||||
// Whether the bot should use cheats (like /tp, /setblock)
|
||||
allowCheats: boolean
|
||||
// Whether the bot should interrupt current action
|
||||
shouldInterrupt: boolean
|
||||
// Output buffer for logging
|
||||
output: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Type definition for a position in the world
|
||||
* Create a new skill context
|
||||
*/
|
||||
export function createContext(bot: Bot): SkillContext {
|
||||
return {
|
||||
bot,
|
||||
isCreative: bot.game.gameMode === 'creative',
|
||||
allowCheats: false,
|
||||
shouldInterrupt: false,
|
||||
output: [],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message to the context's output buffer
|
||||
*/
|
||||
export function log(ctx: SkillContext, message: string): void {
|
||||
ctx.output.push(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Position in the world
|
||||
*/
|
||||
export interface Position {
|
||||
x: number
|
||||
@@ -17,6 +45,6 @@ export interface Position {
|
||||
}
|
||||
|
||||
/**
|
||||
* Type definition for a block face direction
|
||||
* Block face direction
|
||||
*/
|
||||
export type BlockFace = 'top' | 'bottom' | 'north' | 'south' | 'east' | 'west' | 'side'
|
||||
|
||||
@@ -1,100 +1,18 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { BlockFace } from './base'
|
||||
import Vec3 from 'vec3'
|
||||
import type { BlockFace, SkillContext } 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'
|
||||
|
||||
export async function collectBlock(
|
||||
bot: Bot,
|
||||
blockType: string,
|
||||
num: number = 1,
|
||||
exclude: typeof Vec3[] | null = null,
|
||||
): Promise<boolean> {
|
||||
if (num < 1) {
|
||||
log(bot, `Invalid number of blocks to collect: ${num}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
const blocktypes: string[] = [blockType]
|
||||
if (blockType === 'coal' || blockType === 'diamond' || blockType === 'emerald'
|
||||
|| blockType === 'iron' || blockType === 'gold' || blockType === 'lapis_lazuli'
|
||||
|| blockType === 'redstone') {
|
||||
blocktypes.push(`${blockType}_ore`)
|
||||
}
|
||||
if (blockType.endsWith('ore')) {
|
||||
blocktypes.push(`deepslate_${blockType}`)
|
||||
}
|
||||
if (blockType === 'dirt') {
|
||||
blocktypes.push('grass_block')
|
||||
}
|
||||
|
||||
let collected = 0
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
let blocks = world.getNearestBlocks(bot, blocktypes, 64)
|
||||
if (exclude) {
|
||||
blocks = blocks.filter(
|
||||
block => !exclude.some(pos =>
|
||||
pos.x === block.position.x
|
||||
&& pos.y === block.position.y
|
||||
&& pos.z === block.position.z,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const movements = new bot.pathfinder.Movements(bot)
|
||||
movements.dontMineUnderFallingBlock = false
|
||||
blocks = blocks.filter(block => movements.safeToBreak(block))
|
||||
|
||||
if (blocks.length === 0) {
|
||||
log(bot, collected === 0
|
||||
? `No ${blockType} nearby to collect.`
|
||||
: `No more ${blockType} nearby to collect.`)
|
||||
break
|
||||
}
|
||||
|
||||
const block = blocks[0]
|
||||
await bot.tool.equipForBlock(block)
|
||||
const itemId = bot.heldItem ? bot.heldItem.type : null
|
||||
|
||||
if (!block.canHarvest(itemId)) {
|
||||
log(bot, `Don't have right tools to harvest ${blockType}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await bot.collectBlock.collect(block)
|
||||
collected++
|
||||
await autoLight(bot)
|
||||
}
|
||||
catch (err) {
|
||||
if (err.name === 'NoChests') {
|
||||
log(bot, `Failed to collect ${blockType}: Inventory full, no place to deposit.`)
|
||||
break
|
||||
}
|
||||
log(bot, `Failed to collect ${blockType}: ${err}.`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (bot.interrupt_code) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log(bot, `Collected ${collected} ${blockType}.`)
|
||||
return collected > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a torch if needed
|
||||
*/
|
||||
async function autoLight(bot: Bot): Promise<boolean> {
|
||||
if (world.shouldPlaceTorch(bot)) {
|
||||
async function autoLight(ctx: SkillContext): Promise<boolean> {
|
||||
if (world.shouldPlaceTorch(ctx.bot)) {
|
||||
try {
|
||||
const pos = world.getPosition(bot)
|
||||
return await placeBlock(bot, 'torch', pos.x, pos.y, pos.z, 'bottom', true)
|
||||
const pos = world.getPosition(ctx.bot)
|
||||
return await placeBlock(ctx, 'torch', pos.x, pos.y, pos.z, 'bottom', true)
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
@@ -107,26 +25,49 @@ async function autoLight(bot: Bot): Promise<boolean> {
|
||||
* Break a block at the specified position
|
||||
*/
|
||||
export async function breakBlockAt(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
validatePosition(x, y, z)
|
||||
|
||||
const block = bot.blockAt(new Vec3(x, y, z))
|
||||
if (isUnbreakableBlock(block))
|
||||
return false
|
||||
|
||||
if (ctx.allowCheats) {
|
||||
return breakWithCheats(ctx, x, y, z)
|
||||
}
|
||||
|
||||
await moveIntoRange(bot, block)
|
||||
|
||||
if (ctx.isCreative) {
|
||||
return breakInCreative(ctx, block, x, y, z)
|
||||
}
|
||||
|
||||
return breakInSurvival(ctx, block, x, y, z)
|
||||
}
|
||||
|
||||
function validatePosition(x: number, y: number, z: number) {
|
||||
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
|
||||
}
|
||||
function isUnbreakableBlock(block: any): boolean {
|
||||
return block.name === 'air' || block.name === 'water' || block.name === 'lava'
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
async function breakWithCheats(ctx: SkillContext, x: number, y: number, z: number): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
bot.chat(`/setblock ${Math.floor(x)} ${Math.floor(y)} ${Math.floor(z)} air`)
|
||||
log(ctx, `Used /setblock to break block at ${x}, ${y}, ${z}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
async function moveIntoRange(bot: any, block: any) {
|
||||
if (bot.entity.position.distanceTo(block.position) > 4.5) {
|
||||
const pos = block.position
|
||||
const movements = new bot.pathfinder.Movements(bot)
|
||||
@@ -135,18 +76,26 @@ export async function breakBlockAt(
|
||||
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
|
||||
}
|
||||
async function breakInCreative(ctx: SkillContext, block: any, x: number, y: number, z: number): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
await bot.dig(block, true)
|
||||
log(ctx, `Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
async function breakInSurvival(ctx: SkillContext, block: any, x: number, y: number, z: number): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
await bot.tool.equipForBlock(block)
|
||||
const itemId = bot.heldItem?.type
|
||||
if (!block.canHarvest(itemId)) {
|
||||
log(ctx, `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)}.`)
|
||||
log(ctx, `Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -154,7 +103,7 @@ export async function breakBlockAt(
|
||||
* Place a block at the specified position
|
||||
*/
|
||||
export async function placeBlock(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
blockType: string,
|
||||
x: number,
|
||||
y: number,
|
||||
@@ -162,100 +111,157 @@ export async function placeBlock(
|
||||
placeOn: BlockFace = 'bottom',
|
||||
dontCheat = false,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (!mc.getBlockId(blockType)) {
|
||||
log(bot, `Invalid block type: ${blockType}.`)
|
||||
log(ctx, `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
|
||||
if (ctx.allowCheats && !dontCheat) {
|
||||
return placeWithCheats(ctx, blockType, targetDest, placeOn)
|
||||
}
|
||||
|
||||
let itemName = blockType
|
||||
if (itemName === 'redstone_wire') {
|
||||
itemName = 'redstone'
|
||||
return placeWithoutCheats(ctx, blockType, targetDest, placeOn)
|
||||
}
|
||||
|
||||
function getBlockState(blockType: string, placeOn: BlockFace): string {
|
||||
const face = getInvertedFace(placeOn)
|
||||
let blockState = blockType
|
||||
|
||||
if (blockType.includes('torch') && placeOn !== 'bottom') {
|
||||
blockState = handleTorchState(blockType, placeOn, face)
|
||||
}
|
||||
|
||||
if (blockType.includes('button') || blockType === 'lever') {
|
||||
blockState = handleButtonLeverState(blockState, placeOn, face)
|
||||
}
|
||||
|
||||
if (needsFacingState(blockType)) {
|
||||
blockState += `[facing=${face}]`
|
||||
}
|
||||
|
||||
return blockState
|
||||
}
|
||||
|
||||
function getInvertedFace(placeOn: BlockFace): string {
|
||||
const faceMap = {
|
||||
north: 'south',
|
||||
south: 'north',
|
||||
east: 'west',
|
||||
west: 'east',
|
||||
}
|
||||
return faceMap[placeOn] || placeOn
|
||||
}
|
||||
|
||||
function handleTorchState(blockType: string, placeOn: BlockFace, face: string): string {
|
||||
let state = blockType.replace('torch', 'wall_torch')
|
||||
if (placeOn !== 'side' && placeOn !== 'top') {
|
||||
state += `[facing=${face}]`
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
function handleButtonLeverState(blockState: string, placeOn: BlockFace, face: string): string {
|
||||
if (placeOn === 'top') {
|
||||
return `${blockState}[face=ceiling]`
|
||||
}
|
||||
if (placeOn === 'bottom') {
|
||||
return `${blockState}[face=floor]`
|
||||
}
|
||||
return `${blockState}[facing=${face}]`
|
||||
}
|
||||
|
||||
function needsFacingState(blockType: string): boolean {
|
||||
return blockType === 'ladder'
|
||||
|| blockType === 'repeater'
|
||||
|| blockType === 'comparator'
|
||||
|| blockType.includes('stairs')
|
||||
}
|
||||
|
||||
async function placeWithCheats(
|
||||
ctx: SkillContext,
|
||||
blockType: string,
|
||||
targetDest: Vec3,
|
||||
placeOn: BlockFace,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const blockState = getBlockState(blockType, placeOn)
|
||||
|
||||
bot.chat(`/setblock ${targetDest.x} ${targetDest.y} ${targetDest.z} ${blockState}`)
|
||||
|
||||
if (blockType.includes('door')) {
|
||||
bot.chat(`/setblock ${targetDest.x} ${targetDest.y + 1} ${targetDest.z} ${blockState}[half=upper]`)
|
||||
}
|
||||
|
||||
if (blockType.includes('bed')) {
|
||||
bot.chat(`/setblock ${targetDest.x} ${targetDest.y} ${targetDest.z - 1} ${blockState}[part=head]`)
|
||||
}
|
||||
|
||||
log(ctx, `Used /setblock to place ${blockType} at ${targetDest}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
async function placeWithoutCheats(
|
||||
ctx: SkillContext,
|
||||
blockType: string,
|
||||
targetDest: Vec3,
|
||||
placeOn: BlockFace,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const itemName = blockType === 'redstone_wire' ? 'redstone' : blockType
|
||||
|
||||
let block = bot.inventory.items().find(item => item.name === itemName)
|
||||
if (!block && bot.game.gameMode === 'creative') {
|
||||
if (!block && ctx.isCreative) {
|
||||
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.`)
|
||||
log(ctx, `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}.`)
|
||||
log(ctx, `${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.`)
|
||||
if (!await clearBlockSpace(ctx, targetBlock, blockType)) {
|
||||
return false
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
}
|
||||
|
||||
const { buildOffBlock, faceVec } = findPlacementSpot(bot, targetDest, placeOn, emptyBlocks)
|
||||
if (!buildOffBlock) {
|
||||
log(ctx, `Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`)
|
||||
return false
|
||||
}
|
||||
|
||||
await moveIntoPosition(ctx, blockType, targetBlock)
|
||||
return await tryPlaceBlock(ctx, block, buildOffBlock, faceVec, blockType, targetDest)
|
||||
}
|
||||
|
||||
async function clearBlockSpace(
|
||||
ctx: SkillContext,
|
||||
targetBlock: any,
|
||||
blockType: string,
|
||||
): Promise<boolean> {
|
||||
const removed = await breakBlockAt(ctx, targetBlock.position.x, targetBlock.position.y, targetBlock.position.z,
|
||||
)
|
||||
if (!removed) {
|
||||
log(ctx, `Cannot place ${blockType} at ${targetBlock.position}: block in the way.`)
|
||||
return false
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
|
||||
function findPlacementSpot(bot: any, targetDest: Vec3, placeOn: BlockFace, emptyBlocks: string[]) {
|
||||
const dirMap = {
|
||||
top: new Vec3(0, 1, 0),
|
||||
bottom: new Vec3(0, -1, 0),
|
||||
@@ -265,6 +271,22 @@ export async function placeBlock(
|
||||
west: new Vec3(-1, 0, 0),
|
||||
}
|
||||
|
||||
const dirs = getPlacementDirections(placeOn, dirMap)
|
||||
|
||||
for (const d of dirs) {
|
||||
const block = bot.blockAt(targetDest.plus(d))
|
||||
if (!emptyBlocks.includes(block.name)) {
|
||||
return {
|
||||
buildOffBlock: block,
|
||||
faceVec: new Vec3(-d.x, -d.y, -d.z),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { buildOffBlock: null, faceVec: null }
|
||||
}
|
||||
|
||||
function getPlacementDirections(placeOn: BlockFace, dirMap: Record<string, Vec3>): Vec3[] {
|
||||
const dirs = []
|
||||
if (placeOn === 'side') {
|
||||
dirs.push(dirMap.north, dirMap.south, dirMap.east, dirMap.west)
|
||||
@@ -274,29 +296,13 @@ export async function placeBlock(
|
||||
}
|
||||
else {
|
||||
dirs.push(dirMap.bottom)
|
||||
log(bot, `Unknown placeOn value "${placeOn}". Defaulting to bottom.`)
|
||||
}
|
||||
dirs.push(...Object.values(dirMap).filter(d => !dirs.includes(d)))
|
||||
return dirs
|
||||
}
|
||||
|
||||
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))
|
||||
async function moveIntoPosition(ctx: SkillContext, blockType: string, targetBlock: any) {
|
||||
const { bot } = ctx
|
||||
const dontMoveFor = [
|
||||
'torch',
|
||||
'redstone_torch',
|
||||
@@ -312,33 +318,61 @@ export async function placeBlock(
|
||||
'water_bucket',
|
||||
]
|
||||
|
||||
const pos = bot.entity.position
|
||||
const posAbove = pos.plus(new Vec3(0, 1, 0))
|
||||
|
||||
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)
|
||||
await moveAwayFromBlock(bot, targetBlock)
|
||||
}
|
||||
|
||||
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 moveToBlock(bot, targetBlock)
|
||||
}
|
||||
}
|
||||
|
||||
async function moveAwayFromBlock(bot: any, targetBlock: any) {
|
||||
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)
|
||||
}
|
||||
|
||||
async function moveToBlock(bot: any, targetBlock: any) {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
async function tryPlaceBlock(
|
||||
ctx: SkillContext,
|
||||
block: any,
|
||||
buildOffBlock: any,
|
||||
faceVec: Vec3,
|
||||
blockType: string,
|
||||
targetDest: Vec3,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
await bot.equip(block, 'hand')
|
||||
await bot.lookAt(buildOffBlock.position)
|
||||
|
||||
try {
|
||||
await bot.placeBlock(buildOffBlock, faceVec)
|
||||
log(bot, `Placed ${blockType} at ${targetDest}.`)
|
||||
log(ctx, `Placed ${blockType} at ${targetDest}.`)
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
log(bot, `Failed to place ${blockType} at ${targetDest}.`)
|
||||
log(ctx, `Failed to place ${blockType} at ${targetDest}.`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -346,41 +380,49 @@ export async function placeBlock(
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function useDoor(ctx: SkillContext, doorPos: Vec3 | null = null): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
doorPos = doorPos || await findNearestDoor(bot)
|
||||
|
||||
if (!doorPos) {
|
||||
log(bot, 'Could not find a door to use.')
|
||||
log(ctx, 'Could not find a door to use.')
|
||||
return false
|
||||
}
|
||||
|
||||
await goToPosition(bot, doorPos.x, doorPos.y, doorPos.z, 1)
|
||||
await goToPosition(ctx, doorPos.x, doorPos.y, doorPos.z, 1)
|
||||
while (bot.pathfinder.isMoving()) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
return await operateDoor(ctx, doorPos)
|
||||
}
|
||||
|
||||
async function findNearestDoor(bot: any): Promise<Vec3 | null> {
|
||||
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) {
|
||||
return block.position
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function operateDoor(ctx: SkillContext, doorPos: Vec3): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const doorBlock = bot.blockAt(doorPos)
|
||||
await bot.lookAt(doorPos)
|
||||
|
||||
@@ -393,7 +435,7 @@ export async function useDoor(bot: Bot, doorPos: Vec3 | null = null): Promise<bo
|
||||
bot.setControlState('forward', false)
|
||||
await bot.activateBlock(doorBlock)
|
||||
|
||||
log(bot, `Used door at ${doorPos}.`)
|
||||
log(ctx, `Used door at ${doorPos}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -401,77 +443,216 @@ export async function useDoor(bot: Bot, doorPos: Vec3 | null = null): Promise<bo
|
||||
* Till and sow a block at the specified position
|
||||
*/
|
||||
export async function tillAndSow(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
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 { bot } = ctx
|
||||
const pos = { 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.`)
|
||||
const block = bot.blockAt(new Vec3(pos.x, pos.y, pos.z))
|
||||
if (!canTillBlock(block)) {
|
||||
log(ctx, `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.`)
|
||||
const above = bot.blockAt(new Vec3(pos.x, pos.y + 1, pos.z))
|
||||
if (!isBlockClear(above)) {
|
||||
log(ctx, `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)
|
||||
}
|
||||
await moveIntoRange(bot, block)
|
||||
|
||||
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 (!await tillBlock(ctx, block, pos)) {
|
||||
return false
|
||||
}
|
||||
|
||||
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 await sowSeeds(ctx, block, seedType, pos)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function canTillBlock(block: any): boolean {
|
||||
return block.name === 'grass_block' || block.name === 'dirt' || block.name === 'farmland'
|
||||
}
|
||||
|
||||
function isBlockClear(block: any): boolean {
|
||||
return block.name === 'air'
|
||||
}
|
||||
|
||||
async function tillBlock(ctx: SkillContext, block: any, pos: any): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (block.name === 'farmland') {
|
||||
return true
|
||||
}
|
||||
|
||||
const hoe = bot.inventory.items().find(item => item.name.includes('hoe'))
|
||||
if (!hoe) {
|
||||
log(ctx, 'Cannot till, no hoes.')
|
||||
return false
|
||||
}
|
||||
|
||||
await bot.equip(hoe, 'hand')
|
||||
await bot.activateBlock(block)
|
||||
log(ctx, `Tilled block x:${pos.x.toFixed(1)}, y:${pos.y.toFixed(1)}, z:${pos.z.toFixed(1)}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
async function sowSeeds(ctx: SkillContext, block: any, seedType: string, pos: any): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
seedType = fixSeedName(seedType)
|
||||
|
||||
const seeds = bot.inventory.items().find(item => item.name === seedType)
|
||||
if (!seeds) {
|
||||
log(ctx, `No ${seedType} to plant.`)
|
||||
return false
|
||||
}
|
||||
|
||||
await bot.equip(seeds, 'hand')
|
||||
await bot.placeBlock(block, new Vec3(0, -1, 0))
|
||||
log(ctx, `Planted ${seedType} at x:${pos.x.toFixed(1)}, y:${pos.y.toFixed(1)}, z:${pos.z.toFixed(1)}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
function fixSeedName(seedType: string): string {
|
||||
if (seedType.endsWith('seed') && !seedType.endsWith('seeds')) {
|
||||
return `${seedType}s` // Fix common mistake
|
||||
}
|
||||
return seedType
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the nearest block of a specific type
|
||||
*/
|
||||
export async function activateNearestBlock(bot: Bot, type: string): Promise<boolean> {
|
||||
export async function activateNearestBlock(ctx: SkillContext, type: string): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const block = world.getNearestBlock(bot, type, 16)
|
||||
if (!block) {
|
||||
log(bot, `Could not find any ${type} to activate.`)
|
||||
log(ctx, `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 moveIntoRange(bot, block)
|
||||
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)}.`)
|
||||
log(ctx, `Activated ${type} at x:${block.position.x.toFixed(1)}, y:${block.position.y.toFixed(1)}, z:${block.position.z.toFixed(1)}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
export async function collectBlock(
|
||||
ctx: SkillContext,
|
||||
blockType: string,
|
||||
num: number = 1,
|
||||
exclude: typeof Vec3[] | null = null,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (num < 1) {
|
||||
log(ctx, `Invalid number of blocks to collect: ${num}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
const blocktypes = getBlockTypes(blockType)
|
||||
let collected = 0
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
const blocks = getValidBlocks(ctx, blocktypes, exclude)
|
||||
|
||||
if (blocks.length === 0) {
|
||||
logNoBlocksMessage(ctx, blockType, collected)
|
||||
break
|
||||
}
|
||||
|
||||
const block = blocks[0]
|
||||
if (!await canHarvestBlock(ctx, block, blockType)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!await tryCollectBlock(ctx, block, blockType)) {
|
||||
break
|
||||
}
|
||||
|
||||
collected++
|
||||
|
||||
if (bot.interrupt_code) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log(ctx, `Collected ${collected} ${blockType}.`)
|
||||
return collected > 0
|
||||
}
|
||||
|
||||
function getBlockTypes(blockType: string): string[] {
|
||||
const blocktypes: string[] = [blockType]
|
||||
|
||||
const ores = ['coal', 'diamond', 'emerald', 'iron', 'gold', 'lapis_lazuli', 'redstone']
|
||||
if (ores.includes(blockType)) {
|
||||
blocktypes.push(`${blockType}_ore`)
|
||||
}
|
||||
if (blockType.endsWith('ore')) {
|
||||
blocktypes.push(`deepslate_${blockType}`)
|
||||
}
|
||||
if (blockType === 'dirt') {
|
||||
blocktypes.push('grass_block')
|
||||
}
|
||||
|
||||
return blocktypes
|
||||
}
|
||||
|
||||
function getValidBlocks(ctx: SkillContext, blocktypes: string[], exclude: typeof Vec3[] | null): any[] {
|
||||
const { bot } = ctx
|
||||
let blocks = world.getNearestBlocks(bot, blocktypes, 64)
|
||||
|
||||
if (exclude) {
|
||||
blocks = blocks.filter(
|
||||
block => !exclude.some(pos =>
|
||||
pos.x === block.position.x
|
||||
&& pos.y === block.position.y
|
||||
&& pos.z === block.position.z,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const movements = new bot.pathfinder.Movements(bot)
|
||||
movements.dontMineUnderFallingBlock = false
|
||||
return blocks.filter(block => movements.safeToBreak(block))
|
||||
}
|
||||
|
||||
function logNoBlocksMessage(ctx: SkillContext, blockType: string, collected: number): void {
|
||||
log(ctx, collected === 0
|
||||
? `No ${blockType} nearby to collect.`
|
||||
: `No more ${blockType} nearby to collect.`)
|
||||
}
|
||||
|
||||
async function canHarvestBlock(ctx: SkillContext, block: any, blockType: string): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
await bot.tool.equipForBlock(block)
|
||||
const itemId = bot.heldItem ? bot.heldItem.type : null
|
||||
|
||||
if (!block.canHarvest(itemId)) {
|
||||
log(ctx, `Don't have right tools to harvest ${blockType}.`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function tryCollectBlock(ctx: SkillContext, block: any, blockType: string): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
try {
|
||||
await bot.collectBlock.collect(block)
|
||||
await autoLight(ctx)
|
||||
return true
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof Error && err.name === 'NoChests') {
|
||||
log(ctx, `Failed to collect ${blockType}: Inventory full, no place to deposit.`)
|
||||
return false
|
||||
}
|
||||
log(ctx, `Failed to collect ${blockType}: ${err}.`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { Entity } from 'prismarine-entity'
|
||||
import type { SkillContext } from './base'
|
||||
import * as world from '../composables/world'
|
||||
import * as mc from '../utils/mcdata'
|
||||
import { log } from './base'
|
||||
@@ -7,7 +7,8 @@ import { log } from './base'
|
||||
/**
|
||||
* Equip the item with highest attack damage
|
||||
*/
|
||||
async function equipHighestAttack(bot: Bot): Promise<void> {
|
||||
async function equipHighestAttack(ctx: SkillContext): Promise<void> {
|
||||
const { bot } = ctx
|
||||
const weapons = bot.inventory.items().filter(item =>
|
||||
item.name.includes('sword')
|
||||
|| (item.name.includes('axe') && !item.name.includes('pickaxe')),
|
||||
@@ -37,61 +38,65 @@ async function equipHighestAttack(bot: Bot): Promise<void> {
|
||||
/**
|
||||
* 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')
|
||||
export async function attackNearest(
|
||||
ctx: SkillContext,
|
||||
mobType: string,
|
||||
kill = true,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const mob = world.getNearbyEntities(bot, 24).find(entity => entity.name === mobType)
|
||||
|
||||
if (mob) {
|
||||
return await attackEntity(ctx, mob, kill)
|
||||
}
|
||||
|
||||
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.`)
|
||||
log(ctx, `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> {
|
||||
export async function attackEntity(
|
||||
ctx: SkillContext,
|
||||
entity: Entity,
|
||||
kill = true,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const pos = entity.position
|
||||
await equipHighestAttack(bot)
|
||||
await equipHighestAttack(ctx)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
bot.pvp.attack(entity)
|
||||
while (world.getNearbyEntities(bot, 24).includes(entity)) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
if (ctx.shouldInterrupt) {
|
||||
bot.pvp.stop()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
log(ctx, `Successfully killed ${entity.name}.`)
|
||||
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')
|
||||
|
||||
export async function defendSelf(ctx: SkillContext, range = 9): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
let attacked = false
|
||||
let enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range)
|
||||
|
||||
while (enemy) {
|
||||
await equipHighestAttack(bot)
|
||||
await equipHighestAttack(ctx)
|
||||
|
||||
if (bot.entity.position.distanceTo(enemy.position) >= 4
|
||||
&& enemy.name !== 'creeper' && enemy.name !== 'phantom') {
|
||||
@@ -103,10 +108,10 @@ export async function defendSelf(bot: Bot, range = 9): Promise<boolean> {
|
||||
|
||||
if (bot.entity.position.distanceTo(enemy.position) <= 2) {
|
||||
try {
|
||||
const inverted_goal = bot.pathfinder.goals.GoalInvert(
|
||||
const invertedGoal = bot.pathfinder.goals.GoalInvert(
|
||||
bot.pathfinder.goals.GoalFollow(enemy, 2),
|
||||
)
|
||||
await bot.pathfinder.goto(inverted_goal, true)
|
||||
await bot.pathfinder.goto(invertedGoal, true)
|
||||
}
|
||||
catch { /* might error if entity dies, ignore */ }
|
||||
}
|
||||
@@ -116,7 +121,7 @@ export async function defendSelf(bot: Bot, range = 9): Promise<boolean> {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range)
|
||||
|
||||
if (bot.interrupt_code) {
|
||||
if (ctx.shouldInterrupt) {
|
||||
bot.pvp.stop()
|
||||
return false
|
||||
}
|
||||
@@ -124,10 +129,10 @@ export async function defendSelf(bot: Bot, range = 9): Promise<boolean> {
|
||||
|
||||
bot.pvp.stop()
|
||||
if (attacked) {
|
||||
log(bot, `Successfully defended self.`)
|
||||
log(ctx, 'Successfully defended self.')
|
||||
}
|
||||
else {
|
||||
log(bot, `No enemies nearby to defend self from.`)
|
||||
log(ctx, 'No enemies nearby to defend self from.')
|
||||
}
|
||||
return attacked
|
||||
}
|
||||
|
||||
@@ -1,90 +1,102 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { SkillContext } from './base'
|
||||
import * as world from '../composables/world'
|
||||
import * as mc from '../utils/mcdata'
|
||||
import { log } from './base'
|
||||
import { collectBlock } from './blocks'
|
||||
import { placeBlock } from './blocks'
|
||||
import { goToPosition } from './movement'
|
||||
|
||||
/**
|
||||
* Craft items from a recipe
|
||||
*/
|
||||
export async function craftRecipe(bot: Bot, itemName: string, num = 1): Promise<boolean> {
|
||||
export async function craftRecipe(ctx: SkillContext, itemName: string, num = 1): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
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!`)
|
||||
log(ctx, `${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)
|
||||
const itemId = mc.getItemId(itemName)
|
||||
if (itemId === null) {
|
||||
log(ctx, `Invalid item name: ${itemName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
let recipes = bot.recipesFor(itemId, null, 1, null)
|
||||
let craftingTable = null
|
||||
const craftingTableRange = 32
|
||||
|
||||
if (!recipes || recipes.length === 0) {
|
||||
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, true)
|
||||
recipes = bot.recipesFor(itemId, null, 1, true)
|
||||
if (!recipes || recipes.length === 0) {
|
||||
log(bot, `You do not have the resources to craft a ${itemName}.`)
|
||||
log(ctx, `You do not have the resources to craft a ${itemName}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
// Look for crafting table
|
||||
craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange)
|
||||
const worldCtx = { bot, botCtx: { bot, botName: bot.username } }
|
||||
craftingTable = world.getNearestBlock(worldCtx, 'crafting_table', craftingTableRange)
|
||||
if (!craftingTable) {
|
||||
// Try to place crafting table
|
||||
const hasTable = world.getInventoryCounts(bot).crafting_table > 0
|
||||
const inventory = world.getInventoryCounts(worldCtx)
|
||||
const hasTable = inventory.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
|
||||
const pos = world.getNearestFreeSpace(worldCtx, 1, 6)
|
||||
if (pos) {
|
||||
await placeBlock(ctx, 'crafting_table', pos.x, pos.y, pos.z)
|
||||
craftingTable = world.getNearestBlock(worldCtx, 'crafting_table', craftingTableRange)
|
||||
if (craftingTable) {
|
||||
recipes = bot.recipesFor(itemId, null, 1, craftingTable)
|
||||
placedTable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
log(bot, `Crafting ${itemName} requires a crafting table.`)
|
||||
log(ctx, `Crafting ${itemName} requires a crafting table.`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
else {
|
||||
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable)
|
||||
recipes = bot.recipesFor(itemId, null, 1, craftingTable)
|
||||
}
|
||||
}
|
||||
|
||||
if (!recipes || recipes.length === 0) {
|
||||
log(bot, `You do not have the resources to craft a ${itemName}. It requires: ${
|
||||
log(ctx, `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)
|
||||
if (placedTable && craftingTable) {
|
||||
await bot.collectBlock.collect(craftingTable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (craftingTable && bot.entity.position.distanceTo(craftingTable.position) > 4) {
|
||||
await goToPosition(bot, craftingTable.position.x, craftingTable.position.y, craftingTable.position.z, 4)
|
||||
await goToPosition(ctx, 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 worldCtx = { bot, botCtx: { bot, botName: bot.username } }
|
||||
const inventory = world.getInventoryCounts(worldCtx) // 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}.`)
|
||||
log(ctx, `Not enough ${craftLimit.limitingResource} to craft ${num}, crafted ${craftLimit.num}. You now have ${world.getInventoryCounts(worldCtx)[itemName]} ${itemName}.`)
|
||||
}
|
||||
else {
|
||||
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`)
|
||||
log(ctx, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(worldCtx)[itemName]} ${itemName}.`)
|
||||
}
|
||||
|
||||
if (placedTable) {
|
||||
await collectBlock(bot, 'crafting_table', 1)
|
||||
if (placedTable && craftingTable) {
|
||||
await bot.collectBlock.collect(craftingTable)
|
||||
}
|
||||
|
||||
// Equip any armor the bot may have crafted
|
||||
@@ -96,57 +108,67 @@ export async function craftRecipe(bot: Bot, itemName: string, num = 1): Promise<
|
||||
/**
|
||||
* Smelt items in a furnace
|
||||
*/
|
||||
export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<boolean> {
|
||||
export async function smeltItem(ctx: SkillContext, itemName: string, num = 1): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (!mc.isSmeltable(itemName)) {
|
||||
log(bot, `Cannot smelt ${itemName}. Hint: make sure you are smelting the 'raw' item.`)
|
||||
log(ctx, `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)
|
||||
const worldCtx = { bot, botCtx: { bot, botName: bot.username } }
|
||||
let furnaceBlock = world.getNearestBlock(worldCtx, 'furnace', furnaceRange)
|
||||
|
||||
if (!furnaceBlock) {
|
||||
// Try to place furnace
|
||||
const hasFurnace = world.getInventoryCounts(bot).furnace > 0
|
||||
const inventory = world.getInventoryCounts(worldCtx)
|
||||
const hasFurnace = inventory.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
|
||||
const pos = world.getNearestFreeSpace(worldCtx, 1, furnaceRange)
|
||||
if (pos) {
|
||||
await placeBlock(ctx, 'furnace', pos.x, pos.y, pos.z)
|
||||
furnaceBlock = world.getNearestBlock(worldCtx, 'furnace', furnaceRange)
|
||||
placedFurnace = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!furnaceBlock) {
|
||||
log(bot, 'There is no furnace nearby and you have no furnace.')
|
||||
log(ctx, '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)
|
||||
await goToPosition(ctx, 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)}.`)
|
||||
const itemId = mc.getItemId(itemName)
|
||||
if (itemId === null) {
|
||||
log(ctx, `Invalid item name: ${itemName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (inputItem && inputItem.type !== itemId && inputItem.count > 0) {
|
||||
log(ctx, `The furnace is currently smelting ${mc.getItemName(inputItem.type) ?? 'unknown'}.`)
|
||||
if (placedFurnace) {
|
||||
await collectBlock(bot, 'furnace', 1)
|
||||
await bot.collectBlock.collect(furnaceBlock)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the bot has enough items to smelt
|
||||
const invCounts = world.getInventoryCounts(bot)
|
||||
const invCounts = world.getInventoryCounts(worldCtx)
|
||||
if (!invCounts[itemName] || invCounts[itemName] < num) {
|
||||
log(bot, `You do not have enough ${itemName} to smelt.`)
|
||||
log(ctx, `You do not have enough ${itemName} to smelt.`)
|
||||
if (placedFurnace) {
|
||||
await collectBlock(bot, 'furnace', 1)
|
||||
await bot.collectBlock.collect(furnaceBlock)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -155,30 +177,30 @@ export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<bo
|
||||
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.`)
|
||||
log(ctx, `You have no fuel to smelt ${itemName}, you need coal, charcoal, or wood.`)
|
||||
if (placedFurnace) {
|
||||
await collectBlock(bot, 'furnace', 1)
|
||||
await bot.collectBlock.collect(furnaceBlock)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
log(bot, `Using ${fuel.name} as fuel.`)
|
||||
log(ctx, `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}.`)
|
||||
log(ctx, `You don't have enough ${fuel.name} to smelt ${num} ${itemName}; you need ${putFuel}.`)
|
||||
if (placedFurnace) {
|
||||
await collectBlock(bot, 'furnace', 1)
|
||||
await bot.collectBlock.collect(furnaceBlock)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
await furnace.putFuel(fuel.type, null, putFuel)
|
||||
log(bot, `Added ${putFuel} ${mc.getItemName(fuel.type)} to furnace fuel.`)
|
||||
log(ctx, `Added ${putFuel} ${mc.getItemName(fuel.type) ?? 'unknown'} to furnace fuel.`)
|
||||
}
|
||||
|
||||
// Put the items in the furnace
|
||||
await furnace.putInput(mc.getItemId(itemName), null, num)
|
||||
await furnace.putInput(itemId, null, num)
|
||||
|
||||
// Wait for the items to smelt
|
||||
let total = 0
|
||||
@@ -190,7 +212,8 @@ export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<bo
|
||||
await new Promise(resolve => setTimeout(resolve, 10000))
|
||||
let collected = false
|
||||
|
||||
if (furnace.outputItem()) {
|
||||
const outputItem = furnace.outputItem()
|
||||
if (outputItem) {
|
||||
smeltedItem = await furnace.takeOutput()
|
||||
if (smeltedItem) {
|
||||
total += smeltedItem.count
|
||||
@@ -203,7 +226,7 @@ export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<bo
|
||||
}
|
||||
|
||||
collectedLast = collected
|
||||
if (bot.interrupt_code) {
|
||||
if (ctx.shouldInterrupt) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -211,35 +234,37 @@ export async function smeltItem(bot: Bot, itemName: string, num = 1): Promise<bo
|
||||
await bot.closeWindow(furnace)
|
||||
|
||||
if (placedFurnace) {
|
||||
await collectBlock(bot, 'furnace', 1)
|
||||
await bot.collectBlock.collect(furnaceBlock)
|
||||
}
|
||||
|
||||
if (total === 0) {
|
||||
log(bot, `Failed to smelt ${itemName}.`)
|
||||
log(ctx, `Failed to smelt ${itemName}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (total < num) {
|
||||
log(bot, `Only smelted ${total} ${mc.getItemName(smeltedItem.type)}.`)
|
||||
log(ctx, `Only smelted ${total} ${mc.getItemName(smeltedItem?.type ?? 0) ?? 'unknown'}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
log(bot, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smeltedItem.type)}.`)
|
||||
log(ctx, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smeltedItem?.type ?? 0) ?? 'unknown'}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the nearest furnace
|
||||
*/
|
||||
export async function clearNearestFurnace(bot: Bot): Promise<boolean> {
|
||||
const furnaceBlock = world.getNearestBlock(bot, 'furnace', 32)
|
||||
export async function clearNearestFurnace(ctx: SkillContext): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const worldCtx = { bot, botCtx: { bot, botName: bot.username } }
|
||||
const furnaceBlock = world.getNearestBlock(worldCtx, 'furnace', 32)
|
||||
if (!furnaceBlock) {
|
||||
log(bot, 'No furnace nearby to clear.')
|
||||
log(ctx, '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)
|
||||
await goToPosition(ctx, furnaceBlock.position.x, furnaceBlock.position.y, furnaceBlock.position.z, 4)
|
||||
}
|
||||
|
||||
const furnace = await bot.openFurnace(furnaceBlock)
|
||||
@@ -247,15 +272,18 @@ export async function clearNearestFurnace(bot: Bot): Promise<boolean> {
|
||||
// Take the items out of the furnace
|
||||
let smeltedItem, inputItem, fuelItem
|
||||
|
||||
if (furnace.outputItem()) {
|
||||
const outputItem = furnace.outputItem()
|
||||
if (outputItem) {
|
||||
smeltedItem = await furnace.takeOutput()
|
||||
}
|
||||
|
||||
if (furnace.inputItem()) {
|
||||
const furnaceInput = furnace.inputItem()
|
||||
if (furnaceInput) {
|
||||
inputItem = await furnace.takeInput()
|
||||
}
|
||||
|
||||
if (furnace.fuelItem()) {
|
||||
const furnaceFuel = furnace.fuelItem()
|
||||
if (furnaceFuel) {
|
||||
fuelItem = await furnace.takeFuel()
|
||||
}
|
||||
|
||||
@@ -263,6 +291,6 @@ export async function clearNearestFurnace(bot: Bot): Promise<boolean> {
|
||||
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}.`)
|
||||
log(ctx, `Cleared furnace, received ${smeltedName}, ${inputName}, and ${fuelName}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { SkillContext } from './base'
|
||||
import * as world from '../composables/world'
|
||||
import { log } from './base'
|
||||
import { goToPosition } from './movement'
|
||||
@@ -6,7 +7,8 @@ import { goToPosition } from './movement'
|
||||
/**
|
||||
* Pick up nearby items
|
||||
*/
|
||||
export async function pickupNearbyItems(bot: Bot): Promise<boolean> {
|
||||
export async function pickupNearbyItems(ctx: SkillContext): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const distance = 8
|
||||
const getNearestItem = (bot: Bot) =>
|
||||
bot.nearestEntity(entity =>
|
||||
@@ -29,17 +31,18 @@ export async function pickupNearbyItems(bot: Bot): Promise<boolean> {
|
||||
pickedUp++
|
||||
}
|
||||
|
||||
log(bot, `Picked up ${pickedUp} items.`)
|
||||
log(ctx, `Picked up ${pickedUp} items.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Equip an item
|
||||
*/
|
||||
export async function equip(bot: Bot, itemName: string): Promise<boolean> {
|
||||
export async function equip(ctx: SkillContext, itemName: string): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const item = bot.inventory.slots.find(slot => slot && slot.name === itemName)
|
||||
if (!item) {
|
||||
log(bot, `You do not have any ${itemName} to equip.`)
|
||||
log(ctx, `You do not have any ${itemName} to equip.`)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -62,14 +65,15 @@ export async function equip(bot: Bot, itemName: string): Promise<boolean> {
|
||||
await bot.equip(item, 'hand')
|
||||
}
|
||||
|
||||
log(bot, `Equipped ${itemName}.`)
|
||||
log(ctx, `Equipped ${itemName}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard items
|
||||
*/
|
||||
export async function discard(bot: Bot, itemName: string, num = -1): Promise<boolean> {
|
||||
export async function discard(ctx: SkillContext, itemName: string, num = -1): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
let discarded = 0
|
||||
|
||||
while (true) {
|
||||
@@ -88,57 +92,59 @@ export async function discard(bot: Bot, itemName: string, num = -1): Promise<boo
|
||||
}
|
||||
|
||||
if (discarded === 0) {
|
||||
log(bot, `You do not have any ${itemName} to discard.`)
|
||||
log(ctx, `You do not have any ${itemName} to discard.`)
|
||||
return false
|
||||
}
|
||||
|
||||
log(bot, `Discarded ${discarded} ${itemName}.`)
|
||||
log(ctx, `Discarded ${discarded} ${itemName}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Put items in a chest
|
||||
*/
|
||||
export async function putInChest(bot: Bot, itemName: string, num = -1): Promise<boolean> {
|
||||
export async function putInChest(ctx: SkillContext, itemName: string, num = -1): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const chest = world.getNearestBlock(bot, 'chest', 32)
|
||||
if (!chest) {
|
||||
log(bot, 'Could not find a chest nearby.')
|
||||
log(ctx, '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.`)
|
||||
log(ctx, `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)
|
||||
await goToPosition(ctx, 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.`)
|
||||
log(ctx, `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> {
|
||||
export async function takeFromChest(ctx: SkillContext, itemName: string, num = -1): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const chest = world.getNearestBlock(bot, 'chest', 32)
|
||||
if (!chest) {
|
||||
log(bot, 'Could not find a chest nearby.')
|
||||
log(ctx, 'Could not find a chest nearby.')
|
||||
return false
|
||||
}
|
||||
|
||||
await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2)
|
||||
await goToPosition(ctx, 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.`)
|
||||
log(ctx, `Could not find any ${itemName} in the chest.`)
|
||||
await chestContainer.close()
|
||||
return false
|
||||
}
|
||||
@@ -147,31 +153,32 @@ export async function takeFromChest(bot: Bot, itemName: string, num = -1): Promi
|
||||
await chestContainer.withdraw(item.type, null, toTake)
|
||||
await chestContainer.close()
|
||||
|
||||
log(bot, `Successfully took ${toTake} ${itemName} from the chest.`)
|
||||
log(ctx, `Successfully took ${toTake} ${itemName} from the chest.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* View contents of a chest
|
||||
*/
|
||||
export async function viewChest(bot: Bot): Promise<boolean> {
|
||||
export async function viewChest(ctx: SkillContext): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const chest = world.getNearestBlock(bot, 'chest', 32)
|
||||
if (!chest) {
|
||||
log(bot, 'Could not find a chest nearby.')
|
||||
log(ctx, 'Could not find a chest nearby.')
|
||||
return false
|
||||
}
|
||||
|
||||
await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2)
|
||||
await goToPosition(ctx, 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.')
|
||||
log(ctx, 'The chest is empty.')
|
||||
}
|
||||
else {
|
||||
log(bot, 'The chest contains:')
|
||||
log(ctx, 'The chest contains:')
|
||||
for (const item of items) {
|
||||
log(bot, `${item.count} ${item.name}`)
|
||||
log(ctx, `${item.count} ${item.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +189,8 @@ export async function viewChest(bot: Bot): Promise<boolean> {
|
||||
/**
|
||||
* Consume (eat/drink) an item
|
||||
*/
|
||||
export async function consume(bot: Bot, itemName = ''): Promise<boolean> {
|
||||
export async function consume(ctx: SkillContext, itemName = ''): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
let item
|
||||
let name
|
||||
|
||||
@@ -192,13 +200,13 @@ export async function consume(bot: Bot, itemName = ''): Promise<boolean> {
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
log(bot, `You do not have any ${name} to eat.`)
|
||||
log(ctx, `You do not have any ${name} to eat.`)
|
||||
return false
|
||||
}
|
||||
|
||||
await bot.equip(item, 'hand')
|
||||
await bot.consume()
|
||||
log(bot, `Consumed ${item.name}.`)
|
||||
log(ctx, `Consumed ${item.name}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -206,21 +214,22 @@ export async function consume(bot: Bot, itemName = ''): Promise<boolean> {
|
||||
* Give items to a player
|
||||
*/
|
||||
export async function giveToPlayer(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
itemType: string,
|
||||
username: string,
|
||||
num = 1,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const player = bot.players[username]?.entity
|
||||
if (!player) {
|
||||
log(bot, `Could not find ${username}.`)
|
||||
log(ctx, `Could not find ${username}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
await goToPosition(bot, player.position.x, player.position.y, player.position.z, 3)
|
||||
await goToPosition(ctx, 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)
|
||||
await goToPosition(ctx, player.position.x, player.position.y, player.position.z, 1)
|
||||
}
|
||||
|
||||
if (bot.entity.position.distanceTo(player.position) < 2) {
|
||||
@@ -231,17 +240,17 @@ export async function giveToPlayer(
|
||||
|
||||
await bot.lookAt(player.position)
|
||||
|
||||
if (await discard(bot, itemType, num)) {
|
||||
if (await discard(ctx, itemType, num)) {
|
||||
let given = false
|
||||
bot.once('playerCollect', (collector, collected) => {
|
||||
if (collector.username === username) {
|
||||
log(bot, `${username} received ${itemType}.`)
|
||||
log(ctx, `${username} received ${itemType}.`)
|
||||
given = true
|
||||
}
|
||||
})
|
||||
|
||||
const start = Date.now()
|
||||
while (!given && !bot.interrupt_code) {
|
||||
while (!given && !ctx.shouldInterrupt) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
if (given) {
|
||||
return true
|
||||
@@ -252,6 +261,6 @@ export async function giveToPlayer(
|
||||
}
|
||||
}
|
||||
|
||||
log(bot, `Failed to give ${itemType} to ${username}, it was never received.`)
|
||||
log(ctx, `Failed to give ${itemType} to ${username}, it was never received.`)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { Entity } from 'prismarine-entity'
|
||||
import type { SkillContext } from './base'
|
||||
import * as world from '../composables/world'
|
||||
import { log } from './base'
|
||||
|
||||
@@ -7,25 +7,26 @@ import { log } from './base'
|
||||
* Navigate to a specific position
|
||||
*/
|
||||
export async function goToPosition(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
minDistance = 2,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (x == null || y == null || z == null) {
|
||||
log(bot, `Missing coordinates, given x:${x} y:${y} z:${z}`)
|
||||
log(ctx, `Missing coordinates, given x:${x} y:${y} z:${z}`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (bot.modes.isOn('cheat')) {
|
||||
if (ctx.allowCheats) {
|
||||
bot.chat(`/tp @s ${x} ${y} ${z}`)
|
||||
log(bot, `Teleported to ${x}, ${y}, ${z}.`)
|
||||
log(ctx, `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}.`)
|
||||
log(ctx, `You have reached ${x}, ${y}, ${z}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -33,25 +34,25 @@ export async function goToPosition(
|
||||
* Navigate to the nearest block of a specific type
|
||||
*/
|
||||
export async function goToNearestBlock(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
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}.`)
|
||||
log(ctx, `Maximum search range capped at ${MAX_RANGE}.`)
|
||||
range = MAX_RANGE
|
||||
}
|
||||
|
||||
const block = world.getNearestBlock(bot, blockType, range)
|
||||
const block = world.getNearestBlock(ctx.bot, blockType, range)
|
||||
if (!block) {
|
||||
log(bot, `Could not find any ${blockType} in ${range} blocks.`)
|
||||
log(ctx, `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)
|
||||
log(ctx, `Found ${blockType} at ${block.position}.`)
|
||||
await goToPosition(ctx, block.position.x, block.position.y, block.position.z, minDistance)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -59,11 +60,12 @@ export async function goToNearestBlock(
|
||||
* Navigate to the nearest entity of a specific type
|
||||
*/
|
||||
export async function goToNearestEntity(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
entityType: string,
|
||||
minDistance = 2,
|
||||
range = 64,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const entity = world.getNearestEntityWhere(
|
||||
bot,
|
||||
entity => entity.name === entityType,
|
||||
@@ -71,14 +73,14 @@ export async function goToNearestEntity(
|
||||
)
|
||||
|
||||
if (!entity) {
|
||||
log(bot, `Could not find any ${entityType} in ${range} blocks.`)
|
||||
log(ctx, `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.`)
|
||||
log(ctx, `Found ${entityType} ${distance} blocks away.`)
|
||||
await goToPosition(
|
||||
bot,
|
||||
ctx,
|
||||
entity.position.x,
|
||||
entity.position.y,
|
||||
entity.position.z,
|
||||
@@ -90,55 +92,52 @@ export async function goToNearestEntity(
|
||||
/**
|
||||
* Navigate to a specific player
|
||||
*/
|
||||
export async function goToPlayer(bot: Bot, username: string, distance = 3): Promise<boolean> {
|
||||
if (bot.modes.isOn('cheat')) {
|
||||
export async function goToPlayer(
|
||||
ctx: SkillContext,
|
||||
username: string,
|
||||
distance = 3,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
if (ctx.allowCheats) {
|
||||
bot.chat(`/tp @s ${username}`)
|
||||
log(bot, `Teleported to ${username}.`)
|
||||
log(ctx, `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}.`)
|
||||
log(ctx, `Could not find ${username}.`)
|
||||
return false
|
||||
}
|
||||
|
||||
await bot.pathfinder.goto(bot.pathfinder.goals.GoalFollow(player, distance), true)
|
||||
log(bot, `You have reached ${username}.`)
|
||||
log(ctx, `You have reached ${username}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow a player continuously
|
||||
*/
|
||||
export async function followPlayer(bot: Bot, username: string, distance = 4): Promise<boolean> {
|
||||
export async function followPlayer(
|
||||
ctx: SkillContext,
|
||||
username: string,
|
||||
distance = 4,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
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}.`)
|
||||
log(ctx, `You are now actively following player ${username}.`)
|
||||
|
||||
while (!bot.interrupt_code) {
|
||||
while (!ctx.shouldInterrupt) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
if (bot.modes.isOn('cheat')
|
||||
if (ctx.allowCheats
|
||||
&& 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')
|
||||
}
|
||||
await goToPlayer(ctx, username)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -147,12 +146,13 @@ export async function followPlayer(bot: Bot, username: string, distance = 4): Pr
|
||||
/**
|
||||
* Move away from current position
|
||||
*/
|
||||
export async function moveAway(bot: Bot, distance: number): Promise<boolean> {
|
||||
export async function moveAway(ctx: SkillContext, distance: number): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
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')) {
|
||||
if (ctx.allowCheats) {
|
||||
const move = new bot.pathfinder.Movements(bot)
|
||||
const path = await bot.pathfinder.getPathTo(move, invertedGoal, 10000)
|
||||
const lastMove = path.path[path.path.length - 1]
|
||||
@@ -168,7 +168,7 @@ export async function moveAway(bot: Bot, distance: number): Promise<boolean> {
|
||||
|
||||
await bot.pathfinder.goto(invertedGoal)
|
||||
const newPos = bot.entity.position
|
||||
log(bot, `Moved away from nearest entity to ${newPos}.`)
|
||||
log(ctx, `Moved away from nearest entity to ${newPos}.`)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -176,10 +176,11 @@ export async function moveAway(bot: Bot, distance: number): Promise<boolean> {
|
||||
* Move away from a specific entity
|
||||
*/
|
||||
export async function moveAwayFromEntity(
|
||||
bot: Bot,
|
||||
ctx: SkillContext,
|
||||
entity: Entity,
|
||||
distance = 16,
|
||||
): Promise<boolean> {
|
||||
const { bot } = ctx
|
||||
const goal = bot.pathfinder.goals.GoalFollow(entity, distance)
|
||||
const invertedGoal = bot.pathfinder.goals.GoalInvert(goal)
|
||||
await bot.pathfinder.goto(invertedGoal)
|
||||
@@ -189,51 +190,12 @@ export async function moveAwayFromEntity(
|
||||
/**
|
||||
* 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')
|
||||
|
||||
export async function stay(ctx: SkillContext, seconds = 30): Promise<boolean> {
|
||||
const start = Date.now()
|
||||
while (!bot.interrupt_code && (seconds === -1 || Date.now() - start < seconds * 1000)) {
|
||||
while (!ctx.shouldInterrupt && (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
|
||||
}
|
||||
/**
|
||||
* Sleep in the nearest bed within 32 blocks
|
||||
*/
|
||||
export async function goToBed(bot: Bot): Promise<boolean> {
|
||||
const beds: Vec3[] = bot.findBlocks({
|
||||
matching: (block: Block) => block.name.includes('bed'),
|
||||
maxDistance: 32,
|
||||
count: 1,
|
||||
})
|
||||
|
||||
if (beds.length === 0) {
|
||||
log(bot, 'Could not find a bed to sleep in.')
|
||||
return false
|
||||
}
|
||||
|
||||
const loc: Vec3 = beds[0]
|
||||
await goToPosition(bot, loc.x, loc.y, loc.z)
|
||||
|
||||
const bed: Block | null = bot.blockAt(loc)
|
||||
await bot.sleep(bed)
|
||||
log(bot, 'You are in bed.')
|
||||
|
||||
bot.modes.pause('unstuck')
|
||||
|
||||
while (bot.isSleeping) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
}
|
||||
|
||||
log(bot, 'You have woken up.')
|
||||
log(ctx, `Stayed for ${(Date.now() - start) / 1000} seconds.`)
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user