mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-21 21:52:07 +02:00
Merge branch 'main' into viewport-capture
This commit is contained in:
commit
a467bdf357
7 changed files with 316 additions and 84 deletions
|
@ -1,8 +1,8 @@
|
|||
diff --git a/node_modules/mineflayer-collectblock/lib/CollectBlock.js b/node_modules/mineflayer-collectblock/lib/CollectBlock.js
|
||||
index 2c11e8c..4697873 100644
|
||||
index 2c11e8c..bb49c11 100644
|
||||
--- a/node_modules/mineflayer-collectblock/lib/CollectBlock.js
|
||||
+++ b/node_modules/mineflayer-collectblock/lib/CollectBlock.js
|
||||
@@ -77,7 +77,7 @@ function mineBlock(bot, block, options) {
|
||||
@@ -77,10 +77,11 @@ function mineBlock(bot, block, options) {
|
||||
}
|
||||
yield bot.tool.equipForBlock(block, equipToolOptions);
|
||||
// @ts-expect-error
|
||||
|
@ -11,7 +11,20 @@ index 2c11e8c..4697873 100644
|
|||
options.targets.removeTarget(block);
|
||||
return;
|
||||
}
|
||||
@@ -195,6 +195,8 @@ class CollectBlock {
|
||||
+
|
||||
const tempEvents = new TemporarySubscriber_1.TemporarySubscriber(bot);
|
||||
tempEvents.subscribeTo('itemDrop', (entity) => {
|
||||
if (entity.position.distanceTo(block.position.offset(0.5, 0.5, 0.5)) <= 0.5) {
|
||||
@@ -92,7 +93,7 @@ function mineBlock(bot, block, options) {
|
||||
// Waiting for items to drop
|
||||
yield new Promise(resolve => {
|
||||
let remainingTicks = 10;
|
||||
- tempEvents.subscribeTo('physicTick', () => {
|
||||
+ tempEvents.subscribeTo('physicsTick', () => {
|
||||
remainingTicks--;
|
||||
if (remainingTicks <= 0) {
|
||||
tempEvents.cleanup();
|
||||
@@ -195,6 +196,8 @@ class CollectBlock {
|
||||
throw (0, Util_1.error)('UnresolvedDependency', 'The mineflayer-collectblock plugin relies on the mineflayer-tool plugin to run!');
|
||||
}
|
||||
if (this.movements != null) {
|
||||
|
|
13
patches/mineflayer-pvp+1.3.2.patch
Normal file
13
patches/mineflayer-pvp+1.3.2.patch
Normal file
|
@ -0,0 +1,13 @@
|
|||
diff --git a/node_modules/mineflayer-pvp/lib/PVP.js b/node_modules/mineflayer-pvp/lib/PVP.js
|
||||
index 758c2b3..7c7220e 100644
|
||||
--- a/node_modules/mineflayer-pvp/lib/PVP.js
|
||||
+++ b/node_modules/mineflayer-pvp/lib/PVP.js
|
||||
@@ -48,7 +48,7 @@ class PVP {
|
||||
this.meleeAttackRate = new TimingSolver_1.MaxDamageOffset();
|
||||
this.bot = bot;
|
||||
this.movements = new mineflayer_pathfinder_1.Movements(bot, require('minecraft-data')(bot.version));
|
||||
- this.bot.on('physicTick', () => this.update());
|
||||
+ this.bot.on('physicsTick', () => this.update());
|
||||
this.bot.on('entityGone', e => { if (e === this.target)
|
||||
this.stop(); });
|
||||
}
|
|
@ -23,7 +23,7 @@ export const actionsList = [
|
|||
name: '!newAction',
|
||||
description: 'Perform new and unknown custom behaviors that are not available as a command.',
|
||||
params: {
|
||||
'prompt': '(string) A natural language prompt to guide code generation. Make a detailed step-by-step plan.'
|
||||
'prompt': { type: 'string', description: 'A natural language prompt to guide code generation. Make a detailed step-by-step plan.' }
|
||||
},
|
||||
perform: async function (agent, prompt) {
|
||||
// just ignore prompt - it is now in context in chat history
|
||||
|
@ -75,8 +75,8 @@ export const actionsList = [
|
|||
name: '!goToPlayer',
|
||||
description: 'Go to the given player.',
|
||||
params: {
|
||||
'player_name': '(string) The name of the player to go to.',
|
||||
'closeness': '(number) How close to get to the player.'
|
||||
'player_name': {type: 'string', description: 'The name of the player to go to.'},
|
||||
'closeness': {type: 'float', description: 'How close to get to the player.', domain: [0, Infinity]}
|
||||
},
|
||||
perform: wrapExecution(async (agent, player_name, closeness) => {
|
||||
return await skills.goToPlayer(agent.bot, player_name, closeness);
|
||||
|
@ -86,8 +86,8 @@ export const actionsList = [
|
|||
name: '!followPlayer',
|
||||
description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on.',
|
||||
params: {
|
||||
'player_name': '(string) The name of the player to follow.',
|
||||
'follow_dist': '(number) The distance to follow from.'
|
||||
'player_name': {type: 'string', description: 'name of the player to follow.'},
|
||||
'follow_dist': {type: 'float', description: 'The distance to follow from.', domain: [0, Infinity]}
|
||||
},
|
||||
perform: wrapExecution(async (agent, player_name, follow_dist) => {
|
||||
await skills.followPlayer(agent.bot, player_name, follow_dist);
|
||||
|
@ -97,9 +97,9 @@ export const actionsList = [
|
|||
name: '!goToBlock',
|
||||
description: 'Go to the nearest block of a given type.',
|
||||
params: {
|
||||
'type': '(string) The block type to go to.',
|
||||
'closeness': '(number) How close to get to the block.',
|
||||
'search_range': '(number) The distance to search for the block.'
|
||||
'type': { type: 'BlockName', description: 'The block type to go to.' },
|
||||
'closeness': { type: 'float', description: 'How close to get to the block.', domain: [0, Infinity] },
|
||||
'search_range': { type: 'float', description: 'The distance to search for the block.', domain: [0, Infinity] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, type, closeness, range) => {
|
||||
await skills.goToNearestBlock(agent.bot, type, closeness, range);
|
||||
|
@ -108,7 +108,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!moveAway',
|
||||
description: 'Move away from the current location in any direction by a given distance.',
|
||||
params: {'distance': '(number) The distance to move away.'},
|
||||
params: {'distance': { type: 'float', description: 'The distance to move away.', domain: [0, Infinity] }},
|
||||
perform: wrapExecution(async (agent, distance) => {
|
||||
await skills.moveAway(agent.bot, distance);
|
||||
})
|
||||
|
@ -116,7 +116,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!rememberHere',
|
||||
description: 'Save the current location with a given name.',
|
||||
params: {'name': '(string) The name to remember the location as.'},
|
||||
params: {'name': { type: 'string', description: 'The name to remember the location as.' }},
|
||||
perform: async function (agent, name) {
|
||||
const pos = agent.bot.entity.position;
|
||||
agent.memory_bank.rememberPlace(name, pos.x, pos.y, pos.z);
|
||||
|
@ -126,7 +126,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!goToPlace',
|
||||
description: 'Go to a saved location.',
|
||||
params: {'name': '(string) The name of the location to go to.'},
|
||||
params: {'name': { type: 'string', description: 'The name of the location to go to.' }},
|
||||
perform: wrapExecution(async (agent, name) => {
|
||||
const pos = agent.memory_bank.recallPlace(name);
|
||||
if (!pos) {
|
||||
|
@ -140,9 +140,9 @@ export const actionsList = [
|
|||
name: '!givePlayer',
|
||||
description: 'Give the specified item to the given player.',
|
||||
params: {
|
||||
'player_name': '(string) The name of the player to give the item to.',
|
||||
'item_name': '(string) The name of the item to give.' ,
|
||||
'num': '(number) The number of items to give.'
|
||||
'player_name': { type: 'string', description: 'The name of the player to give the item to.' },
|
||||
'item_name': { type: 'ItemName', description: 'The name of the item to give.' },
|
||||
'num': { type: 'int', description: 'The number of items to give.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, player_name, item_name, num) => {
|
||||
await skills.giveToPlayer(agent.bot, item_name, player_name, num);
|
||||
|
@ -151,7 +151,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!equip',
|
||||
description: 'Equip the given item.',
|
||||
params: {'item_name': '(string) The name of the item to equip.'},
|
||||
params: {'item_name': { type: 'ItemName', description: 'The name of the item to equip.' }},
|
||||
perform: wrapExecution(async (agent, item_name) => {
|
||||
await skills.equip(agent.bot, item_name);
|
||||
})
|
||||
|
@ -160,8 +160,8 @@ export const actionsList = [
|
|||
name: '!putInChest',
|
||||
description: 'Put the given item in the nearest chest.',
|
||||
params: {
|
||||
'item_name': '(string) The name of the item to put in the chest.',
|
||||
'num': '(number) The number of items to put in the chest.'
|
||||
'item_name': { type: 'ItemName', description: 'The name of the item to put in the chest.' },
|
||||
'num': { type: 'int', description: 'The number of items to put in the chest.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, item_name, num) => {
|
||||
await skills.putInChest(agent.bot, item_name, num);
|
||||
|
@ -171,8 +171,8 @@ export const actionsList = [
|
|||
name: '!takeFromChest',
|
||||
description: 'Take the given items from the nearest chest.',
|
||||
params: {
|
||||
'item_name': '(string) The name of the item to take.',
|
||||
'num': '(number) The number of items to take.'
|
||||
'item_name': { type: 'ItemName', description: 'The name of the item to take.' },
|
||||
'num': { type: 'int', description: 'The number of items to take.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, item_name, num) => {
|
||||
await skills.takeFromChest(agent.bot, item_name, num);
|
||||
|
@ -190,8 +190,8 @@ export const actionsList = [
|
|||
name: '!discard',
|
||||
description: 'Discard the given item from the inventory.',
|
||||
params: {
|
||||
'item_name': '(string) The name of the item to discard.',
|
||||
'num': '(number) The number of items to discard.',
|
||||
'item_name': { type: 'ItemName', description: 'The name of the item to discard.' },
|
||||
'num': { type: 'int', description: 'The number of items to discard.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, item_name, num) => {
|
||||
const start_loc = agent.bot.entity.position;
|
||||
|
@ -204,8 +204,8 @@ export const actionsList = [
|
|||
name: '!collectBlocks',
|
||||
description: 'Collect the nearest blocks of a given type.',
|
||||
params: {
|
||||
'type': '(string) The block type to collect.',
|
||||
'num': '(number) The number of blocks to collect.'
|
||||
'type': { type: 'BlockName', description: 'The block type to collect.' },
|
||||
'num': { type: 'int', description: 'The number of blocks to collect.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, type, num) => {
|
||||
await skills.collectBlock(agent.bot, type, num);
|
||||
|
@ -215,7 +215,7 @@ export const actionsList = [
|
|||
name: '!collectAllBlocks',
|
||||
description: 'Collect all the nearest blocks of a given type until told to stop.',
|
||||
params: {
|
||||
'type': '(string) The block type to collect.'
|
||||
'type': { type: 'BlockName', description: 'The block type to collect.' }
|
||||
},
|
||||
perform: wrapExecution(async (agent, type) => {
|
||||
let success = await skills.collectBlock(agent.bot, type, 1);
|
||||
|
@ -227,8 +227,8 @@ export const actionsList = [
|
|||
name: '!craftRecipe',
|
||||
description: 'Craft the given recipe a given number of times.',
|
||||
params: {
|
||||
'recipe_name': '(string) The name of the output item to craft.',
|
||||
'num': '(number) The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.'
|
||||
'recipe_name': { type: 'ItemName', description: 'The name of the output item to craft.' },
|
||||
'num': { type: 'int', description: 'The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: wrapExecution(async (agent, recipe_name, num) => {
|
||||
await skills.craftRecipe(agent.bot, recipe_name, num);
|
||||
|
@ -238,8 +238,8 @@ export const actionsList = [
|
|||
name: '!smeltItem',
|
||||
description: 'Smelt the given item the given number of times.',
|
||||
params: {
|
||||
'item_name': '(string) The name of the input item to smelt.',
|
||||
'num': '(number) The number of times to smelt the item.'
|
||||
'item_name': { type: 'string', description: 'The name of the input item to smelt.' },
|
||||
'num': { type: 'int', description: 'The number of times to smelt the item.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: async function (agent, item_name, num) {
|
||||
let response = await wrapExecution(async (agent) => {
|
||||
|
@ -265,7 +265,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!placeHere',
|
||||
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.',
|
||||
params: {'type': '(string) The block type to place.'},
|
||||
params: {type: 'string', description: 'The block type to place.'},
|
||||
perform: wrapExecution(async (agent, type) => {
|
||||
let pos = agent.bot.entity.position;
|
||||
await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z);
|
||||
|
@ -274,7 +274,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!attack',
|
||||
description: 'Attack and kill the nearest entity of a given type.',
|
||||
params: {'type': '(string) The type of entity to attack.'},
|
||||
params: {'type': 'string', description: 'The type of entity to attack.'},
|
||||
perform: wrapExecution(async (agent, type) => {
|
||||
await skills.attackNearest(agent.bot, type, true);
|
||||
})
|
||||
|
@ -289,7 +289,7 @@ export const actionsList = [
|
|||
{
|
||||
name: '!activate',
|
||||
description: 'Activate the nearest object of a given type.',
|
||||
params: {'type': '(string) The type of object to activate.'},
|
||||
params: {'type': { type: 'BlockName', description: 'The type of object to activate.' }},
|
||||
perform: wrapExecution(async (agent, type) => {
|
||||
await skills.activateNearestBlock(agent.bot, type);
|
||||
})
|
||||
|
@ -305,8 +305,8 @@ export const actionsList = [
|
|||
name: '!setMode',
|
||||
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.',
|
||||
params: {
|
||||
'mode_name': '(string) The name of the mode to enable.',
|
||||
'on': '(bool) Whether to enable or disable the mode.'
|
||||
'mode_name': { type: 'string', description: 'The name of the mode to enable.' },
|
||||
'on': { type: 'boolean', description: 'Whether to enable or disable the mode.' }
|
||||
},
|
||||
perform: async function (agent, mode_name, on) {
|
||||
const modes = agent.bot.modes;
|
||||
|
@ -322,7 +322,7 @@ export const actionsList = [
|
|||
name: '!goal',
|
||||
description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.',
|
||||
params: {
|
||||
'selfPrompt': '(string) The goal prompt.',
|
||||
'selfPrompt': { type: 'string', description: 'The goal prompt.' },
|
||||
},
|
||||
perform: async function (agent, prompt) {
|
||||
agent.self_prompter.start(prompt); // don't await, don't return
|
||||
|
@ -340,8 +340,8 @@ export const actionsList = [
|
|||
name: '!npcGoal',
|
||||
description: 'Set a simple goal for an item or building to automatically work towards. Do not use for complex goals.',
|
||||
params: {
|
||||
'name': '(string) The name of the goal to set. Can be item or building name. If empty will automatically choose a goal.',
|
||||
'quantity': '(number) The quantity of the goal to set. Default is 1.'
|
||||
'name': { type: 'string', description: 'The name of the goal to set. Can be item or building name. If empty will automatically choose a goal.' },
|
||||
'quantity': { type: 'int', description: 'The quantity of the goal to set. Default is 1.', domain: [1, Number.MAX_SAFE_INTEGER] }
|
||||
},
|
||||
perform: async function (agent, name=null, quantity=1) {
|
||||
await agent.npc.setGoal(name, quantity);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { getBlockId, getItemId } from "../../utils/mcdata.js";
|
||||
import { actionsList } from './actions.js';
|
||||
import { queryList } from './queries.js';
|
||||
|
||||
let suppressNoDomainWarning = false;
|
||||
|
||||
const commandList = queryList.concat(actionsList);
|
||||
const commandMap = {};
|
||||
|
@ -28,43 +30,138 @@ export function commandExists(commandName) {
|
|||
return commandMap[commandName] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string into a boolean.
|
||||
* @param {string} input
|
||||
* @returns {boolean | null} the boolean or `null` if it could not be parsed.
|
||||
* */
|
||||
function parseBoolean(input) {
|
||||
switch(input.toLowerCase()) {
|
||||
case 'false': //These are interpreted as flase;
|
||||
case 'f':
|
||||
case '0':
|
||||
case 'off':
|
||||
return false;
|
||||
case 'true': //These are interpreted as true;
|
||||
case 't':
|
||||
case '1':
|
||||
case 'on':
|
||||
return true;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value - the value to check
|
||||
* @param {number} lowerBound
|
||||
* @param {number} upperBound
|
||||
* @param {string} endpointType - The type of the endpoints represented as a two character string. `'[)'` `'()'`
|
||||
*/
|
||||
function checkInInterval(number, lowerBound, upperBound, endpointType) {
|
||||
switch (endpointType) {
|
||||
case '[)':
|
||||
return lowerBound <= number && number < upperBound;
|
||||
case '()':
|
||||
return lowerBound < number && number < upperBound;
|
||||
case '(]':
|
||||
return lowerBound < number && number <= upperBound;
|
||||
case '[]':
|
||||
return lowerBound <= number && number <= upperBound;
|
||||
default:
|
||||
throw new Error('Unknown endpoint type:', endpointType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// todo: handle arrays?
|
||||
/**
|
||||
* Returns an object containing the command, the command name, and the comand parameters.
|
||||
* If parsing unsuccessful, returns an error message as a string.
|
||||
* @param {string} message - A message from a player or language model containing a command.
|
||||
* @returns {string | Object}
|
||||
*/
|
||||
function parseCommandMessage(message) {
|
||||
const commandMatch = message.match(commandRegex);
|
||||
if (commandMatch) {
|
||||
const commandName = "!"+commandMatch[1];
|
||||
if (!commandMatch[2])
|
||||
return { commandName, args: [] };
|
||||
let args = commandMatch[2].match(argRegex);
|
||||
if (args) {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
args[i] = args[i].trim();
|
||||
}
|
||||
if (!commandMatch) return `Command is incorrectly formatted`;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
let arg = args[i];
|
||||
const commandName = "!"+commandMatch[1];
|
||||
|
||||
if (arg.includes('=')) {
|
||||
// this sanitizes syntaxes like "x=2" and ignores the param name
|
||||
let split = arg.split('=');
|
||||
args[i] = split[1];
|
||||
}
|
||||
let args;
|
||||
if (commandMatch[2]) args = commandMatch[2].match(argRegex);
|
||||
else args = [];
|
||||
|
||||
if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) {
|
||||
args[i] = arg.substring(1, arg.length-1);
|
||||
} else if (!isNaN(arg)) {
|
||||
args[i] = Number(arg);
|
||||
} else if (arg === 'true' || arg === 'false') {
|
||||
args[i] = arg === 'true';
|
||||
}
|
||||
}
|
||||
const command = getCommand(commandName);
|
||||
if(!command) return `${commandName} is not a command.`
|
||||
|
||||
const params = commandParams(command);
|
||||
const paramNames = commandParamNames(command);
|
||||
|
||||
if (args.length !== params.length)
|
||||
return `Command ${command.name} was given ${args.length} args, but requires ${params.length} args.`;
|
||||
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const param = params[i];
|
||||
//Remove any extra characters
|
||||
let arg = args[i].trim();
|
||||
if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) {
|
||||
arg = arg.substring(1, arg.length-1);
|
||||
}
|
||||
else
|
||||
args = [];
|
||||
|
||||
return { commandName, args };
|
||||
if (arg.includes('=')) {
|
||||
// this sanitizes syntaxes like "x=2" and ignores the param name
|
||||
let split = arg.split('=');
|
||||
args[i] = split[1];
|
||||
}
|
||||
|
||||
//Convert to the correct type
|
||||
switch(param.type) {
|
||||
case 'int':
|
||||
arg = Number.parseInt(arg); break;
|
||||
case 'float':
|
||||
arg = Number.parseFloat(arg); break;
|
||||
case 'boolean':
|
||||
arg = parseBoolean(arg); break;
|
||||
case 'BlockName':
|
||||
case 'ItemName':
|
||||
if (arg.endsWith('plank'))
|
||||
arg += 's'; // catches common mistakes like "oak_plank" instead of "oak_planks"
|
||||
case 'string':
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Command '${commandName}' parameter '${paramNames[i]}' has an unknown type: ${param.type}`);
|
||||
}
|
||||
if(arg === null || Number.isNaN(arg))
|
||||
return `Error: Param '${paramNames[i]}' must be of type ${param.type}.`
|
||||
|
||||
if(typeof arg === 'number') { //Check the domain of numbers
|
||||
const domain = param.domain;
|
||||
if(domain) {
|
||||
/**
|
||||
* Javascript has a built in object for sets but not intervals.
|
||||
* Currently the interval (lowerbound,upperbound] is represented as an Array: `[lowerbound, upperbound, '(]']`
|
||||
*/
|
||||
if (!domain[2]) domain[2] = '[)'; //By default, lower bound is included. Upper is not.
|
||||
|
||||
if(!checkInInterval(arg, ...domain)) {
|
||||
return `Error: Param '${paramNames[i]}' must be an element of ${domain[2][0]}${domain[0]}, ${domain[1]}${domain[2][1]}.`;
|
||||
//Alternatively arg could be set to the nearest value in the domain.
|
||||
}
|
||||
} else if (!suppressNoDomainWarning) {
|
||||
console.warn(`Command '${commandName}' parameter '${paramNames[i]}' has no domain set. Expect any value [-Infinity, Infinity].`)
|
||||
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}.`
|
||||
} else if(param.type === 'ItemName') { //Check that there is an item with this name
|
||||
if(getItemId(arg) == null) return `Invalid item type: ${arg}.`
|
||||
}
|
||||
args[i] = arg;
|
||||
}
|
||||
return null;
|
||||
|
||||
return { commandName, args };
|
||||
}
|
||||
|
||||
export function truncCommandMessage(message) {
|
||||
|
@ -79,15 +176,36 @@ export function isAction(name) {
|
|||
return actionsList.find(action => action.name === name) !== undefined;
|
||||
}
|
||||
|
||||
function numParams(command) {
|
||||
/**
|
||||
* @param {Object} command
|
||||
* @returns {Object[]} The command's parameters.
|
||||
*/
|
||||
function commandParams(command) {
|
||||
if (!command.params)
|
||||
return 0;
|
||||
return Object.keys(command.params).length;
|
||||
return [];
|
||||
return Object.values(command.params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} command
|
||||
* @returns {string[]} The names of the command's parameters.
|
||||
*/
|
||||
function commandParamNames(command) {
|
||||
if (!command.params)
|
||||
return [];
|
||||
return Object.keys(command.params);
|
||||
}
|
||||
|
||||
function numParams(command) {
|
||||
return commandParams(command).length;
|
||||
}
|
||||
|
||||
export async function executeCommand(agent, message) {
|
||||
let parsed = parseCommandMessage(message);
|
||||
if (parsed) {
|
||||
if (typeof parsed === 'string')
|
||||
return parsed; //The command was incorrectly formatted or an invalid input was given.
|
||||
else {
|
||||
console.log('parsed command:', parsed);
|
||||
const command = getCommand(parsed.commandName);
|
||||
const is_action = isAction(command.name);
|
||||
let numArgs = 0;
|
||||
|
@ -106,11 +224,18 @@ export async function executeCommand(agent, message) {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
return `Command is incorrectly formatted`;
|
||||
}
|
||||
|
||||
export function getCommandDocs() {
|
||||
const typeTranslations = {
|
||||
//This was added to keep the prompt the same as before type checks were implemented.
|
||||
//If the language model is giving invalid inputs changing this might help.
|
||||
'float': 'number',
|
||||
'int': 'number',
|
||||
'BlockName': 'string',
|
||||
'ItemName': 'string',
|
||||
'boolean': 'bool'
|
||||
}
|
||||
let docs = `\n*COMMAND DOCS\n You can use the following commands to perform actions and get information about the world.
|
||||
Use the commands with the syntax: !commandName or !commandName("arg1", 1.2, ...) if the command takes arguments.\n
|
||||
Do not use codeblocks. Only use one command in each response, trailing commands and comments will be ignored.\n`;
|
||||
|
@ -119,7 +244,7 @@ export function getCommandDocs() {
|
|||
if (command.params) {
|
||||
docs += 'Params:\n';
|
||||
for (let param in command.params) {
|
||||
docs += param + ': ' + command.params[param] + '\n';
|
||||
docs += `${param}: (${typeTranslations[command.params[param].type]??command.params[param].type}) ${command.params[param].description}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,23 @@ export const queryList = [
|
|||
else if (agent.bot.game.gameMode === 'creative') {
|
||||
res += '\n(You have infinite items in creative mode. You do not need to gather resources!!)';
|
||||
}
|
||||
|
||||
let helmet = bot.inventory.slots[5];
|
||||
let chestplate = bot.inventory.slots[6];
|
||||
let leggings = bot.inventory.slots[7];
|
||||
let boots = bot.inventory.slots[8];
|
||||
res += '\nWEARING: ';
|
||||
if (helmet)
|
||||
res += `\nHead: ${helmet.name}`;
|
||||
if (chestplate)
|
||||
res += `\nTorso: ${chestplate.name}`;
|
||||
if (leggings)
|
||||
res += `\nLegs: ${leggings.name}`;
|
||||
if (boots)
|
||||
res += `\nFeet: ${boots.name}`;
|
||||
if (!helmet && !chestplate && !leggings && !boots)
|
||||
res += 'None';
|
||||
|
||||
return pad(res);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -32,7 +32,6 @@ async function equipHighestAttack(bot) {
|
|||
await bot.equip(weapon, 'hand');
|
||||
}
|
||||
|
||||
|
||||
export async function craftRecipe(bot, itemName, num=1) {
|
||||
/**
|
||||
* Attempt to craft the given item name from a recipe. May craft many items.
|
||||
|
@ -44,14 +43,13 @@ export async function craftRecipe(bot, itemName, num=1) {
|
|||
**/
|
||||
let placedTable = false;
|
||||
|
||||
if (itemName.endsWith('plank'))
|
||||
itemName += 's'; // catches common mistakes like "oak_plank" instead of "oak_planks"
|
||||
|
||||
// get recipes that don't require a crafting table
|
||||
let recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, null);
|
||||
let craftingTable = null;
|
||||
const craftingTableRange = 32;
|
||||
if (!recipes || recipes.length === 0) {
|
||||
placeTable: if (!recipes || recipes.length === 0) {
|
||||
recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, true);
|
||||
if(!recipes || recipes.length === 0) break placeTable; //Don't bother going to the table if we don't have the required resources.
|
||||
|
||||
// Look for crafting table
|
||||
craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange);
|
||||
|
@ -69,7 +67,7 @@ export async function craftRecipe(bot, itemName, num=1) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
log(bot, `You either do not have enough resources to craft ${itemName} or it requires a crafting table.`)
|
||||
log(bot, `Crafting ${itemName} requires a crafting table.`)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -91,11 +89,22 @@ export async function craftRecipe(bot, itemName, num=1) {
|
|||
|
||||
const recipe = recipes[0];
|
||||
console.log('crafting...');
|
||||
await bot.craft(recipe, num, craftingTable);
|
||||
log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
|
||||
//Check that the agent has sufficient items to use the recipe `num` times.
|
||||
const inventory = world.getInventoryCounts(bot); //Items in the agents inventory
|
||||
const requiredIngredients = mc.ingredientsFromPrismarineRecipe(recipe); //Items required to use the recipe once.
|
||||
const craftLimit = mc.calculateLimitingResource(inventory, requiredIngredients);
|
||||
|
||||
await bot.craft(recipe, Math.min(craftLimit.num, num), craftingTable);
|
||||
if(craftLimit.num<num) log(bot, `Not enough ${craftLimit.limitingResource} to craft ${num}, crafted ${craftLimit.num}. You now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
|
||||
else log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
|
||||
if (placedTable) {
|
||||
await collectBlock(bot, 'crafting_table', 1);
|
||||
}
|
||||
|
||||
//Equip any armor the bot may have crafted.
|
||||
//There is probablly a more efficient method than checking the entire inventory but this is all mineflayer-armor-manager provides. :P
|
||||
bot.armorManager.equipAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -682,7 +691,7 @@ export async function equip(bot, itemName) {
|
|||
* @example
|
||||
* await skills.equip(bot, "iron_pickaxe");
|
||||
**/
|
||||
let item = bot.inventory.items().find(item => item.name === itemName);
|
||||
let item = bot.inventory.slots.find(slot => slot && slot.name === itemName);
|
||||
if (!item) {
|
||||
log(bot, `You do not have any ${itemName} to equip.`);
|
||||
return false;
|
||||
|
@ -696,12 +705,13 @@ export async function equip(bot, itemName) {
|
|||
else if (itemName.includes('helmet')) {
|
||||
await bot.equip(item, 'head');
|
||||
}
|
||||
else if (itemName.includes('chestplate')) {
|
||||
else if (itemName.includes('chestplate') || itemName.includes('elytra')) {
|
||||
await bot.equip(item, 'torso');
|
||||
}
|
||||
else {
|
||||
await bot.equip(item, 'hand');
|
||||
}
|
||||
log(bot, `Equipped ${itemName}.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,11 @@ const mc_version = settings.minecraft_version;
|
|||
const mcdata = minecraftData(mc_version);
|
||||
const Item = prismarine_items(mc_version);
|
||||
|
||||
/**
|
||||
* @typedef {string} ItemName
|
||||
* @typedef {string} BlockName
|
||||
*/
|
||||
|
||||
export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak'];
|
||||
export const MATCHING_WOOD_BLOCKS = [
|
||||
'log',
|
||||
|
@ -241,4 +246,53 @@ export function getBlockTool(blockName) {
|
|||
|
||||
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}
|
||||
}
|
Loading…
Add table
Reference in a new issue