refactor: skill ctx

This commit is contained in:
RainbowBird
2025-01-07 02:29:26 +08:00
parent 63aa146fca
commit 02ef3ec6ec
6 changed files with 707 additions and 494 deletions
+33 -5
View File
@@ -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'
+451 -270
View File
@@ -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
}
}
+40 -35
View File
@@ -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
}
+92 -64
View File
@@ -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
}
+44 -35
View File
@@ -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
}
+47 -85
View File
@@ -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
}