refactor: ctx

This commit is contained in:
RainbowBird
2025-01-05 23:20:45 +08:00
parent 52508aba5f
commit c505055d50
5 changed files with 63 additions and 61 deletions
+30 -24
View File
@@ -3,10 +3,15 @@ import mineflayer, { type Bot, type BotOptions } from 'mineflayer'
const logger = useLogg('bot').useGlobalConfig()
let botInstance: Bot | undefined
let ctx: Context | undefined
export interface Context {
bot: Bot
components: Map<string, ComponentLifecycle>
}
export interface Component {
(bot: Bot): void
(ctx: Context): ComponentLifecycle
}
export interface ComponentLifecycle {
@@ -15,62 +20,63 @@ export interface ComponentLifecycle {
export function createBot(options: BotOptions): Bot {
logger.withFields({ options }).log('Creating bot')
botInstance = mineflayer.createBot({
host: options.host,
port: options.port,
username: options.username,
password: options.password,
})
ctx = {
bot: mineflayer.createBot({
host: options.host,
port: options.port,
username: options.username,
password: options.password,
}),
components: new Map(),
}
botInstance.on('error', (err: Error) => {
ctx.bot.on('error', (err: Error) => {
logger.errorWithError('Bot error:', err)
})
botInstance.on('kicked', (reason: string) => {
ctx.bot.on('kicked', (reason: string) => {
logger.withFields({ reason }).error('Bot was kicked')
})
logger.log('Bot created')
return botInstance
return ctx.bot
}
export function useBot() {
const contexts = new Map<string, ComponentLifecycle>()
if (ctx == null || ctx.bot == null) {
throw new Error('Bot instance not found')
}
const cleanup = () => {
logger.log('Cleaning up bot and components')
contexts.forEach((context: ComponentLifecycle) => context.cleanup?.())
contexts.clear()
botInstance?.end()
ctx!.components.forEach((context: ComponentLifecycle) => context.cleanup?.())
ctx!.components.clear()
ctx!.bot.end()
}
const registerComponent = (componentName: string, component: Component) => {
logger.withFields({ componentName }).log('Registering new component')
const context = component(botInstance!)
const context = component(ctx!)
if (context != null)
contexts.set(componentName, context)
ctx!.components.set(componentName, context)
return context
}
const listComponents = () => {
return Array.from(contexts.keys())
return Array.from(ctx!.components.keys())
}
const getComponent = (componentName: string) => {
return contexts.get(componentName)
}
if (!botInstance) {
throw new Error('Bot instance not found')
return ctx!.components.get(componentName)
}
return {
ctx,
registerComponent,
listComponents,
getComponent,
cleanup,
bot: botInstance,
}
}
+6 -7
View File
@@ -1,23 +1,22 @@
import type { Bot } from 'mineflayer'
import type { ComponentLifecycle } from '../bot'
import type { ComponentLifecycle, Context } from '../bot'
import { useLogg } from '@guiiai/logg'
const logger = useLogg('echo').useGlobalConfig()
export function createEchoComponent(bot: Bot): ComponentLifecycle {
export function createEchoComponent(ctx: Context): ComponentLifecycle {
const onChat = (username: string, message: string) => {
if (username === bot.username)
if (username === ctx.bot.username)
return
logger.withFields({ username, message }).log('Chat message received')
bot.chat(message)
ctx.bot.chat(message)
}
bot.on('chat', onChat)
ctx.bot.on('chat', onChat)
return {
cleanup: () => {
bot.removeListener('chat', onChat)
ctx.bot.removeListener('chat', onChat)
},
}
}
+14 -15
View File
@@ -1,15 +1,14 @@
import type { Bot } from 'mineflayer'
import type { ComponentLifecycle } from '../bot'
import type { ComponentLifecycle, Context } from '../ctx.bot'
import { useLogg } from '@guiiai/logg'
import { goals, Movements, pathfinder } from 'mineflayer-pathfinder'
export function createFollowComponent(bot: Bot): ComponentLifecycle {
export function createFollowComponent(ctx: Context): ComponentLifecycle {
const RANGE_GOAL = 2 // get within this radius of the player
const logger = useLogg('follow').useGlobalConfig()
logger.log('Loading follow plugin')
bot.loadPlugin(pathfinder)
ctx.bot.loadPlugin(pathfinder)
let defaultMove: Movements
let following: string | null = null
@@ -18,21 +17,21 @@ export function createFollowComponent(bot: Bot): ComponentLifecycle {
if (!following)
return
const target = bot.players[following]?.entity
const target = ctx.bot.players[following]?.entity
if (!target) {
bot.chat('I lost sight of you!')
ctx.bot.chat('I lost sight of you!')
following = null
return
}
const { x: playerX, y: playerY, z: playerZ } = target.position
bot.pathfinder.setMovements(defaultMove)
bot.pathfinder.setGoal(new goals.GoalNear(playerX, playerY, playerZ, RANGE_GOAL))
ctx.bot.pathfinder.setMovements(defaultMove)
ctx.bot.pathfinder.setGoal(new goals.GoalNear(playerX, playerY, playerZ, RANGE_GOAL))
}
const onChat = (username: string, message: string) => {
if (username === bot.username)
if (username === ctx.bot.username)
return
if (message === 'follow') {
@@ -43,13 +42,13 @@ export function createFollowComponent(bot: Bot): ComponentLifecycle {
else if (message === 'stop') {
following = null
logger.log('Stopping follow')
bot.pathfinder.stop()
ctx.bot.pathfinder.stop()
}
}
bot.once('spawn', () => {
defaultMove = new Movements(bot)
bot.on('chat', onChat)
ctx.bot.once('spawn', () => {
defaultMove = new Movements(ctx.bot)
ctx.bot.on('chat', onChat)
// Continuously update path to follow player
const followInterval = setInterval(() => {
@@ -57,14 +56,14 @@ export function createFollowComponent(bot: Bot): ComponentLifecycle {
followPlayer()
}, 1000)
bot.once('end', () => {
ctx.bot.once('end', () => {
clearInterval(followInterval)
})
})
return {
cleanup: () => {
bot.removeListener('chat', onChat)
ctx.bot.removeListener('chat', onChat)
},
}
}
@@ -1,47 +1,46 @@
// This is an example that uses mineflayer-pathfinder to showcase how simple it is to walk to goals
import type { Bot } from 'mineflayer'
import type { ComponentLifecycle } from '../bot'
import type { ComponentLifecycle, Context } from '../bot'
import { useLogg } from '@guiiai/logg'
import { goals, Movements, pathfinder } from 'mineflayer-pathfinder'
export function createPathFinderComponent(bot: Bot): ComponentLifecycle {
export function createPathFinderComponent(ctx: Context): ComponentLifecycle {
const RANGE_GOAL = 1 // get within this radius of the player
const logger = useLogg('pathfinder').useGlobalConfig()
logger.log('Loading pathfinder plugin')
bot.loadPlugin(pathfinder)
ctx.bot.loadPlugin(pathfinder)
let defaultMove: Movements
const onChat = (username: string, message: string) => {
if (username === bot.username)
if (username === ctx.bot.username)
return
if (message !== 'come')
return
logger.withFields({ username, message }).log('Chat message received')
const target = bot.players[username]?.entity
const target = ctx.bot.players[username]?.entity
if (!target) {
bot.chat('I don\'t see you !')
ctx.bot.chat('I don\'t see you !')
return
}
const { x: playerX, y: playerY, z: playerZ } = target.position
bot.pathfinder.setMovements(defaultMove)
bot.pathfinder.setGoal(new goals.GoalNear(playerX, playerY, playerZ, RANGE_GOAL))
ctx.bot.pathfinder.setMovements(defaultMove)
ctx.bot.pathfinder.setGoal(new goals.GoalNear(playerX, playerY, playerZ, RANGE_GOAL))
}
bot.once('spawn', () => {
defaultMove = new Movements(bot)
bot.on('chat', onChat)
ctx.bot.once('spawn', () => {
defaultMove = new Movements(ctx.bot)
ctx.bot.on('chat', onChat)
})
return {
cleanup: () => {
bot.removeListener('chat', onChat)
ctx.bot.removeListener('chat', onChat)
},
}
}
+1 -2
View File
@@ -2,7 +2,6 @@ import process from 'node:process'
import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg'
import { createBot, useBot } from './bot'
import { createChestComponent } from './components/chest'
import { createEchoComponent } from './components/echo'
import { createFollowComponent } from './components/follow'
import { createPathFinderComponent } from './components/patchfinder'
@@ -22,7 +21,7 @@ async function main() {
registerComponent('echo', createEchoComponent)
registerComponent('pathfinder', createPathFinderComponent)
registerComponent('chest', createChestComponent)
// registerComponent('chest', createChestComponent)
registerComponent('follow', createFollowComponent)
process.on('SIGINT', () => {