From 4fc1c4e44afa18da8f29db5702019477146e31b3 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Tue, 7 Jan 2025 01:31:35 +0800 Subject: [PATCH] feat: bot internal event & health & message & death --- services/minecraft/src/composables/bot.ts | 175 ++++++++++++++++++- services/minecraft/src/composables/events.ts | 9 + 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 services/minecraft/src/composables/events.ts diff --git a/services/minecraft/src/composables/bot.ts b/services/minecraft/src/composables/bot.ts index ee0ebf649..42079d112 100644 --- a/services/minecraft/src/composables/bot.ts +++ b/services/minecraft/src/composables/bot.ts @@ -1,3 +1,4 @@ +import type { BotInternalEventHandlers, BotInternalEvents } from './events' import { useLogg } from '@guiiai/logg' import mineflayer, { type Bot, type BotOptions } from 'mineflayer' @@ -26,6 +27,9 @@ export interface BotContext { lastDamageTime: number lastDamageTaken: number } + + emit: (event: BotInternalEvents) => any + eventListeners: Record> } export interface Component { @@ -59,8 +63,36 @@ export function createBot(options: BotOptions): Bot { lastDamageTime: 0, lastDamageTaken: 0, }, + emit: (event: BotInternalEvents) => { + if (!ctx) + return + const listeners = ctx.eventListeners[event] + if (listeners) { + listeners.forEach(listener => listener()) + } + }, + eventListeners: { + 'time:sunrise': [], + 'time:noon': [], + 'time:sunset': [], + 'time:midnight': [], + }, } + ctx.bot.on('time', () => { + if (!ctx) + return + + if (ctx.bot.time.timeOfDay === 0) + ctx.emit('time:sunrise') + else if (ctx.bot.time.timeOfDay === 6000) + ctx.emit('time:noon') + else if (ctx.bot.time.timeOfDay === 12000) + ctx.emit('time:sunset') + else if (ctx.bot.time.timeOfDay === 18000) + ctx.emit('time:midnight') + }) + ctx.bot.on('health', () => { if (!ctx) return @@ -84,8 +116,27 @@ export function createBot(options: BotOptions): Bot { logger.error('Bot died') }) - ctx.bot.on('messagestr', () => { + ctx.bot.on('messagestr', async (message, _, jsonMsg) => { + if (!ctx) + return + // jsonMsg.translate: + // - death.attack.player + // message: + // - was slain by + // - drowned + if (jsonMsg.translate && jsonMsg.translate.startsWith('death') && message.startsWith(ctx.botName)) { + const deathPos = ctx.bot.entity.position + + // this.memory_bank.rememberPlace('last_death_position', deathPos.x, deathPos.y, deathPos.z) + let deathPosStr: string | undefined + if (deathPos) { + deathPosStr = `x: ${deathPos.x.toFixed(2)}, y: ${deathPos.y.toFixed(2)}, z: ${deathPos.x.toFixed(2)}` + } + + const dimension = ctx.bot.game.dimension + await handleMessage(ctx, 'system', `You died at position ${deathPosStr || 'unknown'} in the ${dimension} dimension with the final message: '${message}'. Your place of death has been saved as 'last_death_position' if you want to return. Previous actions were stopped and you have re-spawned.`) + } }) ctx.bot.on('end', (reason) => { @@ -104,6 +155,128 @@ export function createBot(options: BotOptions): Bot { return ctx.bot } +async function handleMessage(ctx: BotContext, source: string, message: string, maxResponses: number = Infinity) { + // if (!source || !message) { + // console.warn('Received empty message from', source); + // return false; + // } + + // let used_command = false; + // if (maxResponses === null) { + // maxResponses = settings.max_commands === -1 ? Infinity : settings.max_commands; + // } + // if (maxResponses === -1) { + // maxResponses = Infinity; + // } + + // const self_prompt = source === 'system' || source === ctx.botName; + // const from_other_bot = convoManager.isOtherAgent(source); + + // if (!self_prompt && !from_other_bot) { // from user, check for forced commands + // const user_command_name = containsCommand(message); + // if (user_command_name) { + // if (!commandExists(user_command_name)) { + // this.routeResponse(source, `Command '${user_command_name}' does not exist.`); + // return false; + // } + // this.routeResponse(source, `*${source} used ${user_command_name.substring(1)}*`); + // if (user_command_name === '!newAction') { + // // all user-initiated commands are ignored by the bot except for this one + // // add the preceding message to the history to give context for newAction + // this.history.add(source, message); + // } + // let execute_res = await executeCommand(this, message); + // if (execute_res) + // this.routeResponse(source, execute_res); + // return true; + // } + // } + + // if (from_other_bot) + // this.last_sender = source; + + // // Now translate the message + // message = await handleEnglishTranslation(message); + // console.log('received message from', source, ':', message); + + // const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source); + + // let behavior_log = this.bot.modes.flushBehaviorLog(); + // if (behavior_log.trim().length > 0) { + // const MAX_LOG = 500; + // if (behavior_log.length > MAX_LOG) { + // behavior_log = '...' + behavior_log.substring(behavior_log.length - MAX_LOG); + // } + // behavior_log = 'Recent behaviors log: \n' + behavior_log.substring(behavior_log.indexOf('\n')); + // await this.history.add('system', behavior_log); + // } + + // // Handle other user messages + // await this.history.add(source, message); + // this.history.save(); + + // if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting + // maxResponses = 1; // force only respond to this message, then let self-prompting take over + // for (let i=0; i 0) + // chat_message = `${pre_message} ${chat_message}`; + // this.routeResponse(source, chat_message); + // } + + // let execute_res = await executeCommand(this, res); + + // console.log('Agent executed:', command_name, 'and got:', execute_res); + // used_command = true; + + // if (execute_res) + // this.history.add('system', execute_res); + // else + // break; + // } + // else { // conversation response + // this.history.add(this.name, res); + // this.routeResponse(source, res); + // break; + // } + + // this.history.save(); + // } + + // return used_command; +} + export function useBot() { if (ctx == null || ctx.bot == null) { throw new Error('Bot instance not found') diff --git a/services/minecraft/src/composables/events.ts b/services/minecraft/src/composables/events.ts new file mode 100644 index 000000000..e00c57096 --- /dev/null +++ b/services/minecraft/src/composables/events.ts @@ -0,0 +1,9 @@ +export interface BotInternalEventHandlers { + 'time:sunrise': () => void + 'time:noon': () => void + 'time:sunset': () => void + 'time:midnight': () => void +} + +export type BotInternalEvents = keyof BotInternalEventHandlers +export type BotInternalEventsHandler = BotInternalEventHandlers[K]