mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-09-10 03:53:07 +02:00
commit
95e3f85f69
11 changed files with 165 additions and 69 deletions
|
@ -15,3 +15,8 @@ Start minecraft server on localhost port `55916`
|
|||
Add `OPENAI_API_KEY` (and optionally `OPENAI_ORG_ID`) to your environment variables.
|
||||
|
||||
run `node main.js`
|
||||
|
||||
## Node Module Patches
|
||||
|
||||
Some of the node modules that we depend on have bugs in them. PRs have been submitted but currently you must manually implement these fixes in some node modules:
|
||||
- In `mineflayer-collectblock/lib/CollectBlock.js`, replace `!block.canHarvest(bot.heldItem)` with `bot.heldItem !== null && !block.canHarvest(bot.heldItem.type)` (line 80?)
|
7
agent.js
7
agent.js
|
@ -7,12 +7,15 @@ import { containsCodeBlock } from './utils/skill_library.js';
|
|||
|
||||
|
||||
export class Agent {
|
||||
constructor(name, save_path) {
|
||||
constructor(name, save_path, restart_memory=false) {
|
||||
this.name = name;
|
||||
this.bot = initBot(name);
|
||||
this.history = new History(this, save_path);
|
||||
this.coder = new Coder(this);
|
||||
this.history.load();
|
||||
|
||||
if (!restart_memory) {
|
||||
this.history.load();
|
||||
}
|
||||
|
||||
this.bot.on('login', () => {
|
||||
this.bot.chat('Hello world! I am ' + this.name);
|
||||
|
|
2
main.js
2
main.js
|
@ -1,4 +1,4 @@
|
|||
import { Agent } from './agent.js';
|
||||
|
||||
let agent = new Agent('andy', 'bots/andy.json');
|
||||
let agent = new Agent('andy', './bots/andy.json', true);
|
||||
agent.start();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"mineflayer": "^4.14.0",
|
||||
"mineflayer-collectblock": "^1.4.1",
|
||||
"mineflayer-pathfinder": "^2.4.4",
|
||||
"mineflayer-pvp": "^1.3.2",
|
||||
"openai": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ export class Coder {
|
|||
|
||||
readFile(this.fp+'template.js', 'utf8', (err, data) => {
|
||||
if (err) throw err;
|
||||
console.log('Template str:', data);
|
||||
this.code_template = data;
|
||||
});
|
||||
}
|
||||
|
@ -125,6 +124,7 @@ export class Coder {
|
|||
this.agent.bot.interrupt_code = true;
|
||||
this.agent.bot.collectBlock.cancelTask();
|
||||
this.agent.bot.pathfinder.stop();
|
||||
this.agent.bot.pvp.stop();
|
||||
console.log('waiting for code to finish executing... interrupt:', this.agent.bot.interrupt_code);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
|
|
@ -11,6 +11,20 @@
|
|||
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "greg: Collect 10 wood"},
|
||||
{"role": "assistant", "content": "Let me see what's nearby... !blocks"},
|
||||
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
|
||||
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs.\n```\n// I am going to collect 10 oak_log.\nfor (let i=0; i<10; i++) {\n await skills.collectBlock(bot, 'oak_log');\n}\n```"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "bobby: Craft a plank"},
|
||||
{"role": "assistant", "content": "Okay! !craftable"},
|
||||
{"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"},
|
||||
{"role": "assistant", "content": "```\n// I am going to craft a spruce_planks.\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "zZZn98: come here"},
|
||||
{"role": "assistant", "content": "```\n// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, 'zZZn98');\n```"},
|
||||
|
@ -19,7 +33,7 @@
|
|||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "hanky: collect some sand for me please"},
|
||||
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
|
||||
{"role": "assistant", "content": "Collecting sand...\n```\n// I am going to collect 3 sand and give to hanky.\nawait skills.collectBlock(bot, 'sand');\nawait skills.giveToPlayer(bot, 'sand', 'hanky');\n```"},
|
||||
{"role": "system", "content": "Code Output:\nYou have reached player hanky.\nCode execution finished successfully."},
|
||||
{"role": "assistant", "content": "Here!"}
|
||||
|
@ -33,7 +47,7 @@
|
|||
[
|
||||
{"role": "user", "content": "hanky: kill that zombie!"},
|
||||
{"role": "assistant", "content": "I'm attacking!\n```\n//I'm going to attack the nearest zombie.\nawait skills.attackMob(bot, 'zombie');\n```"},
|
||||
{"role": "system", "content": "Code Output:\nNo zombie nearby\nCode execution failed!"},
|
||||
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
|
||||
{"role": "assistant", "content": "I could not find a zombie nearby."}
|
||||
],
|
||||
|
||||
|
@ -47,5 +61,10 @@
|
|||
{"role": "assistant", "content": "```\n// I am navigating to the nearest oak log.\nawait skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"},
|
||||
{"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."},
|
||||
{"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "trixy88: craft some sticks"},
|
||||
{"role": "assistant", "content": "```\n// I am going to craft 8 sticks\nawait skills.craftRecipe(bot, 'stick');\nawait skills.craftRecipe(bot, 'stick');```"}
|
||||
]
|
||||
]
|
|
@ -21,11 +21,13 @@ export async function sendRequest(turns, systemMessage, stop_seq='***') {
|
|||
|
||||
let res = null;
|
||||
try {
|
||||
console.log('Awaiting openai api response...')
|
||||
let completion = await openai.chat.completions.create({
|
||||
model: 'gpt-3.5-turbo-1106',
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages: messages,
|
||||
stop: stop_seq,
|
||||
});
|
||||
console.log('Received.')
|
||||
res = completion.choices[0].message.content;
|
||||
}
|
||||
catch (err) {
|
||||
|
|
|
@ -21,25 +21,28 @@ export class History {
|
|||
this.selected_examples = [];
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
return this.turns;
|
||||
getHistory(include_examples=true) {
|
||||
let history = [];
|
||||
if (include_examples && this.selected_examples.length > 0) {
|
||||
for (let example of this.selected_examples) {
|
||||
history = history.concat(example.turns);
|
||||
}
|
||||
}
|
||||
history = history.concat(this.turns);
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
getSystemMessage(include_examples=true) {
|
||||
getSystemMessage() {
|
||||
let system_message = `You are a playful Minecraft bot named '${this.name}' that can communicate with players, see, move, mine, build, and interact with the world by writing and executing code.
|
||||
Act human-like as if you were a typical Minecraft player, rather than an AI. Be brief in your responses, omit needless words, and do not give instructions unless asked.`;
|
||||
Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, omit needless words, and do not give instructions unless asked.`;
|
||||
system_message += getQueryDocs();
|
||||
system_message += getSkillDocs();
|
||||
if (this.bio != '')
|
||||
system_message += '\n\nBio:\n' + this.bio;
|
||||
if (this.memory != '')
|
||||
system_message += '\n\nMemory:\n' + this.memory;
|
||||
if (include_examples && this.selected_examples.length > 0) {
|
||||
for (let i = 0; i < this.selected_examples.length; i++) {
|
||||
system_message += '\n\nExample ' + (i+1) + ':\n\n';
|
||||
system_message += this.stringifyTurns(this.selected_examples[i].turns);
|
||||
}
|
||||
}
|
||||
|
||||
return system_message;
|
||||
}
|
||||
|
||||
|
@ -47,11 +50,11 @@ export class History {
|
|||
let res = '';
|
||||
for (let turn of turns) {
|
||||
if (turn.role === 'assistant') {
|
||||
res += `\n\nYour output:\n${turn.content}`;
|
||||
res += `\nYour output:\n${turn.content}`;
|
||||
} else if (turn.role === 'system') {
|
||||
res += `\n\nSystem output: ${turn.content}`;
|
||||
res += `\nSystem output: ${turn.content}`;
|
||||
} else {
|
||||
res += `\n\nUser input: ${turn.content}`;
|
||||
res += `\nUser input: ${turn.content}`;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -59,17 +62,23 @@ export class History {
|
|||
}
|
||||
|
||||
async storeMemories(turns) {
|
||||
let memory_prompt = 'Update your "Memory" with the following conversation. Your "Memory" is for storing information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you may need to remember for later. Also include things that you have learned through player feedback or by executing code. Do not include information found in your Docs or that you got right on the first try.';
|
||||
console.log("To summarize:", turns)
|
||||
let memory_prompt = 'Update your "Memory" by summarizing the following conversation. Your "Memory" is for storing information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you may need to remember for later. Also include things that you have learned through player feedback or by executing code. Do not include information found in your Docs or that you got right on the first try. Be extremely brief and clear.';
|
||||
if (this.memory != '') {
|
||||
memory_prompt += ' Include information from your previous memory if it is still relevant. Your output will replace your previous memory.';
|
||||
memory_prompt += `This is your previous memory: "${this.memory}"\n Include and summarize any relevant information from this previous memory. Your output will replace your previous memory.`;
|
||||
}
|
||||
memory_prompt += ' Your output should be a brief list of things you have learned using the following formats:\n';
|
||||
memory_prompt += '\n';
|
||||
memory_prompt += ' Your output should use one of the following formats:\n';
|
||||
memory_prompt += '- When the player... output...\n';
|
||||
memory_prompt += '- I learned that player [name]...\n';
|
||||
|
||||
|
||||
memory_prompt += 'This is the conversation to summarize:\n';
|
||||
memory_prompt += this.stringifyTurns(turns);
|
||||
let memory_turns = [{'role': 'user', 'content': memory_prompt}]
|
||||
this.memory = await sendRequest(memory_turns, this.getSystemMessage(false));
|
||||
|
||||
memory_prompt += 'Summarize relevant information from your previous memory and this conversation:\n';
|
||||
|
||||
let memory_turns = [{'role': 'system', 'content': memory_prompt}]
|
||||
this.memory = await sendRequest(memory_turns, this.getSystemMessage());
|
||||
}
|
||||
|
||||
async loadExamples() {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import minecraftData from 'minecraft-data';
|
||||
import { createBot } from 'mineflayer';
|
||||
import { pathfinder } from 'mineflayer-pathfinder';
|
||||
import { plugin } from 'mineflayer-collectblock';
|
||||
import { plugin as pvp } from 'mineflayer-pvp';
|
||||
import { plugin as collectblock } from 'mineflayer-collectblock';
|
||||
|
||||
const mc_version = '1.19.3'
|
||||
let mcdata = minecraftData(mc_version);
|
||||
const mcdata = minecraftData(mc_version);
|
||||
|
||||
|
||||
export function initBot(username) {
|
||||
|
@ -14,8 +15,9 @@ export function initBot(username) {
|
|||
username: username,
|
||||
version: mc_version,
|
||||
});
|
||||
bot.loadPlugin(pathfinder)
|
||||
bot.loadPlugin(plugin)
|
||||
bot.loadPlugin(pathfinder);
|
||||
bot.loadPlugin(pvp);
|
||||
bot.loadPlugin(collectblock);
|
||||
return bot;
|
||||
}
|
||||
|
||||
|
|
125
utils/skills.js
125
utils/skills.js
|
@ -8,18 +8,16 @@ export function log(bot, message) {
|
|||
}
|
||||
|
||||
|
||||
export async function craftItem(bot, itemName, num=1) {
|
||||
export async function craftRecipe(bot, itemName) {
|
||||
/**
|
||||
* Attempt to craft the given item.
|
||||
* Attempt to craft the given item name from a recipe. May craft many items.
|
||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||
* @param {string} item_name, the item name to craft.
|
||||
* @param {number} num, the number of items to craft. Defaults to 1.
|
||||
* @param {string} itemName, the item name to craft.
|
||||
* @returns {Promise<boolean>} true if the item was crafted, false otherwise.
|
||||
* @example
|
||||
* await skills.craftItem(bot, "wooden_pickaxe", 2);
|
||||
* await skills.craftRecipe(bot, "stick");
|
||||
**/
|
||||
|
||||
let recipes = bot.recipesFor(getItemId(itemName), null, num, null); // get recipes that don't require a crafting table
|
||||
let recipes = bot.recipesFor(getItemId(itemName), null, 1, null); // get recipes that don't require a crafting table
|
||||
let craftingTable = undefined;
|
||||
if (!recipes || recipes.length === 0) {
|
||||
craftingTable = getNearestBlock(bot, 'crafting_table', 6);
|
||||
|
@ -27,37 +25,60 @@ export async function craftItem(bot, itemName, num=1) {
|
|||
log(bot, `You either do not have enough resources to craft ${itemName} or it requires a crafting table, but there is none nearby.`)
|
||||
return false;
|
||||
}
|
||||
recipes = bot.recipesFor(getItemId(itemName), null, num, craftingTable);
|
||||
recipes = bot.recipesFor(getItemId(itemName), null, 1, craftingTable);
|
||||
}
|
||||
if (!recipes || recipes.length === 0) {
|
||||
log(bot, `You do not have the resources to craft ${num} ${itemName}(s).`);
|
||||
log(bot, `You do not have the resources to craft a ${itemName}.`);
|
||||
return false;
|
||||
}
|
||||
const recipe = recipes[0];
|
||||
|
||||
console.log('crafting...');
|
||||
await bot.craft(recipe, num, craftingTable);
|
||||
await bot.craft(recipe, 1, craftingTable);
|
||||
console.log('crafted');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export async function attackMob(bot, mobType) {
|
||||
export async function attackMob(bot, mobType, kill=true) {
|
||||
/**
|
||||
* Attack mob of the given type.
|
||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||
* @param {string} mobType, the type of mob to attack.
|
||||
* @param {boolean} kill, whether or not to continue attacking until the mob is dead. Defaults to true.
|
||||
* @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
|
||||
* @example
|
||||
* await skills.attackMob(bot, "zombie");
|
||||
* await skills.attackMob(bot, "zombie", true);
|
||||
**/
|
||||
const mobs = getNearbyMobs(bot);
|
||||
for (let i = 0; i < mobs.length; i++) {
|
||||
if (mobs[i].mobType == mobType) {
|
||||
bot.attack(mobs[i]);
|
||||
const mob = bot.nearestEntity(entity => entity.name && entity.name.toLowerCase() === mobType.toLowerCase());
|
||||
const attackable = ['animal', 'monster', 'mob'];
|
||||
if (mob && attackable.includes(mob.type)) {
|
||||
let pos = mob.position;
|
||||
console.log(bot.entity.position.distanceTo(pos))
|
||||
|
||||
if (!kill) {
|
||||
if (bot.entity.position.distanceTo(pos) > 5) {
|
||||
console.log('moving to mob...')
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 5));
|
||||
}
|
||||
console.log('attacking mob...')
|
||||
await bot.attack(mob);
|
||||
}
|
||||
else {
|
||||
bot.pvp.attack(mob);
|
||||
while (getNearbyMobs(bot, 16).includes(mob)) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (bot.interrupt_code) {
|
||||
bot.pvp.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
log(bot, 'Could not find any '+mobType+' to attack.');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -71,13 +92,20 @@ export async function collectBlock(bot, blockType) {
|
|||
* @example
|
||||
* await skills.collectBlock(bot, "oak_log");
|
||||
**/
|
||||
const blocks = getNearbyBlocks(bot);
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
if (blocks[i].name == blockType) {
|
||||
await bot.collectBlock.collect(blocks[i]);
|
||||
return true;
|
||||
const block = getNearestBlock(bot, blockType, 64);
|
||||
if (block) {
|
||||
// check if block is collectable
|
||||
await bot.tool.equipForBlock(block);
|
||||
|
||||
const itemId = bot.heldItem ? bot.heldItem.type : null
|
||||
if (!block.canHarvest(itemId)) {
|
||||
log(bot, `Don't have right tools to harvest ${blockType}.`);
|
||||
return false;
|
||||
}
|
||||
await bot.collectBlock.collect(block);
|
||||
return true;
|
||||
}
|
||||
log(bot, `Could not find ${blockType} to collect.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -115,7 +143,7 @@ export async function placeBlock(bot, blockType, x, y, z) {
|
|||
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y - 1, position.x);
|
||||
**/
|
||||
|
||||
const empty_blocks = ['air', 'water', 'lava'];
|
||||
const empty_blocks = ['air', 'water', 'lava', 'grass', 'tall_grass', 'snow', 'dead_bush', 'fern'];
|
||||
const targetBlock = bot.blockAt(new Vec3(x, y, z));
|
||||
if (!empty_blocks.includes(targetBlock.name)) {
|
||||
log(bot, `Cannot place block at ${targetBlock.position} because ${targetBlock.name} is in the way.`);
|
||||
|
@ -137,15 +165,6 @@ export async function placeBlock(bot, blockType, x, y, z) {
|
|||
log(bot, `Cannot place block at ${targetBlock.position} because there is nothing to build off of.`);
|
||||
return false;
|
||||
}
|
||||
console.log("buildOffBlock: ", buildOffBlock.position, buildOffBlock.name, "faceVec: ", faceVec)
|
||||
|
||||
// check if bot is in the way
|
||||
console.log("bot position: ", bot.entity.position, "buildOffBlock.position: ", buildOffBlock.position.plus(faceVec), "distance: ", bot.entity.position.distanceTo(buildOffBlock.position.plus(faceVec)))
|
||||
if (bot.entity.position.distanceTo(buildOffBlock.position.plus(faceVec)) < 0.5) {
|
||||
log(bot, `Cannot place block at ${buildOffBlock.position} because you are in the way.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Placing on: ", buildOffBlock.position, buildOffBlock.name)
|
||||
|
||||
let block = bot.inventory.items().find(item => item.name === blockType);
|
||||
|
@ -155,6 +174,40 @@ export async function placeBlock(bot, blockType, x, y, z) {
|
|||
}
|
||||
await bot.equip(block, 'hand');
|
||||
|
||||
|
||||
// too close
|
||||
let blockAbove = bot.blockAt(targetBlock.position.plus(Vec3(0,1,0)))
|
||||
if (bot.entity.position.distanceTo(targetBlock.position) < 1 || bot.entity.position.distanceTo(blockAbove.position) < 1) {
|
||||
console.log('moving away from block...')
|
||||
let found = false;
|
||||
for(let i = 0; i < 10; i++) {
|
||||
console.log('looking for block...')
|
||||
const randomDirection = new Vec3((Math.random() > 0.5 ? 1 : -1), 0, (Math.random() > 0.5 ? 1 : -1));
|
||||
const pos = targetBlock.position.add(randomDirection.scale(1.2));
|
||||
if (bot.blockAt(pos).name === 'air') {
|
||||
console.log('found good position')
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 1.2));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
console.log('could not find good position')
|
||||
log(bot, `Was too close to place ${blockType} at ${targetBlock.position}.`)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// too far
|
||||
if (bot.entity.position.distanceTo(targetBlock.position) > 4.5) {
|
||||
// move close until it is within 6 blocks
|
||||
console.log('moving closer to block...')
|
||||
let pos = targetBlock.position;
|
||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
|
||||
}
|
||||
// too close
|
||||
|
||||
// turn to face the block
|
||||
await bot.lookAt(buildOffBlock.position.plus(faceVec));
|
||||
|
||||
|
@ -163,7 +216,7 @@ export async function placeBlock(bot, blockType, x, y, z) {
|
|||
console.log("placing block...")
|
||||
|
||||
// wait and then check if the block was placed
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
const newBlock = bot.blockAt(buildOffBlock.position.plus(faceVec));
|
||||
if (!newBlock) return false;
|
||||
if (newBlock.name !== blockType) {
|
||||
|
@ -232,10 +285,14 @@ export async function giveToPlayer(bot, itemType, username) {
|
|||
* await skills.giveToPlayer(bot, "oak_log", "player1");
|
||||
**/
|
||||
let player = bot.players[username].entity
|
||||
if (!player)
|
||||
if (!player){
|
||||
log(bot, `Could not find ${username}.`);
|
||||
return false;
|
||||
if (!getInventoryCounts(bot)[itemType])
|
||||
}
|
||||
if (!getInventoryCounts(bot)[itemType]) {
|
||||
log(bot, `You do not have any ${itemType} to give.`);
|
||||
return false;
|
||||
}
|
||||
await goToPlayer(bot, username);
|
||||
let pos = player.position;
|
||||
await bot.lookAt(pos);
|
||||
|
|
|
@ -49,9 +49,7 @@ export function getNearbyMobs(bot, maxDistance) {
|
|||
for (const entity of Object.values(bot.entities)) {
|
||||
const distance = entity.position.distanceTo(bot.entity.position);
|
||||
if (distance > maxDistance) continue;
|
||||
if (entity.type == 'mob') {
|
||||
entities.push({ entity: entity, distance: distance });
|
||||
}
|
||||
entities.push({ entity: entity, distance: distance });
|
||||
}
|
||||
entities.sort((a, b) => a.distance - b.distance);
|
||||
let res = [];
|
||||
|
@ -138,8 +136,8 @@ export function getNearbyMobTypes(bot) {
|
|||
let mobs = getNearbyMobs(bot, 16);
|
||||
let found = [];
|
||||
for (let i = 0; i < mobs.length; i++) {
|
||||
if (!found.includes(mobs[i].mobType)) {
|
||||
found.push(mobs[i].mobType);
|
||||
if (!found.includes(mobs[i].name)) {
|
||||
found.push(mobs[i].name);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
|
|
Loading…
Add table
Reference in a new issue