refactor: ctx
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user