mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-31 11:23:08 +02:00
Server/Minecraft data now agent-level
Also refactored mcdata as a class.
This commit is contained in:
parent
7a3a5b3f22
commit
418a2470de
15 changed files with 396 additions and 359 deletions
48
main.js
48
main.js
|
@ -2,8 +2,7 @@ import { AgentProcess } from './src/process/agent-process.js';
|
|||
import settings from './settings.js';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import { serverInfo, findServers } from './src/utils/mcserver.js';
|
||||
import mc from 'minecraft-protocol';
|
||||
import { getServer } from './src/utils/mcserver.js';
|
||||
|
||||
function parseArguments() {
|
||||
return yargs(hideBin(process.argv))
|
||||
|
@ -20,49 +19,6 @@ function getProfiles(args) {
|
|||
return args.profiles || settings.profiles;
|
||||
}
|
||||
|
||||
async function getServer() {
|
||||
let server = null;
|
||||
let serverString = "";
|
||||
let serverVersion = "";
|
||||
|
||||
// Search for server
|
||||
if (settings.port == -1)
|
||||
{
|
||||
console.log("No port provided. Searching for LAN server...");
|
||||
|
||||
await findServers(settings.host, true).then((servers) => {
|
||||
if (servers.length > 0)
|
||||
server = servers[0];
|
||||
});
|
||||
|
||||
if (server == null)
|
||||
throw new Error(`No server found on LAN.`);
|
||||
}
|
||||
else
|
||||
server = await serverInfo(settings.host, settings.port);
|
||||
|
||||
// Server not found
|
||||
if (server == null)
|
||||
throw new Error(`Server not found. (Host: ${settings.host}, Port: ${settings.port}) Check the host and port in settings.js.`);
|
||||
|
||||
serverString = `(Host: ${server.host}, Port: ${server.port}, Version: ${server.version})`;
|
||||
|
||||
if (settings.minecraft_version === "auto")
|
||||
serverVersion = server.version;
|
||||
else
|
||||
serverVersion = settings.minecraft_version;
|
||||
|
||||
// Server version unsupported / mismatch
|
||||
if (mc.supportedVersions.indexOf(serverVersion) === -1)
|
||||
throw new Error(`A server was found ${serverString}, but version is unsupported. Supported versions are: ${mc.supportedVersions.join(", ")}.`);
|
||||
else if (settings.minecraft_version !== "auto" && server.version !== settings.minecraft_version)
|
||||
throw new Error(`A server was found ${serverString}, but version is incorrect. Expected ${settings.minecraft_version}, but found ${server.version}.`);
|
||||
else
|
||||
console.log(`Server found. ${serverString}`);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArguments();
|
||||
const profiles = getProfiles(args);
|
||||
|
@ -74,7 +30,7 @@ async function main() {
|
|||
|
||||
for (let i=0; i<profiles.length; i++) {
|
||||
const agent = new AgentProcess();
|
||||
agent.start(profiles[i], load_memory, init_message, i);
|
||||
agent.start(profiles[i], load_memory, init_message, server.host, server.port, server.version, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { History } from './history.js';
|
|||
import { Coder } from './coder.js';
|
||||
import { Prompter } from './prompter.js';
|
||||
import { initModes } from './modes.js';
|
||||
import { initBot } from '../utils/mcdata.js';
|
||||
import { mc } from '../utils/mcdata.js';
|
||||
import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js';
|
||||
import { ActionManager } from './action_manager.js';
|
||||
import { NPCContoller } from './npc/controller.js';
|
||||
|
@ -13,7 +13,7 @@ import { addViewer } from './viewer.js';
|
|||
import settings from '../../settings.js';
|
||||
|
||||
export class Agent {
|
||||
async start(profile_fp, load_mem=false, init_message=null, count_id=0) {
|
||||
async start(profile_fp, load_mem=false, init_message=null, server_host=null, server_port=0, version=null, count_id=0) {
|
||||
try {
|
||||
// Add validation for profile_fp
|
||||
if (!profile_fp) {
|
||||
|
@ -50,9 +50,16 @@ export class Agent {
|
|||
throw new Error(`Failed to initialize examples: ${error.message || error}`);
|
||||
}
|
||||
|
||||
console.log("Initializing Minecraft data...");
|
||||
try {
|
||||
mc.init(server_host, server_port, version);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize Minecraft data: ${error.message || error}`);
|
||||
}
|
||||
|
||||
console.log('Logging into minecraft...');
|
||||
try {
|
||||
this.bot = initBot(this.name);
|
||||
this.bot = mc.initBot(this.name);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize Minecraft bot: ${error.message || error}`);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getBlockId, getItemId } from "../../utils/mcdata.js";
|
||||
import { mc } from "../../utils/mcdata.js";
|
||||
import { actionsList } from './actions.js';
|
||||
import { queryList } from './queries.js';
|
||||
|
||||
|
@ -154,9 +154,9 @@ function parseCommandMessage(message) {
|
|||
suppressNoDomainWarning = true; //Don't spam console. Only give the warning once.
|
||||
}
|
||||
} else if(param.type === 'BlockName') { //Check that there is a block with this name
|
||||
if(getBlockId(arg) == null) return `Invalid block type: ${arg}.`
|
||||
if(mc.getBlockId(arg) == null) return `Invalid block type: ${arg}.`
|
||||
} else if(param.type === 'ItemName') { //Check that there is an item with this name
|
||||
if(getItemId(arg) == null) return `Invalid item type: ${arg}.`
|
||||
if(mc.getItemId(arg) == null) return `Invalid item type: ${arg}.`
|
||||
}
|
||||
args[i] = arg;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
|
||||
|
||||
const pad = (str) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as mc from "../../utils/mcdata.js";
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
import * as world from "./world.js";
|
||||
import pf from 'mineflayer-pathfinder';
|
||||
import Vec3 from 'vec3';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pf from 'mineflayer-pathfinder';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
|
||||
|
||||
export function getNearestFreeSpace(bot, size=1, distance=8) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as skills from './library/skills.js';
|
||||
import * as world from './library/world.js';
|
||||
import * as mc from '../utils/mcdata.js';
|
||||
import { mc } from '../utils/mcdata.js';
|
||||
import settings from '../../settings.js'
|
||||
import { handleTranslation } from '../utils/translator.js';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Vec3 } from 'vec3';
|
||||
import * as skills from '../library/skills.js';
|
||||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
import { blockSatisfied, getTypeOfGeneric, rotateXZ } from './utils.js';
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { BuildGoal } from './build_goal.js';
|
|||
import { itemSatisfied, rotateXZ } from './utils.js';
|
||||
import * as skills from '../library/skills.js';
|
||||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
|
||||
|
||||
export class NPCContoller {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as skills from '../library/skills.js';
|
||||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
import { itemSatisfied } from './utils.js';
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as world from '../library/world.js';
|
||||
import * as mc from '../../utils/mcdata.js';
|
||||
|
||||
import { mc } from '../../utils/mcdata.js';
|
||||
|
||||
export function getTypeOfGeneric(bot, block_name) {
|
||||
// Get type of wooden block
|
||||
|
|
|
@ -3,7 +3,7 @@ import { spawn } from 'child_process';
|
|||
export class AgentProcess {
|
||||
static runningCount = 0;
|
||||
|
||||
start(profile, load_memory=false, init_message=null, count_id=0) {
|
||||
start(profile, load_memory=false, init_message=null, server_host=null, server_port=0, server_version=null, count_id=0) {
|
||||
let args = ['src/process/init-agent.js', this.name];
|
||||
args.push('-p', profile);
|
||||
args.push('-c', count_id);
|
||||
|
@ -12,6 +12,11 @@ export class AgentProcess {
|
|||
if (init_message)
|
||||
args.push('-m', init_message);
|
||||
|
||||
// Pass server/version info to agent
|
||||
args.push('--server_host', server_host);
|
||||
args.push('--server_port', server_port);
|
||||
args.push('--server_version', server_version);
|
||||
|
||||
const agentProcess = spawn('node', args, {
|
||||
stdio: 'inherit',
|
||||
stderr: 'inherit',
|
||||
|
@ -34,7 +39,7 @@ export class AgentProcess {
|
|||
return;
|
||||
}
|
||||
console.log('Restarting agent...');
|
||||
this.start(profile, true, 'Agent process restarted.', count_id);
|
||||
this.start(profile, true, 'Agent process restarted.', server_host, server_port, server_version, count_id);
|
||||
last_restart = Date.now();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -38,6 +38,18 @@ const argv = yargs(args)
|
|||
type: 'number',
|
||||
default: 0,
|
||||
description: 'identifying count for multi-agent scenarios',
|
||||
})
|
||||
.option('server_host', {
|
||||
type: 'string',
|
||||
description: 'minecraft server host',
|
||||
})
|
||||
.option('server_port', {
|
||||
type: 'number',
|
||||
description: 'minecraft server port',
|
||||
})
|
||||
.option('server_version', {
|
||||
type: 'string',
|
||||
description: 'minecraft version'
|
||||
}).argv;
|
||||
|
||||
// Wrap agent start in async IIFE with proper error handling
|
||||
|
@ -45,7 +57,7 @@ const argv = yargs(args)
|
|||
try {
|
||||
console.log('Starting agent with profile:', argv.profile);
|
||||
const agent = new Agent();
|
||||
await agent.start(argv.profile, argv.load_memory, argv.init_message, argv.count_id);
|
||||
await agent.start(argv.profile, argv.load_memory, argv.init_message, argv.server_host, argv.server_port, argv.server_version, argv.count_id);
|
||||
} catch (error) {
|
||||
console.error('Failed to start agent process:', {
|
||||
message: error.message || 'No error message',
|
||||
|
|
|
@ -9,17 +9,300 @@ import { plugin as autoEat } from 'mineflayer-auto-eat';
|
|||
import plugin from 'mineflayer-armor-manager';
|
||||
const armorManager = plugin;
|
||||
|
||||
const mc_version = settings.minecraft_version;
|
||||
const mcdata = minecraftData(mc_version);
|
||||
const Item = prismarine_items(mc_version);
|
||||
class MinecraftData {
|
||||
constructor() {
|
||||
this.mcdata = null;
|
||||
this.Item = null;
|
||||
|
||||
/**
|
||||
this.server_version = null;
|
||||
this.server_port = null;
|
||||
this.server_host = null;
|
||||
}
|
||||
|
||||
init(host, port, version) {
|
||||
this.server_version = version;
|
||||
this.server_port = port;
|
||||
this.server_host = host;
|
||||
|
||||
this.mcdata = minecraftData(this.server_version);
|
||||
this.Item = prismarine_items(this.server_version);
|
||||
}
|
||||
|
||||
initBot(username) {
|
||||
let bot = createBot({
|
||||
username: username,
|
||||
host: this.server_host,
|
||||
port: this.server_port,
|
||||
auth: settings.auth,
|
||||
version: this.server_version,
|
||||
});
|
||||
bot.loadPlugin(pathfinder);
|
||||
bot.loadPlugin(pvp);
|
||||
bot.loadPlugin(collectblock);
|
||||
bot.loadPlugin(autoEat);
|
||||
bot.loadPlugin(armorManager); // auto equip armor
|
||||
return bot;
|
||||
}
|
||||
|
||||
isHuntable(mob) {
|
||||
if (!mob || !mob.name) return false;
|
||||
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep'];
|
||||
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
|
||||
}
|
||||
|
||||
isHostile(mob) {
|
||||
if (!mob || !mob.name) return false;
|
||||
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
|
||||
}
|
||||
|
||||
getItemId(itemName) {
|
||||
let item = this.mcdata.itemsByName[itemName];
|
||||
if (item) {
|
||||
return item.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getItemName(itemId) {
|
||||
let item = this.mcdata.items[itemId]
|
||||
if (item) {
|
||||
return item.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getBlockId(blockName) {
|
||||
let block = this.mcdata.blocksByName[blockName];
|
||||
if (block) {
|
||||
return block.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getBlockName(blockId) {
|
||||
let block = this.mcdata.blocks[blockId]
|
||||
if (block) {
|
||||
return block.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getAllItems(ignore) {
|
||||
if (!ignore) {
|
||||
ignore = [];
|
||||
}
|
||||
let items = []
|
||||
for (const itemId in this.mcdata.items) {
|
||||
const item = this.mcdata.items[itemId];
|
||||
if (!ignore.includes(item.name)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
getAllItemIds(ignore) {
|
||||
const items = this.getAllItems(ignore);
|
||||
let itemIds = [];
|
||||
for (const item of items) {
|
||||
itemIds.push(item.id);
|
||||
}
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
getAllBlocks(ignore) {
|
||||
if (!ignore) {
|
||||
ignore = [];
|
||||
}
|
||||
let blocks = []
|
||||
for (const blockId in this.mcdata.blocks) {
|
||||
const block = this.mcdata.blocks[blockId];
|
||||
if (!ignore.includes(block.name)) {
|
||||
blocks.push(block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
getAllBlockIds(ignore) {
|
||||
const blocks = this.getAllBlocks(ignore);
|
||||
let blockIds = [];
|
||||
for (const block of blocks) {
|
||||
blockIds.push(block.id);
|
||||
}
|
||||
return blockIds;
|
||||
}
|
||||
|
||||
getAllBiomes() {
|
||||
return this.mcdata.biomes;
|
||||
}
|
||||
|
||||
getItemCraftingRecipes(itemName) {
|
||||
let itemId = this.getItemId(itemName);
|
||||
if (!this.mcdata.recipes[itemId]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let recipes = [];
|
||||
for (let r of this.mcdata.recipes[itemId]) {
|
||||
let recipe = {};
|
||||
let ingredients = [];
|
||||
if (r.ingredients) {
|
||||
ingredients = r.ingredients;
|
||||
} else if (r.inShape) {
|
||||
ingredients = r.inShape.flat();
|
||||
}
|
||||
for (let ingredient of ingredients) {
|
||||
let ingredientName = this.getItemName(ingredient);
|
||||
if (ingredientName === null) continue;
|
||||
if (!recipe[ingredientName])
|
||||
recipe[ingredientName] = 0;
|
||||
recipe[ingredientName]++;
|
||||
}
|
||||
recipes.push(recipe);
|
||||
}
|
||||
|
||||
return recipes;
|
||||
}
|
||||
|
||||
isSmeltable(itemName) {
|
||||
const misc_smeltables = ['beef', 'chicken', 'cod', 'mutton', 'porkchop', 'rabbit', 'salmon', 'tropical_fish', 'potato', 'kelp', 'sand', 'cobblestone', 'clay_ball'];
|
||||
return itemName.includes('raw') || itemName.includes('log') || misc_smeltables.includes(itemName);
|
||||
}
|
||||
|
||||
getSmeltingFuel(bot) {
|
||||
let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal')
|
||||
if (fuel)
|
||||
return fuel;
|
||||
fuel = bot.inventory.items().find(i => i.name.includes('log') || i.name.includes('planks'))
|
||||
if (fuel)
|
||||
return fuel;
|
||||
return bot.inventory.items().find(i => i.name === 'coal_block' || i.name === 'lava_bucket');
|
||||
}
|
||||
|
||||
getFuelSmeltOutput(fuelName) {
|
||||
if (fuelName === 'coal' || fuelName === 'charcoal')
|
||||
return 8;
|
||||
if (fuelName.includes('log') || fuelName.includes('planks'))
|
||||
return 1.5
|
||||
if (fuelName === 'coal_block')
|
||||
return 80;
|
||||
if (fuelName === 'lava_bucket')
|
||||
return 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getItemSmeltingIngredient(itemName) {
|
||||
return {
|
||||
baked_potato: 'potato',
|
||||
steak: 'raw_beef',
|
||||
cooked_chicken: 'raw_chicken',
|
||||
cooked_cod: 'raw_cod',
|
||||
cooked_mutton: 'raw_mutton',
|
||||
cooked_porkchop: 'raw_porkchop',
|
||||
cooked_rabbit: 'raw_rabbit',
|
||||
cooked_salmon: 'raw_salmon',
|
||||
dried_kelp: 'kelp',
|
||||
iron_ingot: 'raw_iron',
|
||||
gold_ingot: 'raw_gold',
|
||||
copper_ingot: 'raw_copper',
|
||||
glass: 'sand'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
getItemBlockSources(itemName) {
|
||||
let itemId = this.getItemId(itemName);
|
||||
let sources = [];
|
||||
for (let block of this.getAllBlocks()) {
|
||||
if (block.drops.includes(itemId)) {
|
||||
sources.push(block.name);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
getItemAnimalSource(itemName) {
|
||||
return {
|
||||
raw_beef: 'cow',
|
||||
raw_chicken: 'chicken',
|
||||
raw_cod: 'cod',
|
||||
raw_mutton: 'sheep',
|
||||
raw_porkchop: 'pig',
|
||||
raw_rabbit: 'rabbit',
|
||||
raw_salmon: 'salmon',
|
||||
leather: 'cow',
|
||||
wool: 'sheep'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
getBlockTool(blockName) {
|
||||
let block = this.mcdata.blocksByName[blockName];
|
||||
if (!block || !block.harvestTools) {
|
||||
return null;
|
||||
}
|
||||
return this.getItemName(Object.keys(block.harvestTools)[0]); // Double check first tool is always simplest
|
||||
}
|
||||
|
||||
makeItem(name, amount=1) {
|
||||
return new this.Item(this.getItemId(name), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ingredients required to use the recipe once.
|
||||
*
|
||||
* @param {Recipe} recipe
|
||||
* @returns {Object<mc.ItemName, number>} an object describing the number of each ingredient.
|
||||
*/
|
||||
ingredientsFromPrismarineRecipe(recipe) {
|
||||
let requiredIngedients = {};
|
||||
if (recipe.inShape)
|
||||
for (const ingredient of recipe.inShape.flat()) {
|
||||
if(ingredient.id<0) continue; //prismarine-recipe uses id -1 as an empty crafting slot
|
||||
const ingredientName = this.getItemName(ingredient.id);
|
||||
requiredIngedients[ingredientName] ??=0;
|
||||
requiredIngedients[ingredientName] += ingredient.count;
|
||||
}
|
||||
if (recipe.ingredients)
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if(ingredient.id<0) continue;
|
||||
const ingredientName = this.getItemName(ingredient.id);
|
||||
requiredIngedients[ingredientName] ??=0;
|
||||
requiredIngedients[ingredientName] -= ingredient.count;
|
||||
//Yes, the `-=` is intended.
|
||||
//prismarine-recipe uses positive numbers for the shaped ingredients but negative for unshaped.
|
||||
//Why this is the case is beyond my understanding.
|
||||
}
|
||||
return requiredIngedients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of times an action, such as a crafing recipe, can be completed before running out of resources.
|
||||
* @template T - doesn't have to be an item. This could be any resource.
|
||||
* @param {Object.<T, number>} availableItems - The resources available; e.g, `{'cobble_stone': 7, 'stick': 10}`
|
||||
* @param {Object.<T, number>} requiredItems - The resources required to complete the action once; e.g, `{'cobble_stone': 3, 'stick': 2}`
|
||||
* @param {boolean} discrete - Is the action discrete?
|
||||
* @returns {{num: number, limitingResource: (T | null)}} the number of times the action can be completed and the limmiting resource; e.g `{num: 2, limitingResource: 'cobble_stone'}`
|
||||
*/
|
||||
calculateLimitingResource(availableItems, requiredItems, discrete=true) {
|
||||
let limitingResource = null;
|
||||
let num = Infinity;
|
||||
for (const itemType in requiredItems) {
|
||||
if (availableItems[itemType] < requiredItems[itemType] * num) {
|
||||
limitingResource = itemType;
|
||||
num = availableItems[itemType] / requiredItems[itemType];
|
||||
}
|
||||
}
|
||||
if(discrete) num = Math.floor(num);
|
||||
return {num, limitingResource}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {string} ItemName
|
||||
* @typedef {string} BlockName
|
||||
*/
|
||||
*/
|
||||
|
||||
export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak'];
|
||||
export const MATCHING_WOOD_BLOCKS = [
|
||||
WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak'];
|
||||
MATCHING_WOOD_BLOCKS = [
|
||||
'log',
|
||||
'planks',
|
||||
'sign',
|
||||
|
@ -32,8 +315,8 @@ export const MATCHING_WOOD_BLOCKS = [
|
|||
'button',
|
||||
'pressure_plate',
|
||||
'trapdoor'
|
||||
]
|
||||
export const WOOL_COLORS = [
|
||||
]
|
||||
WOOL_COLORS = [
|
||||
'white',
|
||||
'orange',
|
||||
'magenta',
|
||||
|
@ -50,276 +333,7 @@ export const WOOL_COLORS = [
|
|||
'green',
|
||||
'red',
|
||||
'black'
|
||||
]
|
||||
|
||||
|
||||
export function initBot(username) {
|
||||
let bot = createBot({
|
||||
username: username,
|
||||
|
||||
host: settings.host,
|
||||
port: settings.port,
|
||||
auth: settings.auth,
|
||||
|
||||
version: mc_version,
|
||||
});
|
||||
bot.loadPlugin(pathfinder);
|
||||
bot.loadPlugin(pvp);
|
||||
bot.loadPlugin(collectblock);
|
||||
bot.loadPlugin(autoEat);
|
||||
bot.loadPlugin(armorManager); // auto equip armor
|
||||
|
||||
return bot;
|
||||
]
|
||||
}
|
||||
|
||||
export function isHuntable(mob) {
|
||||
if (!mob || !mob.name) return false;
|
||||
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep'];
|
||||
return animals.includes(mob.name.toLowerCase()) && !mob.metadata[16]; // metadata 16 is not baby
|
||||
}
|
||||
|
||||
export function isHostile(mob) {
|
||||
if (!mob || !mob.name) return false;
|
||||
return (mob.type === 'mob' || mob.type === 'hostile') && mob.name !== 'iron_golem' && mob.name !== 'snow_golem';
|
||||
}
|
||||
|
||||
export function getItemId(itemName) {
|
||||
let item = mcdata.itemsByName[itemName];
|
||||
if (item) {
|
||||
return item.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getItemName(itemId) {
|
||||
let item = mcdata.items[itemId]
|
||||
if (item) {
|
||||
return item.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getBlockId(blockName) {
|
||||
let block = mcdata.blocksByName[blockName];
|
||||
if (block) {
|
||||
return block.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getBlockName(blockId) {
|
||||
let block = mcdata.blocks[blockId]
|
||||
if (block) {
|
||||
return block.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getAllItems(ignore) {
|
||||
if (!ignore) {
|
||||
ignore = [];
|
||||
}
|
||||
let items = []
|
||||
for (const itemId in mcdata.items) {
|
||||
const item = mcdata.items[itemId];
|
||||
if (!ignore.includes(item.name)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export function getAllItemIds(ignore) {
|
||||
const items = getAllItems(ignore);
|
||||
let itemIds = [];
|
||||
for (const item of items) {
|
||||
itemIds.push(item.id);
|
||||
}
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
export function getAllBlocks(ignore) {
|
||||
if (!ignore) {
|
||||
ignore = [];
|
||||
}
|
||||
let blocks = []
|
||||
for (const blockId in mcdata.blocks) {
|
||||
const block = mcdata.blocks[blockId];
|
||||
if (!ignore.includes(block.name)) {
|
||||
blocks.push(block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
export function getAllBlockIds(ignore) {
|
||||
const blocks = getAllBlocks(ignore);
|
||||
let blockIds = [];
|
||||
for (const block of blocks) {
|
||||
blockIds.push(block.id);
|
||||
}
|
||||
return blockIds;
|
||||
}
|
||||
|
||||
export function getAllBiomes() {
|
||||
return mcdata.biomes;
|
||||
}
|
||||
|
||||
export function getItemCraftingRecipes(itemName) {
|
||||
let itemId = getItemId(itemName);
|
||||
if (!mcdata.recipes[itemId]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let recipes = [];
|
||||
for (let r of mcdata.recipes[itemId]) {
|
||||
let recipe = {};
|
||||
let ingredients = [];
|
||||
if (r.ingredients) {
|
||||
ingredients = r.ingredients;
|
||||
} else if (r.inShape) {
|
||||
ingredients = r.inShape.flat();
|
||||
}
|
||||
for (let ingredient of ingredients) {
|
||||
let ingredientName = getItemName(ingredient);
|
||||
if (ingredientName === null) continue;
|
||||
if (!recipe[ingredientName])
|
||||
recipe[ingredientName] = 0;
|
||||
recipe[ingredientName]++;
|
||||
}
|
||||
recipes.push(recipe);
|
||||
}
|
||||
|
||||
return recipes;
|
||||
}
|
||||
|
||||
export function isSmeltable(itemName) {
|
||||
const misc_smeltables = ['beef', 'chicken', 'cod', 'mutton', 'porkchop', 'rabbit', 'salmon', 'tropical_fish', 'potato', 'kelp', 'sand', 'cobblestone', 'clay_ball'];
|
||||
return itemName.includes('raw') || itemName.includes('log') || misc_smeltables.includes(itemName);
|
||||
}
|
||||
|
||||
export function getSmeltingFuel(bot) {
|
||||
let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal')
|
||||
if (fuel)
|
||||
return fuel;
|
||||
fuel = bot.inventory.items().find(i => i.name.includes('log') || i.name.includes('planks'))
|
||||
if (fuel)
|
||||
return fuel;
|
||||
return bot.inventory.items().find(i => i.name === 'coal_block' || i.name === 'lava_bucket');
|
||||
}
|
||||
|
||||
export function getFuelSmeltOutput(fuelName) {
|
||||
if (fuelName === 'coal' || fuelName === 'charcoal')
|
||||
return 8;
|
||||
if (fuelName.includes('log') || fuelName.includes('planks'))
|
||||
return 1.5
|
||||
if (fuelName === 'coal_block')
|
||||
return 80;
|
||||
if (fuelName === 'lava_bucket')
|
||||
return 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getItemSmeltingIngredient(itemName) {
|
||||
return {
|
||||
baked_potato: 'potato',
|
||||
steak: 'raw_beef',
|
||||
cooked_chicken: 'raw_chicken',
|
||||
cooked_cod: 'raw_cod',
|
||||
cooked_mutton: 'raw_mutton',
|
||||
cooked_porkchop: 'raw_porkchop',
|
||||
cooked_rabbit: 'raw_rabbit',
|
||||
cooked_salmon: 'raw_salmon',
|
||||
dried_kelp: 'kelp',
|
||||
iron_ingot: 'raw_iron',
|
||||
gold_ingot: 'raw_gold',
|
||||
copper_ingot: 'raw_copper',
|
||||
glass: 'sand'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
export function getItemBlockSources(itemName) {
|
||||
let itemId = getItemId(itemName);
|
||||
let sources = [];
|
||||
for (let block of getAllBlocks()) {
|
||||
if (block.drops.includes(itemId)) {
|
||||
sources.push(block.name);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
export function getItemAnimalSource(itemName) {
|
||||
return {
|
||||
raw_beef: 'cow',
|
||||
raw_chicken: 'chicken',
|
||||
raw_cod: 'cod',
|
||||
raw_mutton: 'sheep',
|
||||
raw_porkchop: 'pig',
|
||||
raw_rabbit: 'rabbit',
|
||||
raw_salmon: 'salmon',
|
||||
leather: 'cow',
|
||||
wool: 'sheep'
|
||||
}[itemName];
|
||||
}
|
||||
|
||||
export function getBlockTool(blockName) {
|
||||
let block = mcdata.blocksByName[blockName];
|
||||
if (!block || !block.harvestTools) {
|
||||
return null;
|
||||
}
|
||||
return getItemName(Object.keys(block.harvestTools)[0]); // Double check first tool is always simplest
|
||||
}
|
||||
|
||||
export function makeItem(name, amount=1) {
|
||||
return new Item(getItemId(name), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ingredients required to use the recipe once.
|
||||
*
|
||||
* @param {Recipe} recipe
|
||||
* @returns {Object<mc.ItemName, number>} an object describing the number of each ingredient.
|
||||
*/
|
||||
export function ingredientsFromPrismarineRecipe(recipe) {
|
||||
let requiredIngedients = {};
|
||||
if (recipe.inShape)
|
||||
for (const ingredient of recipe.inShape.flat()) {
|
||||
if(ingredient.id<0) continue; //prismarine-recipe uses id -1 as an empty crafting slot
|
||||
const ingredientName = getItemName(ingredient.id);
|
||||
requiredIngedients[ingredientName] ??=0;
|
||||
requiredIngedients[ingredientName] += ingredient.count;
|
||||
}
|
||||
if (recipe.ingredients)
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if(ingredient.id<0) continue;
|
||||
const ingredientName = getItemName(ingredient.id);
|
||||
requiredIngedients[ingredientName] ??=0;
|
||||
requiredIngedients[ingredientName] -= ingredient.count;
|
||||
//Yes, the `-=` is intended.
|
||||
//prismarine-recipe uses positive numbers for the shaped ingredients but negative for unshaped.
|
||||
//Why this is the case is beyond my understanding.
|
||||
}
|
||||
return requiredIngedients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of times an action, such as a crafing recipe, can be completed before running out of resources.
|
||||
* @template T - doesn't have to be an item. This could be any resource.
|
||||
* @param {Object.<T, number>} availableItems - The resources available; e.g, `{'cobble_stone': 7, 'stick': 10}`
|
||||
* @param {Object.<T, number>} requiredItems - The resources required to complete the action once; e.g, `{'cobble_stone': 3, 'stick': 2}`
|
||||
* @param {boolean} discrete - Is the action discrete?
|
||||
* @returns {{num: number, limitingResource: (T | null)}} the number of times the action can be completed and the limmiting resource; e.g `{num: 2, limitingResource: 'cobble_stone'}`
|
||||
*/
|
||||
export function calculateLimitingResource(availableItems, requiredItems, discrete=true) {
|
||||
let limitingResource = null;
|
||||
let num = Infinity;
|
||||
for (const itemType in requiredItems) {
|
||||
if (availableItems[itemType] < requiredItems[itemType] * num) {
|
||||
limitingResource = itemType;
|
||||
num = availableItems[itemType] / requiredItems[itemType];
|
||||
}
|
||||
}
|
||||
if(discrete) num = Math.floor(num);
|
||||
return {num, limitingResource}
|
||||
}
|
||||
export const mc = new MinecraftData();
|
|
@ -1,3 +1,4 @@
|
|||
import settings from '../../settings.js';
|
||||
import net from 'net';
|
||||
import mc from 'minecraft-protocol';
|
||||
|
||||
|
@ -82,3 +83,46 @@ export async function findServers(ip, earlyExit = false, timeout = 100) {
|
|||
|
||||
return servers;
|
||||
}
|
||||
|
||||
export async function getServer() {
|
||||
let server = null;
|
||||
let serverString = "";
|
||||
let serverVersion = "";
|
||||
|
||||
// Search for server
|
||||
if (settings.port == -1)
|
||||
{
|
||||
console.log(`No port provided. Searching for LAN server on host ${settings.host}...`);
|
||||
|
||||
await findServers(settings.host, true).then((servers) => {
|
||||
if (servers.length > 0)
|
||||
server = servers[0];
|
||||
});
|
||||
|
||||
if (server == null)
|
||||
throw new Error(`No server found on LAN.`);
|
||||
}
|
||||
else
|
||||
server = await serverInfo(settings.host, settings.port);
|
||||
|
||||
// Server not found
|
||||
if (server == null)
|
||||
throw new Error(`Server not found. (Host: ${settings.host}, Port: ${settings.port}) Check the host and port in settings.js.`);
|
||||
|
||||
serverString = `(Host: ${server.host}, Port: ${server.port}, Version: ${server.version})`;
|
||||
|
||||
if (settings.minecraft_version === "auto")
|
||||
serverVersion = server.version;
|
||||
else
|
||||
serverVersion = settings.minecraft_version;
|
||||
|
||||
// Server version unsupported / mismatch
|
||||
if (mc.supportedVersions.indexOf(serverVersion) === -1)
|
||||
throw new Error(`A server was found ${serverString}, but version is unsupported. Supported versions are: ${mc.supportedVersions.join(", ")}.`);
|
||||
else if (settings.minecraft_version !== "auto" && server.version !== settings.minecraft_version)
|
||||
throw new Error(`A server was found ${serverString}, but version is incorrect. Expected ${settings.minecraft_version}, but found ${server.version}.`);
|
||||
else
|
||||
console.log(`Server found. ${serverString}`);
|
||||
|
||||
return server;
|
||||
}
|
Loading…
Add table
Reference in a new issue