Server/Minecraft data now agent-level

Also refactored mcdata as a class.
This commit is contained in:
Nimi 2024-11-14 15:00:53 -06:00
parent 7a3a5b3f22
commit 418a2470de
15 changed files with 396 additions and 359 deletions

48
main.js
View file

@ -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);
}
}

View file

@ -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}`);
}

View file

@ -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;
}

View file

@ -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) => {

View file

@ -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';

View file

@ -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) {

View file

@ -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';

View file

@ -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';

View file

@ -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 {

View file

@ -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';

View file

@ -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

View file

@ -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();
}
});

View file

@ -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',

View file

@ -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();

View file

@ -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;
}