From 18e460ae50f536abeb3441a06f321a88bcdcee03 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Tue, 7 Jan 2025 00:16:51 +0800 Subject: [PATCH] wip: world --- services/minecraft/src/agents/agent.ts | 0 services/minecraft/src/composables/world.ts | 305 ++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 services/minecraft/src/agents/agent.ts create mode 100644 services/minecraft/src/composables/world.ts diff --git a/services/minecraft/src/agents/agent.ts b/services/minecraft/src/agents/agent.ts new file mode 100644 index 000000000..e69de29bb diff --git a/services/minecraft/src/composables/world.ts b/services/minecraft/src/composables/world.ts new file mode 100644 index 000000000..0e873b295 --- /dev/null +++ b/services/minecraft/src/composables/world.ts @@ -0,0 +1,305 @@ +import pf from 'mineflayer-pathfinder' + +import * as mc from '../utils/mcdata' + +export function getNearestFreeSpace(bot, size = 1, distance = 8) { + /** + * Get the nearest empty space with solid blocks beneath it of the given size. + * @param {Bot} bot - The bot to get the nearest free space for. + * @param {number} size - The (size x size) of the space to find, default 1. + * @param {number} distance - The maximum distance to search, default 8. + * @returns {Vec3} - The south west corner position of the nearest free space. + * @example + * let position = world.getNearestFreeSpace(bot, 1, 8); + */ + const empty_pos = bot.findBlocks({ + matching: (block) => { + return block && block.name == 'air' + }, + maxDistance: distance, + count: 1000, + }) + for (let i = 0; i < empty_pos.length; i++) { + let empty = true + for (let x = 0; x < size; x++) { + for (let z = 0; z < size; z++) { + const top = bot.blockAt(empty_pos[i].offset(x, 0, z)) + const bottom = bot.blockAt(empty_pos[i].offset(x, -1, z)) + if (!top || !top.name == 'air' || !bottom || bottom.drops.length == 0 || !bottom.diggable) { + empty = false + break + } + } + if (!empty) + break + } + if (empty) { + return empty_pos[i] + } + } +} + +export function getNearestBlocks(bot, block_types = null, distance = 16, count = 10000) { + /** + * Get a list of the nearest blocks of the given types. + * @param {Bot} bot - The bot to get the nearest block for. + * @param {string[]} block_types - The names of the blocks to search for. + * @param {number} distance - The maximum distance to search, default 16. + * @param {number} count - The maximum number of blocks to find, default 10000. + * @returns {Block[]} - The nearest blocks of the given type. + * @example + * let woodBlocks = world.getNearestBlocks(bot, ['oak_log', 'birch_log'], 16, 1); + */ + // if blocktypes is not a list, make it a list + let block_ids = [] + if (block_types === null) { + block_ids = mc.getAllBlockIds(['air']) + } + else { + if (!Array.isArray(block_types)) + block_types = [block_types] + for (const block_type of block_types) { + block_ids.push(mc.getBlockId(block_type)) + } + } + + const positions = bot.findBlocks({ matching: block_ids, maxDistance: distance, count }) + const blocks = [] + for (let i = 0; i < positions.length; i++) { + const block = bot.blockAt(positions[i]) + const distance = positions[i].distanceTo(bot.entity.position) + blocks.push({ block, distance }) + } + blocks.sort((a, b) => a.distance - b.distance) + + const res = [] + for (let i = 0; i < blocks.length; i++) { + res.push(blocks[i].block) + } + return res +} + +export function getNearestBlock(bot, block_type, distance = 16) { + /** + * Get the nearest block of the given type. + * @param {Bot} bot - The bot to get the nearest block for. + * @param {string} block_type - The name of the block to search for. + * @param {number} distance - The maximum distance to search, default 16. + * @returns {Block} - The nearest block of the given type. + * @example + * let coalBlock = world.getNearestBlock(bot, 'coal_ore', 16); + */ + const blocks = getNearestBlocks(bot, block_type, distance, 1) + if (blocks.length > 0) { + return blocks[0] + } + return null +} + +export function getNearbyEntities(bot, maxDistance = 16) { + const entities = [] + for (const entity of Object.values(bot.entities)) { + const distance = entity.position.distanceTo(bot.entity.position) + if (distance > maxDistance) + continue + entities.push({ entity, distance }) + } + entities.sort((a, b) => a.distance - b.distance) + const res = [] + for (let i = 0; i < entities.length; i++) { + res.push(entities[i].entity) + } + return res +} + +export function getNearestEntityWhere(bot, predicate, maxDistance = 16) { + return bot.nearestEntity(entity => predicate(entity) && bot.entity.position.distanceTo(entity.position) < maxDistance) +} + +export function getNearbyPlayers(bot, maxDistance) { + if (maxDistance == null) + maxDistance = 16 + const players = [] + for (const entity of Object.values(bot.entities)) { + const distance = entity.position.distanceTo(bot.entity.position) + if (distance > maxDistance) + continue + if (entity.type == 'player' && entity.username != bot.username) { + players.push({ entity, distance }) + } + } + players.sort((a, b) => a.distance - b.distance) + const res = [] + for (let i = 0; i < players.length; i++) { + res.push(players[i].entity) + } + return res +} + +export function getInventoryStacks(bot) { + const inventory = [] + for (const item of bot.inventory.items()) { + if (item != null) { + inventory.push(item) + } + } + return inventory +} + +export function getInventoryCounts(bot) { + /** + * Get an object representing the bot's inventory. + * @param {Bot} bot - The bot to get the inventory for. + * @returns {object} - An object with item names as keys and counts as values. + * @example + * let inventory = world.getInventoryCounts(bot); + * let oakLogCount = inventory['oak_log']; + * let hasWoodenPickaxe = inventory['wooden_pickaxe'] > 0; + */ + const inventory = {} + for (const item of bot.inventory.items()) { + if (item != null) { + if (inventory[item.name] == null) { + inventory[item.name] = 0 + } + inventory[item.name] += item.count + } + } + return inventory +} + +export function getCraftableItems(bot) { + /** + * Get a list of all items that can be crafted with the bot's current inventory. + * @param {Bot} bot - The bot to get the craftable items for. + * @returns {string[]} - A list of all items that can be crafted. + * @example + * let craftableItems = world.getCraftableItems(bot); + */ + let table = getNearestBlock(bot, 'crafting_table') + if (!table) { + for (const item of bot.inventory.items()) { + if (item != null && item.name === 'crafting_table') { + table = item + break + } + } + } + const res = [] + for (const item of mc.getAllItems()) { + const recipes = bot.recipesFor(item.id, null, 1, table) + if (recipes.length > 0) + res.push(item.name) + } + return res +} + +export function getPosition(bot) { + /** + * Get your position in the world (Note that y is vertical). + * @param {Bot} bot - The bot to get the position for. + * @returns {Vec3} - An object with x, y, and x attributes representing the position of the bot. + * @example + * let position = world.getPosition(bot); + * let x = position.x; + */ + return bot.entity.position +} + +export function getNearbyEntityTypes(bot) { + /** + * Get a list of all nearby mob types. + * @param {Bot} bot - The bot to get nearby mobs for. + * @returns {string[]} - A list of all nearby mobs. + * @example + * let mobs = world.getNearbyEntityTypes(bot); + */ + const mobs = getNearbyEntities(bot, 16) + const found = [] + for (let i = 0; i < mobs.length; i++) { + if (!found.includes(mobs[i].name)) { + found.push(mobs[i].name) + } + } + return found +} + +export function getNearbyPlayerNames(bot) { + /** + * Get a list of all nearby player names. + * @param {Bot} bot - The bot to get nearby players for. + * @returns {string[]} - A list of all nearby players. + * @example + * let players = world.getNearbyPlayerNames(bot); + */ + const players = getNearbyPlayers(bot, 64) + const found = [] + for (let i = 0; i < players.length; i++) { + if (!found.includes(players[i].username) && players[i].username != bot.username) { + found.push(players[i].username) + } + } + return found +} + +export function getNearbyBlockTypes(bot, distance = 16) { + /** + * Get a list of all nearby block names. + * @param {Bot} bot - The bot to get nearby blocks for. + * @param {number} distance - The maximum distance to search, default 16. + * @returns {string[]} - A list of all nearby blocks. + * @example + * let blocks = world.getNearbyBlockTypes(bot); + */ + const blocks = getNearestBlocks(bot, null, distance) + const found = [] + for (let i = 0; i < blocks.length; i++) { + if (!found.includes(blocks[i].name)) { + found.push(blocks[i].name) + } + } + return found +} + +export async function isClearPath(bot, target) { + /** + * Check if there is a path to the target that requires no digging or placing blocks. + * @param {Bot} bot - The bot to get the path for. + * @param {Entity} target - The target to path to. + * @returns {boolean} - True if there is a clear path, false otherwise. + */ + const movements = new pf.Movements(bot) + movements.canDig = false + movements.canPlaceOn = false + const goal = new pf.goals.GoalNear(target.position.x, target.position.y, target.position.z, 1) + const path = await bot.pathfinder.getPathTo(movements, goal, 100) + return path.status === 'success' +} + +export function shouldPlaceTorch(bot) { + if (!bot.modes.isOn('torch_placing') || bot.interrupt_code) + return false + const pos = getPosition(bot) + // TODO: check light level instead of nearby torches, block.light is broken + let nearest_torch = getNearestBlock(bot, 'torch', 6) + if (!nearest_torch) + nearest_torch = getNearestBlock(bot, 'wall_torch', 6) + if (!nearest_torch) { + const block = bot.blockAt(pos) + const has_torch = bot.inventory.items().find(item => item.name === 'torch') + return has_torch && block?.name === 'air' + } + return false +} + +export function getBiomeName(bot) { + /** + * Get the name of the biome the bot is in. + * @param {Bot} bot - The bot to get the biome for. + * @returns {string} - The name of the biome. + * @example + * let biome = world.getBiomeName(bot); + */ + const biomeId = bot.world.getBiome(bot.entity.position) + return mc.getAllBiomes()[biomeId].name +}