commit 6f0f17521aec2da459cf7340be784425ce892bb9 Author: RainbowBird Date: Sat Jan 4 22:38:46 2025 +0800 init diff --git a/services/minecraft/src/main.ts b/services/minecraft/src/main.ts new file mode 100644 index 000000000..3e5f6691c --- /dev/null +++ b/services/minecraft/src/main.ts @@ -0,0 +1,550 @@ +import mineflayer from 'mineflayer' +import { goals, Movements, pathfinder } from 'mineflayer-pathfinder' + +async function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +async function main() { + await sleep(5000) + + 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'。 + }) + + 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)