diff --git a/README.md b/README.md index 11a2894..abd5e98 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Do not connect this bot to public servers with coding enabled. This project allo ## Requirements -- [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc) (up to v1.21.1, recommend v1.21.1) +- [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc) (up to v1.21.4, recommend v1.21.1) - [Node.js Installed](https://nodejs.org/) (at least v18) - One of these: [OpenAI API Key](https://openai.com/blog/openai-api) | [Gemini API Key](https://aistudio.google.com/app/apikey) | [Anthropic API Key](https://docs.anthropic.com/claude/docs/getting-access-to-claude) | [Replicate API Key](https://replicate.com/) | [Hugging Face API Key](https://huggingface.co/) | [Groq API Key](https://console.groq.com/keys) | [Ollama Installed](https://ollama.com/download). | [Mistral API Key](https://docs.mistral.ai/getting-started/models/models_overview/) | [Qwen API Key [Intl.]](https://www.alibabacloud.com/help/en/model-studio/developer-reference/get-api-key)/[[cn]](https://help.aliyun.com/zh/model-studio/getting-started/first-api-call-to-qwen?) | [Novita AI API Key](https://novita.ai/settings?utm_source=github_mindcraft&utm_medium=github_readme&utm_campaign=link#key-management) | [Cerebras API Key](https://cloud.cerebras.ai) | [Mercury API](https://platform.inceptionlabs.ai/docs) diff --git a/settings.js b/settings.js index 2aacf91..4e7b7d2 100644 --- a/settings.js +++ b/settings.js @@ -1,5 +1,5 @@ const settings = { - "minecraft_version": "1.21.1", // supports up to 1.21.1 + "minecraft_version": "auto", // or specific version like "1.21.1" "host": "127.0.0.1", // or "localhost", "your.ip.address.here" "port": 55916, "auth": "offline", // or "microsoft" @@ -7,7 +7,7 @@ const settings = { // the mindserver manages all agents and hosts the UI "mindserver_port": 8080, - "base_profile": "survival", // survival, assistant, creative, or god_mode + "base_profile": "assistant", // survival, assistant, creative, or god_mode "profiles": [ "./andy.json", // "./profiles/gpt.json", diff --git a/src/mindcraft/mcserver.js b/src/mindcraft/mcserver.js new file mode 100644 index 0000000..4e04c74 --- /dev/null +++ b/src/mindcraft/mcserver.js @@ -0,0 +1,151 @@ +import net from 'net'; +import mc from 'minecraft-protocol'; + +/** + * Scans the IP address for Minecraft LAN servers and collects their info. + * @param {string} ip - The IP address to scan. + * @param {number} port - The port to check. + * @param {number} timeout - The connection timeout in ms. + * @param {boolean} verbose - Whether to print output on connection errors. + * @returns {Promise} - A Promise that resolves to an array of server info objects. + */ +export async function serverInfo(ip, port, timeout = 1000, verbose = false) { + return new Promise((resolve) => { + + let timeoutId = setTimeout(() => { + if (verbose) + console.error(`Timeout pinging server ${ip}:${port}`); + resolve(null); // Resolve as null if no response within timeout + }, timeout); + + mc.ping({ + host: ip, + port + }, (err, response) => { + clearTimeout(timeoutId); + + if (err) { + if (verbose) + console.error(`Error pinging server ${ip}:${port}`, err); + return resolve(null); + } + + // extract version number from modded servers like "Paper 1.21.4" + const version = response?.version?.name || ''; + const match = String(version).match(/\d+\.\d+(?:\.\d+)?/); + const numericVersion = match ? match[0] : null; + if (numericVersion !== version) { + console.log(`Modded server found (${version}), attempting to use ${numericVersion}...`); + } + + const serverInfo = { + host: ip, + port, + name: response.description.text || 'No description provided.', + ping: response.latency, + version: numericVersion + }; + + resolve(serverInfo); + }); + }); +} + +/** + * Scans the IP address for Minecraft LAN servers and collects their info. + * @param {string} ip - The IP address to scan. + * @param {boolean} earlyExit - Whether to exit early after finding a server. + * @param {number} timeout - The connection timeout in ms. + * @returns {Promise} - A Promise that resolves to an array of server info objects. + */ +export async function findServers(ip, earlyExit = false, timeout = 100) { + const servers = []; + const startPort = 49000; + const endPort = 65000; + + const checkPort = (port) => { + return new Promise((resolve) => { + const socket = net.createConnection({ host: ip, port, timeout }, () => { + socket.end(); + resolve(port); // Port is open + }); + + socket.on('error', () => resolve(null)); // Port is closed + socket.on('timeout', () => { + socket.destroy(); + resolve(null); + }); + }); + }; + + // This supresses a lot of annoying console output from the mc library + // TODO: find a better way to do this, it supresses other useful output + const originalConsoleLog = console.log; + console.log = () => { }; + + for (let port = startPort; port <= endPort; port++) { + const openPort = await checkPort(port); + if (openPort) { + const server = await serverInfo(ip, port, 200, false); + if (server) { + servers.push(server); + + if (earlyExit) break; + } + } + } + + // Restore console output + console.log = originalConsoleLog; + + return servers; +} + +/** + * Gets the MC server info from the host and port. + * @param {string} host - The host to search for. + * @param {number} port - The port to search for. + * @param {string} version - The version to search for. + * @returns {Promise} - A Promise that resolves to the server info object. + */ +export async function getServer(host, port, version) { + let server = null; + let serverString = ""; + let serverVersion = ""; + + // Search for server + if (port == -1) + { + console.log(`No port provided. Searching for LAN server on host ${host}...`); + + await findServers(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(host, port, 1000, true); + + // Server not found + if (server == null) + throw new Error(`MC server not found. (Host: ${host}, Port: ${port}) Check the host and port in settings.js, and ensure the server is running and open to public or LAN.`); + + serverString = `(Host: ${server.host}, Port: ${server.port}, Version: ${server.version})`; + + if (version === "auto") + serverVersion = server.version; + else + serverVersion = version; + // Server version unsupported / mismatch + if (mc.supportedVersions.indexOf(serverVersion) === -1) + throw new Error(`MC server was found ${serverString}, but version is unsupported. Supported versions are: ${mc.supportedVersions.join(", ")}.`); + else if (version !== "auto" && server.version !== version) + throw new Error(`MC server was found ${serverString}, but version is incorrect. Expected ${version}, but found ${server.version}. Check the server version in settings.js.`); + else + console.log(`MC server found. ${serverString}`); + + return server; +} \ No newline at end of file diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index cd18748..57c4dfe 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -1,11 +1,11 @@ import { createMindServer, registerAgent } from './mindserver.js'; import { AgentProcess } from '../process/agent_process.js'; +import { getServer } from './mcserver.js'; let mindserver; let connected = false; let agent_processes = {}; let agent_count = 0; -let host = 'localhost'; let port = 8080; export async function init(host_public=false, port=8080) { @@ -28,6 +28,12 @@ export async function createAgent(settings) { registerAgent(settings); let load_memory = settings.load_memory || false; let init_message = settings.init_message || null; + + const server = await getServer(settings.host, settings.port, settings.minecraft_version); + settings.host = server.host; + settings.port = server.port; + settings.minecraft_version = server.version; + const agentProcess = new AgentProcess(agent_name, port); agentProcess.start(load_memory, init_message, agent_count); agent_count++;