refactor: component and register
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
import mineflayer, { type Bot, type BotOptions } from 'mineflayer'
|
||||
|
||||
export interface Component {
|
||||
(bot: Bot): void
|
||||
}
|
||||
|
||||
export interface ComponentContext {
|
||||
cleanup: () => void
|
||||
}
|
||||
|
||||
const logger = useLogg('bot').useGlobalConfig()
|
||||
|
||||
export function useBot() {
|
||||
let botInstance: Bot | null = null
|
||||
const contexts = new Map<string, ComponentContext>()
|
||||
|
||||
const 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,
|
||||
})
|
||||
|
||||
botInstance.on('error', (err: Error) => {
|
||||
logger.errorWithError('Bot error:', err)
|
||||
})
|
||||
|
||||
botInstance.on('kicked', (reason: string) => {
|
||||
logger.withFields({ reason }).error('Bot was kicked')
|
||||
})
|
||||
|
||||
logger.log('Bot created')
|
||||
return botInstance
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
logger.log('Cleaning up bot and components')
|
||||
contexts.forEach((context: ComponentContext) => context.cleanup?.())
|
||||
contexts.clear()
|
||||
botInstance?.end()
|
||||
}
|
||||
|
||||
const ensureBot = (): Bot => {
|
||||
if (!botInstance)
|
||||
throw new Error('Bot is not initialized')
|
||||
return botInstance
|
||||
}
|
||||
|
||||
const registerComponent = (componentName: string, component: Component) => {
|
||||
logger.withFields({ componentName }).log('Registering new component')
|
||||
const bot = ensureBot()
|
||||
const context = component(bot)
|
||||
|
||||
if (context != null)
|
||||
contexts.set(componentName, context)
|
||||
return context
|
||||
}
|
||||
|
||||
const listComponents = () => {
|
||||
return Array.from(contexts.keys())
|
||||
}
|
||||
|
||||
const getComponent = (componentName: string) => {
|
||||
return contexts.get(componentName)
|
||||
}
|
||||
|
||||
return {
|
||||
createBot,
|
||||
cleanup,
|
||||
registerComponent,
|
||||
listComponents,
|
||||
getComponent,
|
||||
getBot: ensureBot,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
|
||||
/*
|
||||
* Watch out, this is a big one!
|
||||
*
|
||||
* This is a demonstration to show you how you can interact with:
|
||||
* - Chests
|
||||
* - Furnaces
|
||||
* - Dispensers
|
||||
* - Enchantment Tables
|
||||
*
|
||||
* and of course with your own inventory.
|
||||
*
|
||||
* Each of the main commands makes the bot interact with the block and open
|
||||
* its window. From there you can send another set of commands to actually
|
||||
* interact with the window and make awesome stuff.
|
||||
*
|
||||
* There's also a bonus example which shows you how to use the /invsee command
|
||||
* to see what items another user has in his inventory and what items he has
|
||||
* equipped.
|
||||
* This last one is usually reserved to Server Ops so make sure you have the
|
||||
* appropriate permission to do it or it won't work.
|
||||
*/
|
||||
|
||||
export function createChestComponent(bot: Bot) {
|
||||
bot.on('experience', () => {
|
||||
bot.chat(`I am level ${bot.experience.level}`)
|
||||
})
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username)
|
||||
return
|
||||
switch (true) {
|
||||
case /^list$/.test(message):
|
||||
sayItems()
|
||||
break
|
||||
case /^chest$/.test(message):
|
||||
watchChest(false, ['chest', 'ender_chest', 'trapped_chest'])
|
||||
break
|
||||
case /^furnace$/.test(message):
|
||||
watchFurnace()
|
||||
break
|
||||
case /^dispenser$/.test(message):
|
||||
watchChest(false, ['dispenser'])
|
||||
break
|
||||
case /^enchant$/.test(message):
|
||||
watchEnchantmentTable()
|
||||
break
|
||||
case /^chestminecart$/.test(message):
|
||||
watchChest(true)
|
||||
break
|
||||
case /^invsee \w+( \d)?$/.test(message): {
|
||||
// invsee Herobrine [or]
|
||||
// invsee Herobrine 1
|
||||
const command = message.split(' ')
|
||||
useInvsee(command[0], command[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function sayItems(items = bot.inventory.items()) {
|
||||
const output = items.map(itemToString).join(', ')
|
||||
if (output) {
|
||||
bot.chat(output)
|
||||
}
|
||||
else {
|
||||
bot.chat('empty')
|
||||
}
|
||||
}
|
||||
|
||||
async function watchChest(minecart: boolean, blocks: string[] = []) {
|
||||
let chestToOpen
|
||||
if (minecart) {
|
||||
chestToOpen = Object.keys(bot.entities)
|
||||
.map(id => bot.entities[id])
|
||||
.find(e => e.entityType === bot.registry.entitiesByName.chest_minecart
|
||||
&& e.objectData.intField === 1
|
||||
&& bot.entity.position.distanceTo(e.position) < 3)
|
||||
if (!chestToOpen) {
|
||||
bot.chat('no chest minecart found')
|
||||
return
|
||||
}
|
||||
}
|
||||
else {
|
||||
chestToOpen = bot.findBlock({
|
||||
matching: blocks.map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!chestToOpen) {
|
||||
bot.chat('no chest found')
|
||||
return
|
||||
}
|
||||
}
|
||||
const chest = await bot.openContainer(chestToOpen)
|
||||
sayItems(chest.containerItems())
|
||||
chest.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`chest update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
chest.on('close', () => {
|
||||
bot.chat('chest closed')
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username: string, message: string) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeChest()
|
||||
break
|
||||
case /^withdraw \d+ \w+$/.test(message):
|
||||
// withdraw amount name
|
||||
// ex: withdraw 16 stick
|
||||
withdrawItem(command[2], command[1])
|
||||
break
|
||||
case /^deposit \d+ \w+$/.test(message):
|
||||
// deposit amount name
|
||||
// ex: deposit 16 stick
|
||||
depositItem(command[2], command[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function closeChest() {
|
||||
chest.close()
|
||||
bot.removeListener('chat', onChat)
|
||||
}
|
||||
|
||||
async function withdrawItem(name: string, amount: number) {
|
||||
const item = itemByName(chest.containerItems(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await chest.withdraw(item.type, null, amount)
|
||||
bot.chat(`withdrew ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to withdraw ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function depositItem(name: string, amount: number) {
|
||||
const item = itemByName(chest.items(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await chest.deposit(item.type, null, amount)
|
||||
bot.chat(`deposited ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to deposit ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function watchFurnace() {
|
||||
const furnaceBlock = bot.findBlock({
|
||||
matching: ['furnace', 'lit_furnace'].filter(name => bot.registry.blocksByName[name] !== undefined).map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!furnaceBlock) {
|
||||
bot.chat('no furnace found')
|
||||
return
|
||||
}
|
||||
const furnace = await bot.openFurnace(furnaceBlock)
|
||||
let output = ''
|
||||
output += `input: ${itemToString(furnace.inputItem())}, `
|
||||
output += `fuel: ${itemToString(furnace.fuelItem())}, `
|
||||
output += `output: ${itemToString(furnace.outputItem())}`
|
||||
bot.chat(output)
|
||||
|
||||
furnace.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`furnace update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
furnace.on('close', () => {
|
||||
bot.chat('furnace closed')
|
||||
})
|
||||
furnace.on('update', () => {
|
||||
console.log(`fuel: ${Math.round(furnace.fuel * 100)}% progress: ${Math.round(furnace.progress * 100)}%`)
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username: string, message: string) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeFurnace()
|
||||
break
|
||||
case /^(input|fuel) \d+ \w+$/.test(message):
|
||||
// input amount name
|
||||
// ex: input 32 coal
|
||||
putInFurnace(command[0], command[2], command[1])
|
||||
break
|
||||
case /^take (input|fuel|output)$/.test(message):
|
||||
// take what
|
||||
// ex: take output
|
||||
takeFromFurnace(command[0])
|
||||
break
|
||||
}
|
||||
|
||||
function closeFurnace() {
|
||||
furnace.close()
|
||||
bot.removeListener('chat', onChat)
|
||||
}
|
||||
|
||||
async function putInFurnace(where: string, name: string, amount: number) {
|
||||
const item = itemByName(furnace.items(), name)
|
||||
if (item) {
|
||||
const fn = {
|
||||
input: furnace.putInput,
|
||||
fuel: furnace.putFuel,
|
||||
}[where]
|
||||
try {
|
||||
await fn.call(furnace, item.type, null, amount)
|
||||
bot.chat(`put ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to put ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function takeFromFurnace(what: string) {
|
||||
const fn = {
|
||||
input: furnace.takeInput,
|
||||
fuel: furnace.takeFuel,
|
||||
output: furnace.takeOutput,
|
||||
}[what]
|
||||
try {
|
||||
const item = await fn.call(furnace)
|
||||
bot.chat(`took ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('unable to take')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function watchEnchantmentTable() {
|
||||
const enchantTableBlock = bot.findBlock({
|
||||
matching: ['enchanting_table'].map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!enchantTableBlock) {
|
||||
bot.chat('no enchantment table found')
|
||||
return
|
||||
}
|
||||
const table = await bot.openEnchantmentTable(enchantTableBlock)
|
||||
bot.chat(itemToString(table.targetItem()))
|
||||
|
||||
table.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`enchantment table update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
table.on('close', () => {
|
||||
bot.chat('enchantment table closed')
|
||||
})
|
||||
table.on('ready', () => {
|
||||
bot.chat(`ready to enchant. choices are ${table.enchantments.map(o => o.level).join(', ')}`)
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username: string, message: string) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeEnchantmentTable()
|
||||
break
|
||||
case /^put \w+$/.test(message):
|
||||
// put name
|
||||
// ex: put diamondsword
|
||||
putItem(command[1])
|
||||
break
|
||||
case /^add lapis$/.test(message):
|
||||
addLapis()
|
||||
break
|
||||
case /^enchant \d+$/.test(message):
|
||||
// enchant choice
|
||||
// ex: enchant 2
|
||||
enchantItem(command[1])
|
||||
break
|
||||
case /^take$/.test(message):
|
||||
takeEnchantedItem()
|
||||
break
|
||||
}
|
||||
|
||||
function closeEnchantmentTable() {
|
||||
table.close()
|
||||
}
|
||||
|
||||
async function putItem(name: string) {
|
||||
const item = itemByName(table.window.items(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await table.putTargetItem(item)
|
||||
bot.chat(`I put ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`error putting ${itemToString(item)}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function addLapis() {
|
||||
const item = itemByType(table.window.items(), ['dye', 'purple_dye', 'lapis_lazuli'].filter(name => bot.registry.itemByName[name] !== undefined)
|
||||
.map(name => bot.registry.itemByName[name].id))
|
||||
if (item) {
|
||||
try {
|
||||
await table.putLapis(item)
|
||||
bot.chat(`I put ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`error putting ${itemToString(item)}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat('I don\'t have any lapis')
|
||||
}
|
||||
}
|
||||
|
||||
async function enchantItem(choice: string) {
|
||||
choice = Number.parseInt(choice, 10)
|
||||
try {
|
||||
const item = await table.enchant(choice)
|
||||
bot.chat(`enchanted ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('error enchanting')
|
||||
}
|
||||
}
|
||||
|
||||
async function takeEnchantedItem() {
|
||||
try {
|
||||
const item = await table.takeTargetItem()
|
||||
bot.chat(`got ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('error getting item')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useInvsee(username: string, showEquipment: boolean) {
|
||||
bot.once('windowOpen', (window) => {
|
||||
const count = window.containerItems().length
|
||||
const what = showEquipment ? 'equipment' : 'inventory items'
|
||||
if (count) {
|
||||
bot.chat(`${username}'s ${what}:`)
|
||||
sayItems(window.containerItems())
|
||||
}
|
||||
else {
|
||||
bot.chat(`${username} has no ${what}`)
|
||||
}
|
||||
})
|
||||
if (showEquipment) {
|
||||
// any extra parameter triggers the easter egg
|
||||
// and shows the other player's equipment
|
||||
bot.chat(`/invsee ${username} 1`)
|
||||
}
|
||||
else {
|
||||
bot.chat(`/invsee ${username}`)
|
||||
}
|
||||
}
|
||||
|
||||
function itemToString(item: Item | null): string {
|
||||
if (item) {
|
||||
return `${item.name} x ${item.count}`
|
||||
}
|
||||
else {
|
||||
return '(nothing)'
|
||||
}
|
||||
}
|
||||
|
||||
function itemByType(items: Item[], type: number): Item | null {
|
||||
let item: Item | null = null
|
||||
let i: number
|
||||
for (i = 0; i < items.length; ++i) {
|
||||
item = items[i]
|
||||
if (item && item.type === type)
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function itemByName(items: Item[], name: string): Item | null {
|
||||
let item: Item | null = null
|
||||
let i: number
|
||||
for (i = 0; i < items.length; ++i) {
|
||||
item = items[i]
|
||||
if (item && item.name === name)
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Bot } from 'mineflayer'
|
||||
import type { ComponentContext } from '../bot'
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
|
||||
const logger = useLogg('echo').useGlobalConfig()
|
||||
|
||||
export function createEchoComponent(botInstance: Bot): ComponentContext {
|
||||
const onChat = (username: string, message: string) => {
|
||||
if (username === botInstance.username)
|
||||
return
|
||||
|
||||
logger.withFields({ username, message }).log('Chat message received')
|
||||
botInstance.chat(message)
|
||||
}
|
||||
|
||||
botInstance.on('chat', onChat)
|
||||
|
||||
return {
|
||||
cleanup: () => {
|
||||
botInstance.removeListener('chat', onChat)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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 { ComponentContext } from '../bot'
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
import { goals, Movements, pathfinder } from 'mineflayer-pathfinder'
|
||||
|
||||
export function createPathFinderComponent(botInstance: Bot): ComponentContext {
|
||||
const RANGE_GOAL = 1 // get within this radius of the player
|
||||
|
||||
const logger = useLogg('pathfinder').useGlobalConfig()
|
||||
logger.log('Loading pathfinder plugin')
|
||||
|
||||
botInstance.loadPlugin(pathfinder)
|
||||
|
||||
let defaultMove: Movements
|
||||
|
||||
const onChat = (username: string, message: string) => {
|
||||
if (username === botInstance.username)
|
||||
return
|
||||
if (message !== 'come')
|
||||
return
|
||||
|
||||
logger.withFields({ username, message }).log('Chat message received')
|
||||
const target = botInstance.players[username]?.entity
|
||||
if (!target) {
|
||||
botInstance.chat('I don\'t see you !')
|
||||
return
|
||||
}
|
||||
|
||||
const { x: playerX, y: playerY, z: playerZ } = target.position
|
||||
|
||||
botInstance.pathfinder.setMovements(defaultMove)
|
||||
botInstance.pathfinder.setGoal(new goals.GoalNear(playerX, playerY, playerZ, RANGE_GOAL))
|
||||
}
|
||||
|
||||
botInstance.once('spawn', () => {
|
||||
logger.log('Spawning bot')
|
||||
defaultMove = new Movements(botInstance)
|
||||
botInstance.on('chat', onChat)
|
||||
})
|
||||
|
||||
return {
|
||||
cleanup: () => {
|
||||
botInstance.removeListener('chat', onChat)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { BotOptions } from 'mineflayer'
|
||||
|
||||
export const defaultConfig: BotOptions = {
|
||||
host: 'localhost',
|
||||
username: 'airi',
|
||||
port: 49415,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
+23
-543
@@ -1,550 +1,30 @@
|
||||
import mineflayer from 'mineflayer'
|
||||
import { goals, Movements, pathfinder } from 'mineflayer-pathfinder'
|
||||
import process from 'node:process'
|
||||
import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg'
|
||||
import { useBot } from './bot'
|
||||
import { createEchoComponent } from './components/echo'
|
||||
import { createPathFinderComponent } from './components/patchfinder'
|
||||
import { defaultConfig } from './config'
|
||||
|
||||
async function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
const logger = useLogg('main').useGlobalConfig()
|
||||
|
||||
async function main() {
|
||||
await sleep(5000)
|
||||
// await sleep(5000)
|
||||
setGlobalLogLevel(LogLevel.Debug)
|
||||
setGlobalFormat(Format.Pretty)
|
||||
|
||||
const bot = mineflayer.createBot({
|
||||
host: '10.0.0.100', // minecraft 服务器的 IP 地址
|
||||
username: 'airi', // minecraft 用户名
|
||||
// password: '12345678', // minecraft 密码, 如果你玩的是不需要正版验证的服务器,请注释掉。
|
||||
port: 56304, // 默认使用 25565,如果你的服务器端口不是这个请取消注释并填写。
|
||||
// version: false, // 如果需要指定re使用一个版本或快照时,请取消注释并手动填写(如:"1.8.9" 或 "1.16.5"),否则会自动设置。
|
||||
// auth: 'mojang' // 如果需要使用微软账号登录时,请取消注释,然后将值设置为 'microsoft',否则会自动设置为 'mojang'。
|
||||
const bot = useBot()
|
||||
bot.createBot(defaultConfig)
|
||||
|
||||
bot.registerComponent('echo', createEchoComponent)
|
||||
bot.registerComponent('pathfinder', createPathFinderComponent)
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
bot.cleanup()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username)
|
||||
return
|
||||
bot.chat(message)
|
||||
})
|
||||
|
||||
// 记录错误和被踢出服务器的原因:
|
||||
bot.on('kicked', console.log)
|
||||
bot.on('error', console.log)
|
||||
|
||||
bot.loadPlugin(pathfinder)
|
||||
|
||||
const RANGE_GOAL = 1
|
||||
bot.once('spawn', () => {
|
||||
const defaultMove = new Movements(bot)
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username)
|
||||
return
|
||||
if (message !== 'come')
|
||||
return
|
||||
const target = bot.players[username]?.entity
|
||||
if (!target) {
|
||||
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))
|
||||
})
|
||||
})
|
||||
|
||||
/** TP */
|
||||
let target: Entity = null
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username)
|
||||
return
|
||||
target = bot.players[username].entity
|
||||
let entity
|
||||
switch (message) {
|
||||
case 'forward':
|
||||
bot.setControlState('forward', true)
|
||||
break
|
||||
case 'back':
|
||||
bot.setControlState('back', true)
|
||||
break
|
||||
case 'left':
|
||||
bot.setControlState('left', true)
|
||||
break
|
||||
case 'right':
|
||||
bot.setControlState('right', true)
|
||||
break
|
||||
case 'sprint':
|
||||
bot.setControlState('sprint', true)
|
||||
break
|
||||
case 'stop':
|
||||
bot.clearControlStates()
|
||||
break
|
||||
case 'jump':
|
||||
bot.setControlState('jump', true)
|
||||
bot.setControlState('jump', false)
|
||||
break
|
||||
case 'jump a lot':
|
||||
bot.setControlState('jump', true)
|
||||
break
|
||||
case 'stop jumping':
|
||||
bot.setControlState('jump', false)
|
||||
break
|
||||
case 'attack':
|
||||
entity = bot.nearestEntity()
|
||||
if (entity) {
|
||||
bot.attack(entity, true)
|
||||
}
|
||||
else {
|
||||
bot.chat('no nearby entities')
|
||||
}
|
||||
break
|
||||
case 'mount':
|
||||
entity = bot.nearestEntity((entity) => { return entity.name === 'minecart' })
|
||||
if (entity) {
|
||||
bot.mount(entity)
|
||||
}
|
||||
else {
|
||||
bot.chat('no nearby objects')
|
||||
}
|
||||
break
|
||||
case 'dismount':
|
||||
bot.dismount()
|
||||
break
|
||||
case 'move vehicle forward':
|
||||
bot.moveVehicle(0.0, 1.0)
|
||||
break
|
||||
case 'move vehicle backward':
|
||||
bot.moveVehicle(0.0, -1.0)
|
||||
break
|
||||
case 'move vehicle left':
|
||||
bot.moveVehicle(1.0, 0.0)
|
||||
break
|
||||
case 'move vehicle right':
|
||||
bot.moveVehicle(-1.0, 0.0)
|
||||
break
|
||||
case 'tp':
|
||||
bot.entity.position.y += 10
|
||||
break
|
||||
case 'pos':
|
||||
bot.chat(bot.entity.position.toString())
|
||||
break
|
||||
case 'yp':
|
||||
bot.chat(`Yaw ${bot.entity.yaw}, pitch: ${bot.entity.pitch}`)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
bot.once('spawn', () => {
|
||||
// keep your eyes on the target, so creepy!
|
||||
setInterval(watchTarget, 50)
|
||||
|
||||
function watchTarget() {
|
||||
if (!target)
|
||||
return
|
||||
bot.lookAt(target.position.offset(0, target.height, 0))
|
||||
}
|
||||
})
|
||||
|
||||
bot.on('mount', () => {
|
||||
bot.chat(`mounted ${bot.vehicle.displayName}`)
|
||||
})
|
||||
|
||||
bot.on('dismount', (vehicle) => {
|
||||
bot.chat(`dismounted ${vehicle.displayName}`)
|
||||
})
|
||||
|
||||
/**
|
||||
* Chest
|
||||
*/
|
||||
bot.on('experience', () => {
|
||||
bot.chat(`I am level ${bot.experience.level}`)
|
||||
})
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username)
|
||||
return
|
||||
switch (true) {
|
||||
case /^list$/.test(message):
|
||||
sayItems()
|
||||
break
|
||||
case /^chest$/.test(message):
|
||||
watchChest(false, ['chest', 'ender_chest', 'trapped_chest'])
|
||||
break
|
||||
case /^furnace$/.test(message):
|
||||
watchFurnace()
|
||||
break
|
||||
case /^dispenser$/.test(message):
|
||||
watchChest(false, ['dispenser'])
|
||||
break
|
||||
case /^enchant$/.test(message):
|
||||
watchEnchantmentTable()
|
||||
break
|
||||
case /^chestminecart$/.test(message):
|
||||
watchChest(true)
|
||||
break
|
||||
case /^invsee \w+( \d)?$/.test(message): {
|
||||
// invsee Herobrine [or]
|
||||
// invsee Herobrine 1
|
||||
const command = message.split(' ')
|
||||
useInvsee(command[0], command[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function sayItems(items = bot.inventory.items()) {
|
||||
const output = items.map(itemToString).join(', ')
|
||||
if (output) {
|
||||
bot.chat(output)
|
||||
}
|
||||
else {
|
||||
bot.chat('empty')
|
||||
}
|
||||
}
|
||||
|
||||
async function watchChest(minecart, blocks = []) {
|
||||
let chestToOpen
|
||||
if (minecart) {
|
||||
chestToOpen = Object.keys(bot.entities)
|
||||
.map(id => bot.entities[id])
|
||||
.find(e => e.entityType === bot.registry.entitiesByName.chest_minecart
|
||||
&& e.objectData.intField === 1
|
||||
&& bot.entity.position.distanceTo(e.position) < 3)
|
||||
if (!chestToOpen) {
|
||||
bot.chat('no chest minecart found')
|
||||
return
|
||||
}
|
||||
}
|
||||
else {
|
||||
chestToOpen = bot.findBlock({
|
||||
matching: blocks.map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!chestToOpen) {
|
||||
bot.chat('no chest found')
|
||||
return
|
||||
}
|
||||
}
|
||||
const chest = await bot.openContainer(chestToOpen)
|
||||
sayItems(chest.containerItems())
|
||||
chest.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`chest update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
chest.on('close', () => {
|
||||
bot.chat('chest closed')
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username, message) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeChest()
|
||||
break
|
||||
case /^withdraw \d+ \w+$/.test(message):
|
||||
// withdraw amount name
|
||||
// ex: withdraw 16 stick
|
||||
withdrawItem(command[2], command[1])
|
||||
break
|
||||
case /^deposit \d+ \w+$/.test(message):
|
||||
// deposit amount name
|
||||
// ex: deposit 16 stick
|
||||
depositItem(command[2], command[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function closeChest() {
|
||||
chest.close()
|
||||
bot.removeListener('chat', onChat)
|
||||
}
|
||||
|
||||
async function withdrawItem(name, amount) {
|
||||
const item = itemByName(chest.containerItems(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await chest.withdraw(item.type, null, amount)
|
||||
bot.chat(`withdrew ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to withdraw ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function depositItem(name, amount) {
|
||||
const item = itemByName(chest.items(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await chest.deposit(item.type, null, amount)
|
||||
bot.chat(`deposited ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to deposit ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function watchFurnace() {
|
||||
const furnaceBlock = bot.findBlock({
|
||||
matching: ['furnace', 'lit_furnace'].filter(name => bot.registry.blocksByName[name] !== undefined).map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!furnaceBlock) {
|
||||
bot.chat('no furnace found')
|
||||
return
|
||||
}
|
||||
const furnace = await bot.openFurnace(furnaceBlock)
|
||||
let output = ''
|
||||
output += `input: ${itemToString(furnace.inputItem())}, `
|
||||
output += `fuel: ${itemToString(furnace.fuelItem())}, `
|
||||
output += `output: ${itemToString(furnace.outputItem())}`
|
||||
bot.chat(output)
|
||||
|
||||
furnace.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`furnace update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
furnace.on('close', () => {
|
||||
bot.chat('furnace closed')
|
||||
})
|
||||
furnace.on('update', () => {
|
||||
console.log(`fuel: ${Math.round(furnace.fuel * 100)}% progress: ${Math.round(furnace.progress * 100)}%`)
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username, message) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeFurnace()
|
||||
break
|
||||
case /^(input|fuel) \d+ \w+$/.test(message):
|
||||
// input amount name
|
||||
// ex: input 32 coal
|
||||
putInFurnace(command[0], command[2], command[1])
|
||||
break
|
||||
case /^take (input|fuel|output)$/.test(message):
|
||||
// take what
|
||||
// ex: take output
|
||||
takeFromFurnace(command[0])
|
||||
break
|
||||
}
|
||||
|
||||
function closeFurnace() {
|
||||
furnace.close()
|
||||
bot.removeListener('chat', onChat)
|
||||
}
|
||||
|
||||
async function putInFurnace(where, name, amount) {
|
||||
const item = itemByName(furnace.items(), name)
|
||||
if (item) {
|
||||
const fn = {
|
||||
input: furnace.putInput,
|
||||
fuel: furnace.putFuel,
|
||||
}[where]
|
||||
try {
|
||||
await fn.call(furnace, item.type, null, amount)
|
||||
bot.chat(`put ${amount} ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`unable to put ${amount} ${item.name}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function takeFromFurnace(what) {
|
||||
const fn = {
|
||||
input: furnace.takeInput,
|
||||
fuel: furnace.takeFuel,
|
||||
output: furnace.takeOutput,
|
||||
}[what]
|
||||
try {
|
||||
const item = await fn.call(furnace)
|
||||
bot.chat(`took ${item.name}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('unable to take')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function watchEnchantmentTable() {
|
||||
const enchantTableBlock = bot.findBlock({
|
||||
matching: ['enchanting_table'].map(name => bot.registry.blocksByName[name].id),
|
||||
maxDistance: 6,
|
||||
})
|
||||
if (!enchantTableBlock) {
|
||||
bot.chat('no enchantment table found')
|
||||
return
|
||||
}
|
||||
const table = await bot.openEnchantmentTable(enchantTableBlock)
|
||||
bot.chat(itemToString(table.targetItem()))
|
||||
|
||||
table.on('updateSlot', (slot, oldItem, newItem) => {
|
||||
bot.chat(`enchantment table update: ${itemToString(oldItem)} -> ${itemToString(newItem)} (slot: ${slot})`)
|
||||
})
|
||||
table.on('close', () => {
|
||||
bot.chat('enchantment table closed')
|
||||
})
|
||||
table.on('ready', () => {
|
||||
bot.chat(`ready to enchant. choices are ${table.enchantments.map(o => o.level).join(', ')}`)
|
||||
})
|
||||
|
||||
bot.on('chat', onChat)
|
||||
|
||||
function onChat(username, message) {
|
||||
if (username === bot.username)
|
||||
return
|
||||
const command = message.split(' ')
|
||||
switch (true) {
|
||||
case /^close$/.test(message):
|
||||
closeEnchantmentTable()
|
||||
break
|
||||
case /^put \w+$/.test(message):
|
||||
// put name
|
||||
// ex: put diamondsword
|
||||
putItem(command[1])
|
||||
break
|
||||
case /^add lapis$/.test(message):
|
||||
addLapis()
|
||||
break
|
||||
case /^enchant \d+$/.test(message):
|
||||
// enchant choice
|
||||
// ex: enchant 2
|
||||
enchantItem(command[1])
|
||||
break
|
||||
case /^take$/.test(message):
|
||||
takeEnchantedItem()
|
||||
break
|
||||
}
|
||||
|
||||
function closeEnchantmentTable() {
|
||||
table.close()
|
||||
}
|
||||
|
||||
async function putItem(name) {
|
||||
const item = itemByName(table.window.items(), name)
|
||||
if (item) {
|
||||
try {
|
||||
await table.putTargetItem(item)
|
||||
bot.chat(`I put ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`error putting ${itemToString(item)}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat(`unknown item ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function addLapis() {
|
||||
const item = itemByType(table.window.items(), ['dye', 'purple_dye', 'lapis_lazuli'].filter(name => bot.registry.itemByName[name] !== undefined)
|
||||
.map(name => bot.registry.itemByName[name].id))
|
||||
if (item) {
|
||||
try {
|
||||
await table.putLapis(item)
|
||||
bot.chat(`I put ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat(`error putting ${itemToString(item)}`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
bot.chat('I don\'t have any lapis')
|
||||
}
|
||||
}
|
||||
|
||||
async function enchantItem(choice) {
|
||||
choice = Number.parseInt(choice, 10)
|
||||
try {
|
||||
const item = await table.enchant(choice)
|
||||
bot.chat(`enchanted ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('error enchanting')
|
||||
}
|
||||
}
|
||||
|
||||
async function takeEnchantedItem() {
|
||||
try {
|
||||
const item = await table.takeTargetItem()
|
||||
bot.chat(`got ${itemToString(item)}`)
|
||||
}
|
||||
catch (err) {
|
||||
bot.chat('error getting item')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useInvsee(username, showEquipment) {
|
||||
bot.once('windowOpen', (window) => {
|
||||
const count = window.containerItems().length
|
||||
const what = showEquipment ? 'equipment' : 'inventory items'
|
||||
if (count) {
|
||||
bot.chat(`${username}'s ${what}:`)
|
||||
sayItems(window.containerItems())
|
||||
}
|
||||
else {
|
||||
bot.chat(`${username} has no ${what}`)
|
||||
}
|
||||
})
|
||||
if (showEquipment) {
|
||||
// any extra parameter triggers the easter egg
|
||||
// and shows the other player's equipment
|
||||
bot.chat(`/invsee ${username} 1`)
|
||||
}
|
||||
else {
|
||||
bot.chat(`/invsee ${username}`)
|
||||
}
|
||||
}
|
||||
|
||||
function itemToString(item) {
|
||||
if (item) {
|
||||
return `${item.name} x ${item.count}`
|
||||
}
|
||||
else {
|
||||
return '(nothing)'
|
||||
}
|
||||
}
|
||||
|
||||
function itemByType(items, type) {
|
||||
let item
|
||||
let i
|
||||
for (i = 0; i < items.length; ++i) {
|
||||
item = items[i]
|
||||
if (item && item.type === type)
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function itemByName(items, name) {
|
||||
let item
|
||||
let i
|
||||
for (i = 0; i < items.length; ++i) {
|
||||
item = items[i]
|
||||
if (item && item.name === name)
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
main().catch((err: Error) => {
|
||||
logger.errorWithError('Fatal error', err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user