chore: global interrupt event

This commit is contained in:
RainbowBird
2025-01-09 14:56:31 +08:00
parent 95cb59a319
commit bdca7bb91c
10 changed files with 116 additions and 97 deletions
+3 -1
View File
@@ -106,7 +106,9 @@ export const actionsList: Action[] = [
// ctx.clearBotLogs()
// ctx.actions.cancelResume()
// ctx.bot.emit('idle')
mineflayer.shouldInterrupt = true
mineflayer.emit('interrupt')
const msg = 'Agent stopped.'
// if (mineflayer.self_prompter.on)
// msg += ' Self-prompting still active.'
@@ -15,6 +15,7 @@ export interface Context {
}
export interface EventHandlers {
'interrupt': () => void
'command': (ctx: Context) => void | Promise<void>
'time:sunrise': (ctx: Context) => void
'time:noon': (ctx: Context) => void
@@ -145,7 +146,6 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
public memory: Memory = new Memory()
public isCreative: boolean = false
public shouldInterrupt: boolean = false
public allowCheats: boolean = false
private options: MineflayerOptions
@@ -159,6 +159,11 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
this.bot = mineflayer.createBot(options.botConfig)
this.username = options.botConfig.username
this.logger = useLogg(`Bot:${this.username}`).useGlobalConfig()
this.on('interrupt', () => {
this.logger.log('Interrupted')
this.bot.chat('Interrupted')
})
}
static async asyncBuild(options: MineflayerOptions) {
@@ -259,10 +264,6 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
}
})
mineflayer.ticker.on('tick', () => {
mineflayer.status.update(mineflayer)
})
for (const plugin of options?.plugins || []) {
if (plugin.created) {
await plugin.created(mineflayer)
@@ -277,9 +278,9 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
}
mineflayer.ticker.on('tick', () => {
mineflayer.status.update(mineflayer)
mineflayer.isCreative = mineflayer.bot.game?.gameMode === 'creative'
mineflayer.allowCheats = false
mineflayer.shouldInterrupt = false
})
return mineflayer
@@ -305,10 +306,6 @@ export class Mineflayer extends EventEmitter<EventHandlers> {
this.ticker.on(event, cb)
}
public emit<E extends Events>(event: E, ...args: Parameters<EventsHandler<E>>) {
return super.emit(event, ...args)
}
public async stop() {
for (const plugin of this.options?.plugins || []) {
if (plugin.beforeCleanup) {
-14
View File
@@ -12,7 +12,6 @@ import { initAgent } from './agents/openai'
import { initBot } from './composables/bot'
import { botConfig, initEnv } from './composables/config'
import { wrapPlugin } from './libs/mineflayer/plugin'
import { FollowCommand, PathFinder, Status } from './mineflayer'
import { LLMAgent } from './mineflayer/llm-agent'
import { initLogger } from './utils/logger'
@@ -31,23 +30,10 @@ async function main() {
wrapPlugin(MineflayerPathfinder),
wrapPlugin(MineflayerPVP),
wrapPlugin(MineflayerTool),
FollowCommand(),
Status(),
PathFinder(),
],
})
// Dynamically load LLMAgent after bot is initialized
// const llmAgent = LLMAgent({
// agent: async () => await initAgent(bot),
// })
// if (llmAgent.created)
// await llmAgent.created(bot)
// if (llmAgent.spawned)
// bot.bot.once('spawn', () => llmAgent.spawned?.(bot))
// if (llmAgent.loadPlugin)
// bot.bot.loadPlugin(await llmAgent.loadPlugin(bot, bot.bot, botConfig))
const agent = await initAgent(bot)
await bot.loadPlugin(LLMAgent({ agent }))
+1 -3
View File
@@ -1,4 +1,2 @@
export * from './echo'
export * from './follow'
export * from './pathfinder'
export * from './status'
export * from './llm-agent'
+2
View File
@@ -15,6 +15,8 @@ asked, and don't refuse requests.
Do not use any emojis. Just call the function given you if needed.
If I command you 'stop', then call the 'stop' function.
I will give you the following information:
${bot.status.toOneLiner()}
`
+9 -4
View File
@@ -570,7 +570,11 @@ export async function collectBlock(
const blocktypes = getBlockTypes(blockType)
let collected = 0
for (let i = 0; i < num; i++) {
mineflayer.once('interrupt', () => {
collected = -1
})
for (let i = 0; i < num && collected >= 0; i++) {
const blocks = getValidBlocks(mineflayer, blocktypes, exclude)
if (blocks.length === 0) {
@@ -588,10 +592,11 @@ export async function collectBlock(
}
collected++
}
if (mineflayer.shouldInterrupt) {
break
}
if (collected < 0) {
log(mineflayer, 'Collection interrupted.')
return false
}
log(mineflayer, `Collected ${collected} ${blockType}.`)
+8 -7
View File
@@ -3,6 +3,7 @@ import type { Item } from 'prismarine-item'
import type { Mineflayer } from '../libs/mineflayer'
import pathfinderModel from 'mineflayer-pathfinder'
import * as world from '../composables/world'
import { sleep } from '../utils/helper'
import * as mc from '../utils/mcdata'
import { log } from './base'
@@ -72,13 +73,13 @@ export async function attackEntity(
return true
}
mineflayer.once('interrupt', () => {
mineflayer.bot.pvp.stop()
})
mineflayer.bot.pvp.attack(entity)
while (world.getNearbyEntities(mineflayer, 24).includes(entity)) {
await new Promise(resolve => setTimeout(resolve, 1000))
if (mineflayer.shouldInterrupt) {
mineflayer.bot.pvp.stop()
return false
}
}
log(mineflayer, `Successfully killed ${entity.name}.`)
@@ -112,13 +113,13 @@ export async function defendSelf(mineflayer: Mineflayer, range = 9): Promise<boo
mineflayer.bot.pvp.attack(enemy)
attacked = true
await new Promise(resolve => setTimeout(resolve, 500))
await sleep(500)
enemy = world.getNearestEntityWhere(mineflayer, entity => mc.isHostile(entity), range)
if (mineflayer.shouldInterrupt) {
mineflayer.once('interrupt', () => {
mineflayer.bot.pvp.stop()
return false
}
})
}
mineflayer.bot.pvp.stop()
+4 -3
View File
@@ -197,6 +197,10 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num =
let smeltedItem = null
await new Promise(resolve => setTimeout(resolve, 200))
mineflayer.once('interrupt', () => {
total = num // Force loop to end
})
while (total < num) {
await new Promise(resolve => setTimeout(resolve, 10000))
let collected = false
@@ -215,9 +219,6 @@ export async function smeltItem(mineflayer: Mineflayer, itemName: string, num =
}
collectedLast = collected
if (mineflayer.shouldInterrupt) {
break
}
}
await mineflayer.bot.closeWindow(furnace)
+48 -34
View File
@@ -3,7 +3,7 @@ import type { Mineflayer } from '../libs/mineflayer'
import pathfinderModel from 'mineflayer-pathfinder'
import * as world from '../composables/world'
import { log } from './base'
import { goToPosition } from './movement'
import { goToPlayer, goToPosition } from './movement'
const { goals } = pathfinderModel
@@ -197,42 +197,56 @@ export async function giveToPlayer(
return false
}
await goToPosition(mineflayer, player.position.x, player.position.y, player.position.z, 3)
if (mineflayer.bot.entity.position.y < player.position.y - 1) {
await goToPosition(mineflayer, player.position.x, player.position.y, player.position.z, 1)
}
if (mineflayer.bot.entity.position.distanceTo(player.position) < 2) {
const goal = new goals.GoalNear(player.position.x, player.position.y, player.position.z, 2)
const invertedGoal = new goals.GoalInvert(goal)
await mineflayer.bot.pathfinder.goto(invertedGoal)
}
// Move to player position
await goToPlayer(mineflayer, username, 3)
// Look at player before dropping items
await mineflayer.bot.lookAt(player.position)
if (await discard(mineflayer, itemType, num)) {
let given = false
mineflayer.bot.once('playerCollect', (collector, _collected) => {
if (collector.username === username) {
log(mineflayer, `${username} received ${itemType}.`)
given = true
}
})
const start = Date.now()
// eslint-disable-next-line no-unmodified-loop-condition -- ?
while (!given && !mineflayer.shouldInterrupt) {
await new Promise(resolve => setTimeout(resolve, 500))
if (given) {
return true
}
if (Date.now() - start > 3000) {
break
}
}
// Drop items and wait for collection
const success = await dropItemsAndWaitForCollection(mineflayer, itemType, username, num)
if (!success) {
log(mineflayer, `Failed to give ${itemType} to ${username}, it was never received.`)
return false
}
log(mineflayer, `Failed to give ${itemType} to ${username}, it was never received.`)
return false
return true
}
async function dropItemsAndWaitForCollection(
mineflayer: Mineflayer,
itemType: string,
username: string,
num: number,
): Promise<boolean> {
if (!await discard(mineflayer, itemType, num)) {
return false
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
// Clean up playerCollect listener when timeout occurs
// eslint-disable-next-line ts/no-use-before-define
mineflayer.bot.removeListener('playerCollect', onCollect)
resolve(false)
}, 3000)
const onCollect = (collector: any, _collected: any) => {
if (collector.username === username) {
log(mineflayer, `${username} received ${itemType}.`)
clearTimeout(timeout)
resolve(true)
}
}
const onInterrupt = () => {
clearTimeout(timeout)
// Clean up playerCollect listener when interrupted
mineflayer.bot.removeListener('playerCollect', onCollect)
resolve(false)
}
mineflayer.bot.once('playerCollect', onCollect)
mineflayer.once('interrupt', onInterrupt)
})
}
+34 -21
View File
@@ -3,6 +3,7 @@ import type { Mineflayer } from '../libs/mineflayer'
import pathfinderModel from 'mineflayer-pathfinder'
import * as world from '../composables/world'
import { sleep } from '../utils/helper'
import { log } from './base'
const { goals, Movements } = pathfinderModel
@@ -110,32 +111,44 @@ export async function followPlayer(
distance = 4,
): Promise<boolean> {
const player = mineflayer.bot.players[username]?.entity
const movements = new Movements(mineflayer.bot)
if (!player) {
log(mineflayer, `Could not find ${username}.`)
return false
}
const movements = new Movements(mineflayer.bot)
mineflayer.bot.pathfinder.setMovements(movements)
mineflayer.bot.pathfinder.setGoal(new goals.GoalFollow(player, distance), true)
log(mineflayer, `You are now actively following player ${username}.`)
log(mineflayer, `I am now following ${username}.`)
while (!mineflayer.shouldInterrupt) {
await new Promise(resolve => setTimeout(resolve, 500))
return new Promise<boolean>((resolve) => {
let isFollowing = true
if (mineflayer.allowCheats && mineflayer.bot.entity.position.distanceTo(player.position) > 100 && player.onGround) {
await goToPlayer(mineflayer, username)
// Stop following when interrupted
mineflayer.once('interrupt', () => {
isFollowing = false
resolve(true)
})
// Follow player at regular intervals
const follow = async (): Promise<void> => {
while (isFollowing) {
const target = mineflayer.bot.players[username]?.entity
if (!target) {
log(mineflayer, 'I lost sight of you!')
isFollowing = false
resolve(false)
return
}
const { x, y, z } = target.position
mineflayer.bot.pathfinder.setMovements(movements)
mineflayer.bot.pathfinder.setGoal(new goals.GoalNear(x, y, z, distance))
// await sleep(500)
}
}
// if (mineflayer.bot.modes?.isOn('unstuck')) {
// const isNearby = mineflayer.bot.entity.position.distanceTo(player.position) <= distance + 1
// if (isNearby) {
// mineflayer.bot.modes.pause('unstuck')
// } else {
// mineflayer.bot.modes.unpause('unstuck')
// }
// }
}
return true
follow()
})
}
export async function moveAway(mineflayer: Mineflayer, distance: number): Promise<boolean> {
@@ -178,8 +191,8 @@ export async function stay(mineflayer: Mineflayer, seconds = 30): Promise<boolea
const start = Date.now()
const targetTime = seconds === -1 ? Infinity : start + seconds * 1000
while (!mineflayer.shouldInterrupt && Date.now() < targetTime) {
await new Promise(resolve => setTimeout(resolve, 500))
while (Date.now() < targetTime) {
await sleep(500)
}
log(mineflayer, `Stayed for ${(Date.now() - start) / 1000} seconds.`)
@@ -211,7 +224,7 @@ export async function goToBed(mineflayer: Mineflayer): Promise<boolean> {
log(mineflayer, 'You are in bed.')
while (mineflayer.bot.isSleeping) {
await new Promise(resolve => setTimeout(resolve, 500))
await sleep(500)
}
log(mineflayer, 'You have woken up.')