mindcraft 0.1.4-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/FAQ.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -0
  4. package/andy.json +6 -0
  5. package/bin/mindcraft.js +80 -0
  6. package/keys.example.json +19 -0
  7. package/main.js +80 -0
  8. package/package.json +78 -0
  9. package/patches/minecraft-data+3.97.0.patch +13 -0
  10. package/patches/mineflayer+4.33.0.patch +54 -0
  11. package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
  12. package/patches/mineflayer-pvp+1.3.2.patch +13 -0
  13. package/patches/prismarine-viewer+1.33.0.patch +13 -0
  14. package/patches/protodef+1.19.0.patch +15 -0
  15. package/profiles/andy-4-reasoning.json +14 -0
  16. package/profiles/andy-4.json +7 -0
  17. package/profiles/azure.json +19 -0
  18. package/profiles/claude.json +7 -0
  19. package/profiles/claude_thinker.json +15 -0
  20. package/profiles/deepseek.json +7 -0
  21. package/profiles/defaults/_default.json +256 -0
  22. package/profiles/defaults/assistant.json +14 -0
  23. package/profiles/defaults/creative.json +14 -0
  24. package/profiles/defaults/god_mode.json +14 -0
  25. package/profiles/defaults/survival.json +14 -0
  26. package/profiles/freeguy.json +7 -0
  27. package/profiles/gemini.json +9 -0
  28. package/profiles/gpt.json +12 -0
  29. package/profiles/grok.json +7 -0
  30. package/profiles/llama.json +10 -0
  31. package/profiles/mercury.json +9 -0
  32. package/profiles/mistral.json +5 -0
  33. package/profiles/qwen.json +17 -0
  34. package/profiles/tasks/construction_profile.json +42 -0
  35. package/profiles/tasks/cooking_profile.json +11 -0
  36. package/profiles/tasks/crafting_profile.json +71 -0
  37. package/profiles/vllm.json +10 -0
  38. package/settings.js +64 -0
  39. package/src/agent/action_manager.js +177 -0
  40. package/src/agent/agent.js +561 -0
  41. package/src/agent/coder.js +229 -0
  42. package/src/agent/commands/actions.js +504 -0
  43. package/src/agent/commands/index.js +259 -0
  44. package/src/agent/commands/queries.js +347 -0
  45. package/src/agent/connection_handler.js +96 -0
  46. package/src/agent/conversation.js +353 -0
  47. package/src/agent/history.js +122 -0
  48. package/src/agent/library/full_state.js +89 -0
  49. package/src/agent/library/index.js +23 -0
  50. package/src/agent/library/lockdown.js +32 -0
  51. package/src/agent/library/skill_library.js +93 -0
  52. package/src/agent/library/skills.js +2093 -0
  53. package/src/agent/library/world.js +431 -0
  54. package/src/agent/memory_bank.js +25 -0
  55. package/src/agent/mindserver_proxy.js +136 -0
  56. package/src/agent/modes.js +446 -0
  57. package/src/agent/npc/build_goal.js +80 -0
  58. package/src/agent/npc/construction/dirt_shelter.json +38 -0
  59. package/src/agent/npc/construction/large_house.json +230 -0
  60. package/src/agent/npc/construction/small_stone_house.json +42 -0
  61. package/src/agent/npc/construction/small_wood_house.json +42 -0
  62. package/src/agent/npc/controller.js +261 -0
  63. package/src/agent/npc/data.js +50 -0
  64. package/src/agent/npc/item_goal.js +355 -0
  65. package/src/agent/npc/utils.js +126 -0
  66. package/src/agent/self_prompter.js +146 -0
  67. package/src/agent/settings.js +7 -0
  68. package/src/agent/speak.js +150 -0
  69. package/src/agent/tasks/construction_tasks.js +1104 -0
  70. package/src/agent/tasks/cooking_tasks.js +358 -0
  71. package/src/agent/tasks/tasks.js +594 -0
  72. package/src/agent/templates/execTemplate.js +6 -0
  73. package/src/agent/templates/lintTemplate.js +10 -0
  74. package/src/agent/vision/browser_viewer.js +8 -0
  75. package/src/agent/vision/camera.js +78 -0
  76. package/src/agent/vision/vision_interpreter.js +82 -0
  77. package/src/mindcraft/index.js +28 -0
  78. package/src/mindcraft/mcserver.js +154 -0
  79. package/src/mindcraft/mindcraft.js +111 -0
  80. package/src/mindcraft/mindserver.js +328 -0
  81. package/src/mindcraft/public/index.html +1253 -0
  82. package/src/mindcraft/public/settings_spec.json +145 -0
  83. package/src/mindcraft/userconfig.js +72 -0
  84. package/src/mindcraft-py/example.py +27 -0
  85. package/src/mindcraft-py/init-mindcraft.js +24 -0
  86. package/src/mindcraft-py/mindcraft.py +99 -0
  87. package/src/models/_model_map.js +89 -0
  88. package/src/models/azure.js +32 -0
  89. package/src/models/cerebras.js +61 -0
  90. package/src/models/claude.js +87 -0
  91. package/src/models/deepseek.js +59 -0
  92. package/src/models/gemini.js +176 -0
  93. package/src/models/glhf.js +71 -0
  94. package/src/models/gpt.js +147 -0
  95. package/src/models/grok.js +82 -0
  96. package/src/models/groq.js +95 -0
  97. package/src/models/huggingface.js +86 -0
  98. package/src/models/hyperbolic.js +114 -0
  99. package/src/models/lmstudio.js +74 -0
  100. package/src/models/mercury.js +95 -0
  101. package/src/models/mistral.js +94 -0
  102. package/src/models/novita.js +71 -0
  103. package/src/models/ollama.js +115 -0
  104. package/src/models/openrouter.js +77 -0
  105. package/src/models/prompter.js +366 -0
  106. package/src/models/qwen.js +80 -0
  107. package/src/models/replicate.js +60 -0
  108. package/src/models/vllm.js +81 -0
  109. package/src/process/agent_process.js +84 -0
  110. package/src/process/init_agent.js +54 -0
  111. package/src/utils/examples.js +83 -0
  112. package/src/utils/keys.js +34 -0
  113. package/src/utils/math.js +13 -0
  114. package/src/utils/mcdata.js +572 -0
  115. package/src/utils/text.js +78 -0
  116. package/src/utils/translator.js +30 -0
@@ -0,0 +1,2093 @@
1
+ import * as mc from "../../utils/mcdata.js";
2
+ import * as world from "./world.js";
3
+ import pf from 'mineflayer-pathfinder';
4
+ import Vec3 from 'vec3';
5
+ import settings from "../../../settings.js";
6
+
7
+ const blockPlaceDelay = settings.block_place_delay == null ? 0 : settings.block_place_delay;
8
+ const useDelay = blockPlaceDelay > 0;
9
+
10
+ export function log(bot, message) {
11
+ bot.output += message + '\n';
12
+ }
13
+
14
+ async function autoLight(bot) {
15
+ if (world.shouldPlaceTorch(bot)) {
16
+ try {
17
+ const pos = world.getPosition(bot);
18
+ return await placeBlock(bot, 'torch', pos.x, pos.y, pos.z, 'bottom', true);
19
+ } catch (err) {return false;}
20
+ }
21
+ return false;
22
+ }
23
+
24
+ async function equipHighestAttack(bot) {
25
+ let weapons = bot.inventory.items().filter(item => item.name.includes('sword') || (item.name.includes('axe') && !item.name.includes('pickaxe')));
26
+ if (weapons.length === 0)
27
+ weapons = bot.inventory.items().filter(item => item.name.includes('pickaxe') || item.name.includes('shovel'));
28
+ if (weapons.length === 0)
29
+ return;
30
+ weapons.sort((a, b) => a.attackDamage < b.attackDamage);
31
+ let weapon = weapons[0];
32
+ if (weapon)
33
+ await bot.equip(weapon, 'hand');
34
+ }
35
+
36
+ export async function craftRecipe(bot, itemName, num=1) {
37
+ /**
38
+ * Attempt to craft the given item name from a recipe. May craft many items.
39
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
40
+ * @param {string} itemName, the item name to craft.
41
+ * @returns {Promise<boolean>} true if the recipe was crafted, false otherwise.
42
+ * @example
43
+ * await skills.craftRecipe(bot, "stick");
44
+ **/
45
+ let placedTable = false;
46
+
47
+ if (mc.getItemCraftingRecipes(itemName).length == 0) {
48
+ log(bot, `${itemName} is either not an item, or it does not have a crafting recipe!`);
49
+ return false;
50
+ }
51
+
52
+ // get recipes that don't require a crafting table
53
+ let recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, null);
54
+ let craftingTable = null;
55
+ const craftingTableRange = 16;
56
+ placeTable: if (!recipes || recipes.length === 0) {
57
+ recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, true);
58
+ if(!recipes || recipes.length === 0) break placeTable; //Don't bother going to the table if we don't have the required resources.
59
+
60
+ // Look for crafting table
61
+ craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange);
62
+ if (craftingTable === null){
63
+
64
+ // Try to place crafting table
65
+ let hasTable = world.getInventoryCounts(bot)['crafting_table'] > 0;
66
+ if (hasTable) {
67
+ let pos = world.getNearestFreeSpace(bot, 1, 6);
68
+ await placeBlock(bot, 'crafting_table', pos.x, pos.y, pos.z);
69
+ craftingTable = world.getNearestBlock(bot, 'crafting_table', craftingTableRange);
70
+ if (craftingTable) {
71
+ recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable);
72
+ placedTable = true;
73
+ }
74
+ }
75
+ else {
76
+ log(bot, `Crafting ${itemName} requires a crafting table.`)
77
+ return false;
78
+ }
79
+ }
80
+ else {
81
+ recipes = bot.recipesFor(mc.getItemId(itemName), null, 1, craftingTable);
82
+ }
83
+ }
84
+ if (!recipes || recipes.length === 0) {
85
+ log(bot, `You do not have the resources to craft a ${itemName}. It requires: ${Object.entries(mc.getItemCraftingRecipes(itemName)[0][0]).map(([key, value]) => `${key}: ${value}`).join(', ')}.`);
86
+ if (placedTable) {
87
+ await collectBlock(bot, 'crafting_table', 1);
88
+ }
89
+ return false;
90
+ }
91
+
92
+ if (craftingTable && bot.entity.position.distanceTo(craftingTable.position) > 4) {
93
+ await goToNearestBlock(bot, 'crafting_table', 4, craftingTableRange);
94
+ }
95
+
96
+ const recipe = recipes[0];
97
+ console.log('crafting...');
98
+ //Check that the agent has sufficient items to use the recipe `num` times.
99
+ const inventory = world.getInventoryCounts(bot); //Items in the agents inventory
100
+ const requiredIngredients = mc.ingredientsFromPrismarineRecipe(recipe); //Items required to use the recipe once.
101
+ const craftLimit = mc.calculateLimitingResource(inventory, requiredIngredients);
102
+
103
+ await bot.craft(recipe, Math.min(craftLimit.num, num), craftingTable);
104
+ if(craftLimit.num<num) log(bot, `Not enough ${craftLimit.limitingResource} to craft ${num}, crafted ${craftLimit.num}. You now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
105
+ else log(bot, `Successfully crafted ${itemName}, you now have ${world.getInventoryCounts(bot)[itemName]} ${itemName}.`);
106
+ if (placedTable) {
107
+ await collectBlock(bot, 'crafting_table', 1);
108
+ }
109
+
110
+ //Equip any armor the bot may have crafted.
111
+ //There is probablly a more efficient method than checking the entire inventory but this is all mineflayer-armor-manager provides. :P
112
+ bot.armorManager.equipAll();
113
+
114
+ return true;
115
+ }
116
+
117
+ export async function wait(bot, milliseconds) {
118
+ /**
119
+ * Waits for the given number of milliseconds.
120
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
121
+ * @param {number} milliseconds, the number of milliseconds to wait.
122
+ * @returns {Promise<boolean>} true if the wait was successful, false otherwise.
123
+ * @example
124
+ * await skills.wait(bot, 1000);
125
+ **/
126
+ // setTimeout is disabled to prevent unawaited code, so this is a safe alternative that enables interrupts
127
+ let timeLeft = milliseconds;
128
+ let startTime = Date.now();
129
+
130
+ while (timeLeft > 0) {
131
+ if (bot.interrupt_code) return false;
132
+
133
+ let waitTime = Math.min(2000, timeLeft);
134
+ await new Promise(resolve => setTimeout(resolve, waitTime));
135
+
136
+ let elapsed = Date.now() - startTime;
137
+ timeLeft = milliseconds - elapsed;
138
+ }
139
+ return true;
140
+ }
141
+
142
+ export async function smeltItem(bot, itemName, num=1) {
143
+ /**
144
+ * Puts 1 coal in furnace and smelts the given item name, waits until the furnace runs out of fuel or input items.
145
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
146
+ * @param {string} itemName, the item name to smelt. Ores must contain "raw" like raw_iron.
147
+ * @param {number} num, the number of items to smelt. Defaults to 1.
148
+ * @returns {Promise<boolean>} true if the item was smelted, false otherwise. Fail
149
+ * @example
150
+ * await skills.smeltItem(bot, "raw_iron");
151
+ * await skills.smeltItem(bot, "beef");
152
+ **/
153
+
154
+ if (!mc.isSmeltable(itemName)) {
155
+ log(bot, `Cannot smelt ${itemName}. Hint: make sure you are smelting the 'raw' item.`);
156
+ return false;
157
+ }
158
+
159
+ let placedFurnace = false;
160
+ let furnaceBlock = undefined;
161
+ const furnaceRange = 16;
162
+ furnaceBlock = world.getNearestBlock(bot, 'furnace', furnaceRange);
163
+ if (!furnaceBlock){
164
+ // Try to place furnace
165
+ let hasFurnace = world.getInventoryCounts(bot)['furnace'] > 0;
166
+ if (hasFurnace) {
167
+ let pos = world.getNearestFreeSpace(bot, 1, furnaceRange);
168
+ await placeBlock(bot, 'furnace', pos.x, pos.y, pos.z);
169
+ furnaceBlock = world.getNearestBlock(bot, 'furnace', furnaceRange);
170
+ placedFurnace = true;
171
+ }
172
+ }
173
+ if (!furnaceBlock){
174
+ log(bot, `There is no furnace nearby and you have no furnace.`)
175
+ return false;
176
+ }
177
+ if (bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
178
+ await goToNearestBlock(bot, 'furnace', 4, furnaceRange);
179
+ }
180
+ bot.modes.pause('unstuck');
181
+ await bot.lookAt(furnaceBlock.position);
182
+
183
+ console.log('smelting...');
184
+ const furnace = await bot.openFurnace(furnaceBlock);
185
+ // check if the furnace is already smelting something
186
+ let input_item = furnace.inputItem();
187
+ if (input_item && input_item.type !== mc.getItemId(itemName) && input_item.count > 0) {
188
+ // TODO: check if furnace is currently burning fuel. furnace.fuel is always null, I think there is a bug.
189
+ // This only checks if the furnace has an input item, but it may not be smelting it and should be cleared.
190
+ log(bot, `The furnace is currently smelting ${mc.getItemName(input_item.type)}.`);
191
+ if (placedFurnace)
192
+ await collectBlock(bot, 'furnace', 1);
193
+ return false;
194
+ }
195
+ // check if the bot has enough items to smelt
196
+ let inv_counts = world.getInventoryCounts(bot);
197
+ if (!inv_counts[itemName] || inv_counts[itemName] < num) {
198
+ log(bot, `You do not have enough ${itemName} to smelt.`);
199
+ if (placedFurnace)
200
+ await collectBlock(bot, 'furnace', 1);
201
+ return false;
202
+ }
203
+
204
+ // fuel the furnace
205
+ if (!furnace.fuelItem()) {
206
+ let fuel = mc.getSmeltingFuel(bot);
207
+ if (!fuel) {
208
+ log(bot, `You have no fuel to smelt ${itemName}, you need coal, charcoal, or wood.`);
209
+ if (placedFurnace)
210
+ await collectBlock(bot, 'furnace', 1);
211
+ return false;
212
+ }
213
+ log(bot, `Using ${fuel.name} as fuel.`);
214
+
215
+ const put_fuel = Math.ceil(num / mc.getFuelSmeltOutput(fuel.name));
216
+
217
+ if (fuel.count < put_fuel) {
218
+ log(bot, `You don't have enough ${fuel.name} to smelt ${num} ${itemName}; you need ${put_fuel}.`);
219
+ if (placedFurnace)
220
+ await collectBlock(bot, 'furnace', 1);
221
+ return false;
222
+ }
223
+ await furnace.putFuel(fuel.type, null, put_fuel);
224
+ log(bot, `Added ${put_fuel} ${mc.getItemName(fuel.type)} to furnace fuel.`);
225
+ console.log(`Added ${put_fuel} ${mc.getItemName(fuel.type)} to furnace fuel.`)
226
+ }
227
+ // put the items in the furnace
228
+ await furnace.putInput(mc.getItemId(itemName), null, num);
229
+ // wait for the items to smelt
230
+ let total = 0;
231
+ let smelted_item = null;
232
+ await new Promise(resolve => setTimeout(resolve, 200));
233
+ let last_collected = Date.now();
234
+ while (total < num) {
235
+ await new Promise(resolve => setTimeout(resolve, 1000));
236
+ if (furnace.outputItem()) {
237
+ smelted_item = await furnace.takeOutput();
238
+ if (smelted_item) {
239
+ total += smelted_item.count;
240
+ last_collected = Date.now();
241
+ }
242
+ }
243
+ if (Date.now() - last_collected > 11000) {
244
+ break; // if nothing has been collected in 11 seconds, stop
245
+ }
246
+ if (bot.interrupt_code) {
247
+ break;
248
+ }
249
+ }
250
+ // take all remaining in input/fuel slots
251
+ if (furnace.inputItem()) {
252
+ await furnace.takeInput();
253
+ }
254
+ if (furnace.fuelItem()) {
255
+ await furnace.takeFuel();
256
+ }
257
+
258
+ await bot.closeWindow(furnace);
259
+
260
+ if (placedFurnace) {
261
+ await collectBlock(bot, 'furnace', 1);
262
+ }
263
+ if (total === 0) {
264
+ log(bot, `Failed to smelt ${itemName}.`);
265
+ return false;
266
+ }
267
+ if (total < num) {
268
+ log(bot, `Only smelted ${total} ${mc.getItemName(smelted_item.type)}.`);
269
+ return false;
270
+ }
271
+ log(bot, `Successfully smelted ${itemName}, got ${total} ${mc.getItemName(smelted_item.type)}.`);
272
+ return true;
273
+ }
274
+
275
+ export async function clearNearestFurnace(bot) {
276
+ /**
277
+ * Clears the nearest furnace of all items.
278
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
279
+ * @returns {Promise<boolean>} true if the furnace was cleared, false otherwise.
280
+ * @example
281
+ * await skills.clearNearestFurnace(bot);
282
+ **/
283
+ let furnaceBlock = world.getNearestBlock(bot, 'furnace', 32);
284
+ if (!furnaceBlock) {
285
+ log(bot, `No furnace nearby to clear.`);
286
+ return false;
287
+ }
288
+ if (bot.entity.position.distanceTo(furnaceBlock.position) > 4) {
289
+ await goToNearestBlock(bot, 'furnace', 4, 32);
290
+ }
291
+
292
+ console.log('clearing furnace...');
293
+ const furnace = await bot.openFurnace(furnaceBlock);
294
+ console.log('opened furnace...')
295
+ // take the items out of the furnace
296
+ let smelted_item, intput_item, fuel_item;
297
+ if (furnace.outputItem())
298
+ smelted_item = await furnace.takeOutput();
299
+ if (furnace.inputItem())
300
+ intput_item = await furnace.takeInput();
301
+ if (furnace.fuelItem())
302
+ fuel_item = await furnace.takeFuel();
303
+ console.log(smelted_item, intput_item, fuel_item)
304
+ let smelted_name = smelted_item ? `${smelted_item.count} ${smelted_item.name}` : `0 smelted items`;
305
+ let input_name = intput_item ? `${intput_item.count} ${intput_item.name}` : `0 input items`;
306
+ let fuel_name = fuel_item ? `${fuel_item.count} ${fuel_item.name}` : `0 fuel items`;
307
+ log(bot, `Cleared furnace, received ${smelted_name}, ${input_name}, and ${fuel_name}.`);
308
+ return true;
309
+
310
+ }
311
+
312
+
313
+ export async function attackNearest(bot, mobType, kill=true) {
314
+ /**
315
+ * Attack mob of the given type.
316
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
317
+ * @param {string} mobType, the type of mob to attack.
318
+ * @param {boolean} kill, whether or not to continue attacking until the mob is dead. Defaults to true.
319
+ * @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
320
+ * @example
321
+ * await skills.attackNearest(bot, "zombie", true);
322
+ **/
323
+ bot.modes.pause('cowardice');
324
+ if (mobType === 'drowned' || mobType === 'cod' || mobType === 'salmon' || mobType === 'tropical_fish' || mobType === 'squid')
325
+ bot.modes.pause('self_preservation'); // so it can go underwater. TODO: have an drowning mode so we don't turn off all self_preservation
326
+ const mob = world.getNearbyEntities(bot, 24).find(entity => entity.name === mobType);
327
+ if (mob) {
328
+ return await attackEntity(bot, mob, kill);
329
+ }
330
+ log(bot, 'Could not find any '+mobType+' to attack.');
331
+ return false;
332
+ }
333
+
334
+ export async function attackEntity(bot, entity, kill=true) {
335
+ /**
336
+ * Attack mob of the given type.
337
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
338
+ * @param {Entity} entity, the entity to attack.
339
+ * @returns {Promise<boolean>} true if the entity was attacked, false if interrupted
340
+ * @example
341
+ * await skills.attackEntity(bot, entity);
342
+ **/
343
+
344
+ let pos = entity.position;
345
+ await equipHighestAttack(bot)
346
+
347
+ if (!kill) {
348
+ if (bot.entity.position.distanceTo(pos) > 5) {
349
+ console.log('moving to mob...')
350
+ await goToPosition(bot, pos.x, pos.y, pos.z);
351
+ }
352
+ console.log('attacking mob...')
353
+ await bot.attack(entity);
354
+ }
355
+ else {
356
+ bot.pvp.attack(entity);
357
+ while (world.getNearbyEntities(bot, 24).includes(entity)) {
358
+ await new Promise(resolve => setTimeout(resolve, 1000));
359
+ if (bot.interrupt_code) {
360
+ bot.pvp.stop();
361
+ return false;
362
+ }
363
+ }
364
+ log(bot, `Successfully killed ${entity.name}.`);
365
+ await pickupNearbyItems(bot);
366
+ return true;
367
+ }
368
+ }
369
+
370
+ export async function defendSelf(bot, range=9) {
371
+ /**
372
+ * Defend yourself from all nearby hostile mobs until there are no more.
373
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
374
+ * @param {number} range, the range to look for mobs. Defaults to 8.
375
+ * @returns {Promise<boolean>} true if the bot found any enemies and has killed them, false if no entities were found.
376
+ * @example
377
+ * await skills.defendSelf(bot);
378
+ * **/
379
+ bot.modes.pause('self_defense');
380
+ bot.modes.pause('cowardice');
381
+ let attacked = false;
382
+ let enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range);
383
+ while (enemy) {
384
+ await equipHighestAttack(bot);
385
+ if (bot.entity.position.distanceTo(enemy.position) >= 4 && enemy.name !== 'creeper' && enemy.name !== 'phantom') {
386
+ try {
387
+ bot.pathfinder.setMovements(new pf.Movements(bot));
388
+ await bot.pathfinder.goto(new pf.goals.GoalFollow(enemy, 3.5), true);
389
+ } catch (err) {/* might error if entity dies, ignore */}
390
+ }
391
+ if (bot.entity.position.distanceTo(enemy.position) <= 2) {
392
+ try {
393
+ bot.pathfinder.setMovements(new pf.Movements(bot));
394
+ let inverted_goal = new pf.goals.GoalInvert(new pf.goals.GoalFollow(enemy, 2));
395
+ await bot.pathfinder.goto(inverted_goal, true);
396
+ } catch (err) {/* might error if entity dies, ignore */}
397
+ }
398
+ bot.pvp.attack(enemy);
399
+ attacked = true;
400
+ await new Promise(resolve => setTimeout(resolve, 500));
401
+ enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), range);
402
+ if (bot.interrupt_code) {
403
+ bot.pvp.stop();
404
+ return false;
405
+ }
406
+ }
407
+ bot.pvp.stop();
408
+ if (attacked)
409
+ log(bot, `Successfully defended self.`);
410
+ else
411
+ log(bot, `No enemies nearby to defend self from.`);
412
+ return attacked;
413
+ }
414
+
415
+
416
+
417
+ export async function collectBlock(bot, blockType, num=1, exclude=null) {
418
+ /**
419
+ * Collect one of the given block type.
420
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
421
+ * @param {string} blockType, the type of block to collect.
422
+ * @param {number} num, the number of blocks to collect. Defaults to 1.
423
+ * @param {list} exclude, a list of positions to exclude from the search. Defaults to null.
424
+ * @returns {Promise<boolean>} true if the block was collected, false if the block type was not found.
425
+ * @example
426
+ * await skills.collectBlock(bot, "oak_log");
427
+ **/
428
+ if (num < 1) {
429
+ log(bot, `Invalid number of blocks to collect: ${num}.`);
430
+ return false;
431
+ }
432
+ let blocktypes = [blockType];
433
+ if (blockType === 'coal' || blockType === 'diamond' || blockType === 'emerald' || blockType === 'iron' || blockType === 'gold' || blockType === 'lapis_lazuli' || blockType === 'redstone')
434
+ blocktypes.push(blockType+'_ore');
435
+ if (blockType.endsWith('ore'))
436
+ blocktypes.push('deepslate_'+blockType);
437
+ if (blockType === 'dirt')
438
+ blocktypes.push('grass_block');
439
+ if (blockType === 'cobblestone')
440
+ blocktypes.push('stone');
441
+ const isLiquid = blockType === 'lava' || blockType === 'water';
442
+
443
+ let collected = 0;
444
+
445
+ const movements = new pf.Movements(bot);
446
+ movements.dontMineUnderFallingBlock = false;
447
+ movements.dontCreateFlow = true;
448
+
449
+ // Blocks to ignore safety for, usually next to lava/water
450
+ const unsafeBlocks = ['obsidian'];
451
+
452
+ for (let i=0; i<num; i++) {
453
+ let blocks = world.getNearestBlocksWhere(bot, block => {
454
+ if (!blocktypes.includes(block.name)) {
455
+ return false;
456
+ }
457
+ if (exclude) {
458
+ for (let position of exclude) {
459
+ if (block.position.x === position.x && block.position.y === position.y && block.position.z === position.z) {
460
+ return false;
461
+ }
462
+ }
463
+ }
464
+ if (isLiquid) {
465
+ // collect only source blocks
466
+ return block.metadata === 0;
467
+ }
468
+
469
+ return movements.safeToBreak(block) || unsafeBlocks.includes(block.name);
470
+ }, 64, 1);
471
+
472
+ if (blocks.length === 0) {
473
+ if (collected === 0)
474
+ log(bot, `No ${blockType} nearby to collect.`);
475
+ else
476
+ log(bot, `No more ${blockType} nearby to collect.`);
477
+ break;
478
+ }
479
+ const block = blocks[0];
480
+ await bot.tool.equipForBlock(block);
481
+ if (isLiquid) {
482
+ const bucket = bot.inventory.findInventoryItem('bucket');
483
+ if (!bucket) {
484
+ log(bot, `Don't have bucket to harvest ${blockType}.`);
485
+ return false;
486
+ }
487
+ await bot.equip(bucket, 'hand');
488
+ }
489
+ const itemId = bot.heldItem ? bot.heldItem.type : null
490
+ if (!block.canHarvest(itemId)) {
491
+ log(bot, `Don't have right tools to harvest ${blockType}.`);
492
+ return false;
493
+ }
494
+ try {
495
+ let success = false;
496
+ if (isLiquid) {
497
+ success = await useToolOnBlock(bot, 'bucket', block);
498
+ }
499
+ else if (mc.mustCollectManually(blockType)) {
500
+ await goToPosition(bot, block.position.x, block.position.y, block.position.z, 2);
501
+ await bot.dig(block);
502
+ await pickupNearbyItems(bot);
503
+ success = true;
504
+ }
505
+ else {
506
+ await bot.collectBlock.collect(block);
507
+ success = true;
508
+ }
509
+ if (success)
510
+ collected++;
511
+ await autoLight(bot);
512
+ }
513
+ catch (err) {
514
+ if (err.name === 'NoChests') {
515
+ log(bot, `Failed to collect ${blockType}: Inventory full, no place to deposit.`);
516
+ break;
517
+ }
518
+ else {
519
+ log(bot, `Failed to collect ${blockType}: ${err}.`);
520
+ continue;
521
+ }
522
+ }
523
+
524
+ if (bot.interrupt_code)
525
+ break;
526
+ }
527
+ log(bot, `Collected ${collected} ${blockType}.`);
528
+ return collected > 0;
529
+ }
530
+
531
+ export async function pickupNearbyItems(bot) {
532
+ /**
533
+ * Pick up all nearby items.
534
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
535
+ * @returns {Promise<boolean>} true if the items were picked up, false otherwise.
536
+ * @example
537
+ * await skills.pickupNearbyItems(bot);
538
+ **/
539
+ const distance = 8;
540
+ const getNearestItem = bot => bot.nearestEntity(entity => entity.name === 'item' && bot.entity.position.distanceTo(entity.position) < distance);
541
+ let nearestItem = getNearestItem(bot);
542
+ let pickedUp = 0;
543
+ while (nearestItem) {
544
+ let movements = new pf.Movements(bot);
545
+ movements.canDig = false;
546
+ bot.pathfinder.setMovements(movements);
547
+ await goToGoal(bot, new pf.goals.GoalFollow(nearestItem, 1));
548
+ await new Promise(resolve => setTimeout(resolve, 200));
549
+ let prev = nearestItem;
550
+ nearestItem = getNearestItem(bot);
551
+ if (prev === nearestItem) {
552
+ break;
553
+ }
554
+ pickedUp++;
555
+ }
556
+ log(bot, `Picked up ${pickedUp} items.`);
557
+ return true;
558
+ }
559
+
560
+
561
+ export async function breakBlockAt(bot, x, y, z) {
562
+ /**
563
+ * Break the block at the given position. Will use the bot's equipped item.
564
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
565
+ * @param {number} x, the x coordinate of the block to break.
566
+ * @param {number} y, the y coordinate of the block to break.
567
+ * @param {number} z, the z coordinate of the block to break.
568
+ * @returns {Promise<boolean>} true if the block was broken, false otherwise.
569
+ * @example
570
+ * let position = world.getPosition(bot);
571
+ * await skills.breakBlockAt(bot, position.x, position.y - 1, position.x);
572
+ **/
573
+ if (x == null || y == null || z == null) throw new Error('Invalid position to break block at.');
574
+ let block = bot.blockAt(Vec3(x, y, z));
575
+ if (block.name !== 'air' && block.name !== 'water' && block.name !== 'lava') {
576
+ if (bot.modes.isOn('cheat')) {
577
+ if (useDelay) { await new Promise(resolve => setTimeout(resolve, blockPlaceDelay)); }
578
+ let msg = '/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z) + ' air';
579
+ bot.chat(msg);
580
+ log(bot, `Used /setblock to break block at ${x}, ${y}, ${z}.`);
581
+ return true;
582
+ }
583
+
584
+ if (bot.entity.position.distanceTo(block.position) > 4.5) {
585
+ let pos = block.position;
586
+ let movements = new pf.Movements(bot);
587
+ movements.canPlaceOn = false;
588
+ movements.allow1by1towers = false;
589
+ bot.pathfinder.setMovements(movements);
590
+ await goToGoal(bot, new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
591
+ }
592
+ if (bot.game.gameMode !== 'creative') {
593
+ await bot.tool.equipForBlock(block);
594
+ const itemId = bot.heldItem ? bot.heldItem.type : null
595
+ if (!block.canHarvest(itemId)) {
596
+ log(bot, `Don't have right tools to break ${block.name}.`);
597
+ return false;
598
+ }
599
+ }
600
+ await bot.dig(block, true);
601
+ log(bot, `Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`);
602
+ }
603
+ else {
604
+ log(bot, `Skipping block at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)} because it is ${block.name}.`);
605
+ return false;
606
+ }
607
+ return true;
608
+ }
609
+
610
+
611
+ export async function placeBlock(bot, blockType, x, y, z, placeOn='bottom', dontCheat=false) {
612
+ /**
613
+ * Place the given block type at the given position. It will build off from any adjacent blocks. Will fail if there is a block in the way or nothing to build off of.
614
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
615
+ * @param {string} blockType, the type of block to place, which can be a block or item name.
616
+ * @param {number} x, the x coordinate of the block to place.
617
+ * @param {number} y, the y coordinate of the block to place.
618
+ * @param {number} z, the z coordinate of the block to place.
619
+ * @param {string} placeOn, the preferred side of the block to place on. Can be 'top', 'bottom', 'north', 'south', 'east', 'west', or 'side'. Defaults to bottom. Will place on first available side if not possible.
620
+ * @param {boolean} dontCheat, overrides cheat mode to place the block normally. Defaults to false.
621
+ * @returns {Promise<boolean>} true if the block was placed, false otherwise.
622
+ * @example
623
+ * let p = world.getPosition(bot);
624
+ * await skills.placeBlock(bot, "oak_log", p.x + 2, p.y, p.x);
625
+ * await skills.placeBlock(bot, "torch", p.x + 1, p.y, p.x, 'side');
626
+ **/
627
+ const target_dest = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z));
628
+
629
+ if (blockType === 'air') {
630
+ log(bot, `Placing air (removing block) at ${target_dest}.`);
631
+ return await breakBlockAt(bot, x, y, z);
632
+ }
633
+
634
+ if (bot.modes.isOn('cheat') && !dontCheat) {
635
+ if (bot.restrict_to_inventory) {
636
+ let block = bot.inventory.findInventoryItem(blockType);
637
+ if (!block) {
638
+ log(bot, `Cannot place ${blockType}, you are restricted to your current inventory.`);
639
+ return false;
640
+ }
641
+ }
642
+
643
+ // invert the facing direction
644
+ let face = placeOn === 'north' ? 'south' : placeOn === 'south' ? 'north' : placeOn === 'east' ? 'west' : 'east';
645
+ if (blockType.includes('torch') && placeOn !== 'bottom') {
646
+ // insert wall_ before torch
647
+ blockType = blockType.replace('torch', 'wall_torch');
648
+ if (placeOn !== 'side' && placeOn !== 'top') {
649
+ blockType += `[facing=${face}]`;
650
+ }
651
+ }
652
+ if (blockType.includes('button') || blockType === 'lever') {
653
+ if (placeOn === 'top') {
654
+ blockType += `[face=ceiling]`;
655
+ }
656
+ else if (placeOn === 'bottom') {
657
+ blockType += `[face=floor]`;
658
+ }
659
+ else {
660
+ blockType += `[facing=${face}]`;
661
+ }
662
+ }
663
+ if (blockType === 'ladder' || blockType === 'repeater' || blockType === 'comparator') {
664
+ blockType += `[facing=${face}]`;
665
+ }
666
+ if (blockType.includes('stairs')) {
667
+ blockType += `[facing=${face}]`;
668
+ }
669
+ if (useDelay) { await new Promise(resolve => setTimeout(resolve, blockPlaceDelay)); }
670
+ let msg = '/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z) + ' ' + blockType;
671
+ bot.chat(msg);
672
+ if (blockType.includes('door'))
673
+ if (useDelay) { await new Promise(resolve => setTimeout(resolve, blockPlaceDelay)); }
674
+ bot.chat('/setblock ' + Math.floor(x) + ' ' + Math.floor(y+1) + ' ' + Math.floor(z) + ' ' + blockType + '[half=upper]');
675
+ if (blockType.includes('bed'))
676
+ if (useDelay) { await new Promise(resolve => setTimeout(resolve, blockPlaceDelay)); }
677
+ bot.chat('/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z-1) + ' ' + blockType + '[part=head]');
678
+ log(bot, `Used /setblock to place ${blockType} at ${target_dest}.`);
679
+ return true;
680
+ }
681
+
682
+ let item_name = blockType;
683
+ if (item_name == "redstone_wire")
684
+ item_name = "redstone";
685
+ else if (item_name === 'water') {
686
+ item_name = 'water_bucket';
687
+ }
688
+ else if (item_name === 'lava') {
689
+ item_name = 'lava_bucket';
690
+ }
691
+ let block_item = bot.inventory.findInventoryItem(item_name);
692
+ if (!block_item && bot.game.gameMode === 'creative' && !bot.restrict_to_inventory) {
693
+ await bot.creative.setInventorySlot(36, mc.makeItem(item_name, 1)); // 36 is first hotbar slot
694
+ block_item = bot.inventory.findInventoryItem(item_name);
695
+ }
696
+ if (!block_item) {
697
+ log(bot, `Don't have any ${item_name} to place.`);
698
+ return false;
699
+ }
700
+
701
+ const targetBlock = bot.blockAt(target_dest);
702
+ if (targetBlock.name === blockType || (targetBlock.name === 'grass_block' && blockType === 'dirt')) {
703
+ log(bot, `${blockType} already at ${targetBlock.position}.`);
704
+ return false;
705
+ }
706
+ const empty_blocks = ['air', 'water', 'lava', 'grass', 'short_grass', 'tall_grass', 'snow', 'dead_bush', 'fern'];
707
+ if (!empty_blocks.includes(targetBlock.name)) {
708
+ log(bot, `${targetBlock.name} in the way at ${targetBlock.position}.`);
709
+ const removed = await breakBlockAt(bot, x, y, z);
710
+ if (!removed) {
711
+ log(bot, `Cannot place ${blockType} at ${targetBlock.position}: block in the way.`);
712
+ return false;
713
+ }
714
+ await new Promise(resolve => setTimeout(resolve, 200)); // wait for block to break
715
+ }
716
+ // get the buildoffblock and facevec based on whichever adjacent block is not empty
717
+ let buildOffBlock = null;
718
+ let faceVec = null;
719
+ const dir_map = {
720
+ 'top': Vec3(0, 1, 0),
721
+ 'bottom': Vec3(0, -1, 0),
722
+ 'north': Vec3(0, 0, -1),
723
+ 'south': Vec3(0, 0, 1),
724
+ 'east': Vec3(1, 0, 0),
725
+ 'west': Vec3(-1, 0, 0),
726
+ }
727
+ let dirs = [];
728
+ if (placeOn === 'side') {
729
+ dirs.push(dir_map['north'], dir_map['south'], dir_map['east'], dir_map['west']);
730
+ }
731
+ else if (dir_map[placeOn] !== undefined) {
732
+ dirs.push(dir_map[placeOn]);
733
+ }
734
+ else {
735
+ dirs.push(dir_map['bottom']);
736
+ log(bot, `Unknown placeOn value "${placeOn}". Defaulting to bottom.`);
737
+ }
738
+ dirs.push(...Object.values(dir_map).filter(d => !dirs.includes(d)));
739
+
740
+ for (let d of dirs) {
741
+ const block = bot.blockAt(target_dest.plus(d));
742
+ if (!empty_blocks.includes(block.name)) {
743
+ buildOffBlock = block;
744
+ faceVec = new Vec3(-d.x, -d.y, -d.z); // invert
745
+ break;
746
+ }
747
+ }
748
+ if (!buildOffBlock) {
749
+ log(bot, `Cannot place ${blockType} at ${targetBlock.position}: nothing to place on.`);
750
+ return false;
751
+ }
752
+
753
+ const pos = bot.entity.position;
754
+ const pos_above = pos.plus(Vec3(0,1,0));
755
+ const dont_move_for = ['torch', 'redstone_torch', 'redstone', 'lever', 'button', 'rail', 'detector_rail',
756
+ 'powered_rail', 'activator_rail', 'tripwire_hook', 'tripwire', 'water_bucket', 'string'];
757
+ if (!dont_move_for.includes(item_name) && (pos.distanceTo(targetBlock.position) < 1.1 || pos_above.distanceTo(targetBlock.position) < 1.1)) {
758
+ // too close
759
+ let goal = new pf.goals.GoalNear(targetBlock.position.x, targetBlock.position.y, targetBlock.position.z, 2);
760
+ let inverted_goal = new pf.goals.GoalInvert(goal);
761
+ bot.pathfinder.setMovements(new pf.Movements(bot));
762
+ await bot.pathfinder.goto(inverted_goal);
763
+ }
764
+ if (bot.entity.position.distanceTo(targetBlock.position) > 4.5) {
765
+ // too far
766
+ let pos = targetBlock.position;
767
+ let movements = new pf.Movements(bot);
768
+ bot.pathfinder.setMovements(movements);
769
+ await goToGoal(bot, new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
770
+ }
771
+
772
+ // will throw error if an entity is in the way, and sometimes even if the block was placed
773
+ try {
774
+ if (item_name.includes('bucket')) {
775
+ await useToolOnBlock(bot, item_name, buildOffBlock);
776
+ }
777
+ else {
778
+ await bot.equip(block_item, 'hand');
779
+ await bot.lookAt(buildOffBlock.position.offset(0.5, 0.5, 0.5));
780
+ await bot.placeBlock(buildOffBlock, faceVec);
781
+ log(bot, `Placed ${blockType} at ${target_dest}.`);
782
+ await new Promise(resolve => setTimeout(resolve, 200));
783
+ return true;
784
+ }
785
+ } catch (err) {
786
+ log(bot, `Failed to place ${blockType} at ${target_dest}.`);
787
+ return false;
788
+ }
789
+ }
790
+
791
+ export async function equip(bot, itemName) {
792
+ /**
793
+ * Equip the given item to the proper body part, like tools or armor.
794
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
795
+ * @param {string} itemName, the item or block name to equip.
796
+ * @returns {Promise<boolean>} true if the item was equipped, false otherwise.
797
+ * @example
798
+ * await skills.equip(bot, "iron_pickaxe");
799
+ **/
800
+ if (itemName === 'hand') {
801
+ await bot.unequip('hand');
802
+ log(bot, `Unequipped hand.`);
803
+ return true;
804
+ }
805
+ let item = bot.inventory.slots.find(slot => slot && slot.name === itemName);
806
+ if (!item) {
807
+ if (bot.game.gameMode === "creative") {
808
+ await bot.creative.setInventorySlot(36, mc.makeItem(itemName, 1));
809
+ item = bot.inventory.findInventoryItem(itemName);
810
+ }
811
+ else {
812
+ log(bot, `You do not have any ${itemName} to equip.`);
813
+ return false;
814
+ }
815
+ }
816
+ if (itemName.includes('leggings')) {
817
+ await bot.equip(item, 'legs');
818
+ }
819
+ else if (itemName.includes('boots')) {
820
+ await bot.equip(item, 'feet');
821
+ }
822
+ else if (itemName.includes('helmet')) {
823
+ await bot.equip(item, 'head');
824
+ }
825
+ else if (itemName.includes('chestplate') || itemName.includes('elytra')) {
826
+ await bot.equip(item, 'torso');
827
+ }
828
+ else if (itemName.includes('shield')) {
829
+ await bot.equip(item, 'off-hand');
830
+ }
831
+ else {
832
+ await bot.equip(item, 'hand');
833
+ }
834
+ log(bot, `Equipped ${itemName}.`);
835
+ return true;
836
+ }
837
+
838
+ export async function discard(bot, itemName, num=-1) {
839
+ /**
840
+ * Discard the given item.
841
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
842
+ * @param {string} itemName, the item or block name to discard.
843
+ * @param {number} num, the number of items to discard. Defaults to -1, which discards all items.
844
+ * @returns {Promise<boolean>} true if the item was discarded, false otherwise.
845
+ * @example
846
+ * await skills.discard(bot, "oak_log");
847
+ **/
848
+ let discarded = 0;
849
+ while (true) {
850
+ let item = bot.inventory.findInventoryItem(itemName);
851
+ if (!item) {
852
+ break;
853
+ }
854
+ let to_discard = num === -1 ? item.count : Math.min(num - discarded, item.count);
855
+ await bot.toss(item.type, null, to_discard);
856
+ discarded += to_discard;
857
+ if (num !== -1 && discarded >= num) {
858
+ break;
859
+ }
860
+ }
861
+ if (discarded === 0) {
862
+ log(bot, `You do not have any ${itemName} to discard.`);
863
+ return false;
864
+ }
865
+ log(bot, `Discarded ${discarded} ${itemName}.`);
866
+ return true;
867
+ }
868
+
869
+ export async function putInChest(bot, itemName, num=-1) {
870
+ /**
871
+ * Put the given item in the nearest chest.
872
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
873
+ * @param {string} itemName, the item or block name to put in the chest.
874
+ * @param {number} num, the number of items to put in the chest. Defaults to -1, which puts all items.
875
+ * @returns {Promise<boolean>} true if the item was put in the chest, false otherwise.
876
+ * @example
877
+ * await skills.putInChest(bot, "oak_log");
878
+ **/
879
+ let chest = world.getNearestBlock(bot, 'chest', 32);
880
+ if (!chest) {
881
+ log(bot, `Could not find a chest nearby.`);
882
+ return false;
883
+ }
884
+ let item = bot.inventory.findInventoryItem(itemName);
885
+ if (!item) {
886
+ log(bot, `You do not have any ${itemName} to put in the chest.`);
887
+ return false;
888
+ }
889
+ let to_put = num === -1 ? item.count : Math.min(num, item.count);
890
+ await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2);
891
+ const chestContainer = await bot.openContainer(chest);
892
+ await chestContainer.deposit(item.type, null, to_put);
893
+ await chestContainer.close();
894
+ log(bot, `Successfully put ${to_put} ${itemName} in the chest.`);
895
+ return true;
896
+ }
897
+
898
+ export async function takeFromChest(bot, itemName, num=-1) {
899
+ /**
900
+ * Take the given item from the nearest chest, potentially from multiple slots.
901
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
902
+ * @param {string} itemName, the item or block name to take from the chest.
903
+ * @param {number} num, the number of items to take from the chest. Defaults to -1, which takes all items.
904
+ * @returns {Promise<boolean>} true if the item was taken from the chest, false otherwise.
905
+ * @example
906
+ * await skills.takeFromChest(bot, "oak_log");
907
+ * **/
908
+ let chest = world.getNearestBlock(bot, 'chest', 32);
909
+ if (!chest) {
910
+ log(bot, `Could not find a chest nearby.`);
911
+ return false;
912
+ }
913
+ await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2);
914
+ const chestContainer = await bot.openContainer(chest);
915
+
916
+ // Find all matching items in the chest
917
+ let matchingItems = chestContainer.containerItems().filter(item => item.name === itemName);
918
+ if (matchingItems.length === 0) {
919
+ log(bot, `Could not find any ${itemName} in the chest.`);
920
+ await chestContainer.close();
921
+ return false;
922
+ }
923
+
924
+ let totalAvailable = matchingItems.reduce((sum, item) => sum + item.count, 0);
925
+ let remaining = num === -1 ? totalAvailable : Math.min(num, totalAvailable);
926
+ let totalTaken = 0;
927
+
928
+ // Take items from each slot until we've taken enough or run out
929
+ for (const item of matchingItems) {
930
+ if (remaining <= 0) break;
931
+
932
+ let toTakeFromSlot = Math.min(remaining, item.count);
933
+ await chestContainer.withdraw(item.type, null, toTakeFromSlot);
934
+
935
+ totalTaken += toTakeFromSlot;
936
+ remaining -= toTakeFromSlot;
937
+ }
938
+
939
+ await chestContainer.close();
940
+ log(bot, `Successfully took ${totalTaken} ${itemName} from the chest.`);
941
+ return totalTaken > 0;
942
+ }
943
+
944
+ export async function viewChest(bot) {
945
+ /**
946
+ * View the contents of the nearest chest.
947
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
948
+ * @returns {Promise<boolean>} true if the chest was viewed, false otherwise.
949
+ * @example
950
+ * await skills.viewChest(bot);
951
+ * **/
952
+ let chest = world.getNearestBlock(bot, 'chest', 32);
953
+ if (!chest) {
954
+ log(bot, `Could not find a chest nearby.`);
955
+ return false;
956
+ }
957
+ await goToPosition(bot, chest.position.x, chest.position.y, chest.position.z, 2);
958
+ const chestContainer = await bot.openContainer(chest);
959
+ let items = chestContainer.containerItems();
960
+ if (items.length === 0) {
961
+ log(bot, `The chest is empty.`);
962
+ }
963
+ else {
964
+ log(bot, `The chest contains:`);
965
+ for (let item of items) {
966
+ log(bot, `${item.count} ${item.name}`);
967
+ }
968
+ }
969
+ await chestContainer.close();
970
+ return true;
971
+ }
972
+
973
+ export async function consume(bot, itemName="") {
974
+ /**
975
+ * Eat/drink the given item.
976
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
977
+ * @param {string} itemName, the item to eat/drink.
978
+ * @returns {Promise<boolean>} true if the item was eaten, false otherwise.
979
+ * @example
980
+ * await skills.eat(bot, "apple");
981
+ **/
982
+ let item, name;
983
+ if (itemName) {
984
+ item = bot.inventory.findInventoryItem(itemName);
985
+ name = itemName;
986
+ }
987
+ if (!item) {
988
+ log(bot, `You do not have any ${name} to eat.`);
989
+ return false;
990
+ }
991
+ await bot.equip(item, 'hand');
992
+ await bot.consume();
993
+ log(bot, `Consumed ${item.name}.`);
994
+ return true;
995
+ }
996
+
997
+
998
+ export async function giveToPlayer(bot, itemType, username, num=1) {
999
+ /**
1000
+ * Give one of the specified item to the specified player
1001
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1002
+ * @param {string} itemType, the name of the item to give.
1003
+ * @param {string} username, the username of the player to give the item to.
1004
+ * @param {number} num, the number of items to give. Defaults to 1.
1005
+ * @returns {Promise<boolean>} true if the item was given, false otherwise.
1006
+ * @example
1007
+ * await skills.giveToPlayer(bot, "oak_log", "player1");
1008
+ **/
1009
+ if (bot.username === username) {
1010
+ log(bot, `You cannot give items to yourself.`);
1011
+ return false;
1012
+ }
1013
+ let player = bot.players[username].entity
1014
+ if (!player) {
1015
+ log(bot, `Could not find ${username}.`);
1016
+ return false;
1017
+ }
1018
+ await goToPlayer(bot, username, 3);
1019
+ // if we are 2 below the player
1020
+ log(bot, bot.entity.position.y, player.position.y);
1021
+ if (bot.entity.position.y < player.position.y - 1) {
1022
+ await goToPlayer(bot, username, 1);
1023
+ }
1024
+ // if we are too close, make some distance
1025
+ if (bot.entity.position.distanceTo(player.position) < 2) {
1026
+ let too_close = true;
1027
+ let start_moving_away = Date.now();
1028
+ await moveAwayFromEntity(bot, player, 2);
1029
+ while (too_close && !bot.interrupt_code) {
1030
+ await new Promise(resolve => setTimeout(resolve, 500));
1031
+ too_close = bot.entity.position.distanceTo(player.position) < 5;
1032
+ if (too_close) {
1033
+ await moveAwayFromEntity(bot, player, 5);
1034
+ }
1035
+ if (Date.now() - start_moving_away > 3000) {
1036
+ break;
1037
+ }
1038
+ }
1039
+ if (too_close) {
1040
+ log(bot, `Failed to give ${itemType} to ${username}, too close.`);
1041
+ return false;
1042
+ }
1043
+ }
1044
+
1045
+ await bot.lookAt(player.position);
1046
+ if (await discard(bot, itemType, num)) {
1047
+ let given = false;
1048
+ bot.once('playerCollect', (collector, collected) => {
1049
+ console.log(collected.name);
1050
+ if (collector.username === username) {
1051
+ log(bot, `${username} received ${itemType}.`);
1052
+ given = true;
1053
+ }
1054
+ });
1055
+ let start = Date.now();
1056
+ while (!given && !bot.interrupt_code) {
1057
+ await new Promise(resolve => setTimeout(resolve, 500));
1058
+ if (given) {
1059
+ return true;
1060
+ }
1061
+ if (Date.now() - start > 3000) {
1062
+ break;
1063
+ }
1064
+ }
1065
+ }
1066
+ log(bot, `Failed to give ${itemType} to ${username}, it was never received.`);
1067
+ return false;
1068
+ }
1069
+
1070
+ export async function goToGoal(bot, goal) {
1071
+ /**
1072
+ * Navigate to the given goal. Use doors and attempt minimally destructive movements.
1073
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1074
+ * @param {pf.goals.Goal} goal, the goal to navigate to.
1075
+ **/
1076
+
1077
+ const nonDestructiveMovements = new pf.Movements(bot);
1078
+ const dontBreakBlocks = ['glass', 'glass_pane'];
1079
+ for (let block of dontBreakBlocks) {
1080
+ nonDestructiveMovements.blocksCantBreak.add(mc.getBlockId(block));
1081
+ }
1082
+ nonDestructiveMovements.placeCost = 2;
1083
+ nonDestructiveMovements.digCost = 10;
1084
+
1085
+ const destructiveMovements = new pf.Movements(bot);
1086
+
1087
+ let final_movements = destructiveMovements;
1088
+
1089
+ const pathfind_timeout = 1000;
1090
+ if (await bot.pathfinder.getPathTo(nonDestructiveMovements, goal, pathfind_timeout).status === 'success') {
1091
+ final_movements = nonDestructiveMovements;
1092
+ log(bot, `Found non-destructive path.`);
1093
+ }
1094
+ else if (await bot.pathfinder.getPathTo(destructiveMovements, goal, pathfind_timeout).status === 'success') {
1095
+ log(bot, `Found destructive path.`);
1096
+ }
1097
+ else {
1098
+ log(bot, `Path not found, but attempting to navigate anyway using destructive movements.`);
1099
+ }
1100
+
1101
+ const doorCheckInterval = startDoorInterval(bot);
1102
+
1103
+ bot.pathfinder.setMovements(final_movements);
1104
+ try {
1105
+ await bot.pathfinder.goto(goal);
1106
+ clearInterval(doorCheckInterval);
1107
+ return true;
1108
+ } catch (err) {
1109
+ clearInterval(doorCheckInterval);
1110
+ // we need to catch so we can clean up the door check interval, then rethrow the error
1111
+ throw err;
1112
+ }
1113
+ }
1114
+
1115
+ let _doorInterval = null;
1116
+ function startDoorInterval(bot) {
1117
+ /**
1118
+ * Start helper interval that opens nearby doors if the bot is stuck.
1119
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1120
+ * @returns {number} the interval id.
1121
+ **/
1122
+ if (_doorInterval) {
1123
+ clearInterval(_doorInterval);
1124
+ }
1125
+ let prev_pos = bot.entity.position.clone();
1126
+ let prev_check = Date.now();
1127
+ let stuck_time = 0;
1128
+
1129
+
1130
+ const doorCheckInterval = setInterval(() => {
1131
+ const now = Date.now();
1132
+ if (bot.entity.position.distanceTo(prev_pos) >= 0.1) {
1133
+ stuck_time = 0;
1134
+ } else {
1135
+ stuck_time += now - prev_check;
1136
+ }
1137
+
1138
+ if (stuck_time > 1200) {
1139
+ // shuffle positions so we're not always opening the same door
1140
+ const positions = [
1141
+ bot.entity.position.clone(),
1142
+ bot.entity.position.offset(0, 0, 1),
1143
+ bot.entity.position.offset(0, 0, -1),
1144
+ bot.entity.position.offset(1, 0, 0),
1145
+ bot.entity.position.offset(-1, 0, 0),
1146
+ ]
1147
+ let elevated_positions = positions.map(position => position.offset(0, 1, 0));
1148
+ positions.push(...elevated_positions);
1149
+ positions.push(bot.entity.position.offset(0, 2, 0)); // above head
1150
+ positions.push(bot.entity.position.offset(0, -1, 0)); // below feet
1151
+
1152
+ let currentIndex = positions.length;
1153
+ while (currentIndex != 0) {
1154
+ let randomIndex = Math.floor(Math.random() * currentIndex);
1155
+ currentIndex--;
1156
+ [positions[currentIndex], positions[randomIndex]] = [
1157
+ positions[randomIndex], positions[currentIndex]];
1158
+ }
1159
+
1160
+ for (let position of positions) {
1161
+ let block = bot.blockAt(position);
1162
+ if (block && block.name &&
1163
+ !block.name.includes('iron') &&
1164
+ (block.name.includes('door') ||
1165
+ block.name.includes('fence_gate') ||
1166
+ block.name.includes('trapdoor')))
1167
+ {
1168
+ bot.activateBlock(block);
1169
+ break;
1170
+ }
1171
+ }
1172
+ stuck_time = 0;
1173
+ }
1174
+ prev_pos = bot.entity.position.clone();
1175
+ prev_check = now;
1176
+ }, 200);
1177
+ _doorInterval = doorCheckInterval;
1178
+ return doorCheckInterval;
1179
+ }
1180
+
1181
+ export async function goToPosition(bot, x, y, z, min_distance=2) {
1182
+ /**
1183
+ * Navigate to the given position.
1184
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1185
+ * @param {number} x, the x coordinate to navigate to. If null, the bot's current x coordinate will be used.
1186
+ * @param {number} y, the y coordinate to navigate to. If null, the bot's current y coordinate will be used.
1187
+ * @param {number} z, the z coordinate to navigate to. If null, the bot's current z coordinate will be used.
1188
+ * @param {number} distance, the distance to keep from the position. Defaults to 2.
1189
+ * @returns {Promise<boolean>} true if the position was reached, false otherwise.
1190
+ * @example
1191
+ * let position = world.world.getNearestBlock(bot, "oak_log", 64).position;
1192
+ * await skills.goToPosition(bot, position.x, position.y, position.x + 20);
1193
+ **/
1194
+ if (x == null || y == null || z == null) {
1195
+ log(bot, `Missing coordinates, given x:${x} y:${y} z:${z}`);
1196
+ return false;
1197
+ }
1198
+ if (bot.modes.isOn('cheat')) {
1199
+ bot.chat('/tp @s ' + x + ' ' + y + ' ' + z);
1200
+ log(bot, `Teleported to ${x}, ${y}, ${z}.`);
1201
+ return true;
1202
+ }
1203
+
1204
+ const checkDigProgress = () => {
1205
+ if (bot.targetDigBlock) {
1206
+ const targetBlock = bot.targetDigBlock;
1207
+ const itemId = bot.heldItem ? bot.heldItem.type : null;
1208
+ if (!targetBlock.canHarvest(itemId)) {
1209
+ log(bot, `Pathfinding stopped: Cannot break ${targetBlock.name} with current tools.`);
1210
+ bot.pathfinder.stop();
1211
+ bot.stopDigging();
1212
+ }
1213
+ }
1214
+ };
1215
+
1216
+ const progressInterval = setInterval(checkDigProgress, 1000);
1217
+
1218
+ try {
1219
+ await goToGoal(bot, new pf.goals.GoalNear(x, y, z, min_distance));
1220
+ clearInterval(progressInterval);
1221
+ const distance = bot.entity.position.distanceTo(new Vec3(x, y, z));
1222
+ if (distance <= min_distance+1) {
1223
+ log(bot, `You have reached at ${x}, ${y}, ${z}.`);
1224
+ return true;
1225
+ }
1226
+ else {
1227
+ log(bot, `Unable to reach ${x}, ${y}, ${z}, you are ${Math.round(distance)} blocks away.`);
1228
+ return false;
1229
+ }
1230
+ } catch (err) {
1231
+ log(bot, `Pathfinding stopped: ${err.message}.`);
1232
+ clearInterval(progressInterval);
1233
+ return false;
1234
+ }
1235
+ }
1236
+
1237
+ export async function goToNearestBlock(bot, blockType, min_distance=2, range=64) {
1238
+ /**
1239
+ * Navigate to the nearest block of the given type.
1240
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1241
+ * @param {string} blockType, the type of block to navigate to.
1242
+ * @param {number} min_distance, the distance to keep from the block. Defaults to 2.
1243
+ * @param {number} range, the range to look for the block. Defaults to 64.
1244
+ * @returns {Promise<boolean>} true if the block was reached, false otherwise.
1245
+ * @example
1246
+ * await skills.goToNearestBlock(bot, "oak_log", 64, 2);
1247
+ * **/
1248
+ const MAX_RANGE = 512;
1249
+ if (range > MAX_RANGE) {
1250
+ log(bot, `Maximum search range capped at ${MAX_RANGE}. `);
1251
+ range = MAX_RANGE;
1252
+ }
1253
+ let block = null;
1254
+ if (blockType === 'water' || blockType === 'lava') {
1255
+ let blocks = world.getNearestBlocksWhere(bot, block => block.name === blockType && block.metadata === 0, range, 1);
1256
+ if (blocks.length === 0) {
1257
+ log(bot, `Could not find any source ${blockType} in ${range} blocks, looking for uncollectable flowing instead...`);
1258
+ blocks = world.getNearestBlocksWhere(bot, block => block.name === blockType, range, 1);
1259
+ }
1260
+ block = blocks[0];
1261
+ }
1262
+ else {
1263
+ block = world.getNearestBlock(bot, blockType, range);
1264
+ }
1265
+ if (!block) {
1266
+ log(bot, `Could not find any ${blockType} in ${range} blocks.`);
1267
+ return false;
1268
+ }
1269
+ log(bot, `Found ${blockType} at ${block.position}. Navigating...`);
1270
+ await goToPosition(bot, block.position.x, block.position.y, block.position.z, min_distance);
1271
+ return true;
1272
+ }
1273
+
1274
+ export async function goToNearestEntity(bot, entityType, min_distance=2, range=64) {
1275
+ /**
1276
+ * Navigate to the nearest entity of the given type.
1277
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1278
+ * @param {string} entityType, the type of entity to navigate to.
1279
+ * @param {number} min_distance, the distance to keep from the entity. Defaults to 2.
1280
+ * @param {number} range, the range to look for the entity. Defaults to 64.
1281
+ * @returns {Promise<boolean>} true if the entity was reached, false otherwise.
1282
+ **/
1283
+ let entity = world.getNearestEntityWhere(bot, entity => entity.name === entityType, range);
1284
+ if (!entity) {
1285
+ log(bot, `Could not find any ${entityType} in ${range} blocks.`);
1286
+ return false;
1287
+ }
1288
+ let distance = bot.entity.position.distanceTo(entity.position);
1289
+ log(bot, `Found ${entityType} ${distance} blocks away.`);
1290
+ await goToPosition(bot, entity.position.x, entity.position.y, entity.position.z, min_distance);
1291
+ return true;
1292
+ }
1293
+
1294
+ export async function goToPlayer(bot, username, distance=3) {
1295
+ /**
1296
+ * Navigate to the given player.
1297
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1298
+ * @param {string} username, the username of the player to navigate to.
1299
+ * @param {number} distance, the goal distance to the player.
1300
+ * @returns {Promise<boolean>} true if the player was found, false otherwise.
1301
+ * @example
1302
+ * await skills.goToPlayer(bot, "player");
1303
+ **/
1304
+ if (bot.username === username) {
1305
+ log(bot, `You are already at ${username}.`);
1306
+ return true;
1307
+ }
1308
+ if (bot.modes.isOn('cheat')) {
1309
+ bot.chat('/tp @s ' + username);
1310
+ log(bot, `Teleported to ${username}.`);
1311
+ return true;
1312
+ }
1313
+
1314
+ bot.modes.pause('self_defense');
1315
+ bot.modes.pause('cowardice');
1316
+ let player = bot.players[username].entity
1317
+ if (!player) {
1318
+ log(bot, `Could not find ${username}.`);
1319
+ return false;
1320
+ }
1321
+
1322
+ distance = Math.max(distance, 0.5);
1323
+ const goal = new pf.goals.GoalFollow(player, distance);
1324
+
1325
+ await goToGoal(bot, goal, true);
1326
+
1327
+ log(bot, `You have reached ${username}.`);
1328
+ }
1329
+
1330
+
1331
+ export async function followPlayer(bot, username, distance=4) {
1332
+ /**
1333
+ * Follow the given player endlessly. Will not return until the code is manually stopped.
1334
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1335
+ * @param {string} username, the username of the player to follow.
1336
+ * @returns {Promise<boolean>} true if the player was found, false otherwise.
1337
+ * @example
1338
+ * await skills.followPlayer(bot, "player");
1339
+ **/
1340
+ let player = bot.players[username].entity
1341
+ if (!player)
1342
+ return false;
1343
+
1344
+ const move = new pf.Movements(bot);
1345
+ move.digCost = 10;
1346
+ bot.pathfinder.setMovements(move);
1347
+ let doorCheckInterval = startDoorInterval(bot);
1348
+
1349
+ bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, distance), true);
1350
+ log(bot, `You are now actively following player ${username}.`);
1351
+
1352
+
1353
+ while (!bot.interrupt_code) {
1354
+ await new Promise(resolve => setTimeout(resolve, 500));
1355
+ // in cheat mode, if the distance is too far, teleport to the player
1356
+ const distance_from_player = bot.entity.position.distanceTo(player.position);
1357
+
1358
+ const teleport_distance = 100;
1359
+ const ignore_modes_distance = 30;
1360
+ const nearby_distance = distance + 2;
1361
+
1362
+ if (distance_from_player > teleport_distance && bot.modes.isOn('cheat')) {
1363
+ // teleport with cheat mode
1364
+ await goToPlayer(bot, username);
1365
+ }
1366
+ else if (distance_from_player > ignore_modes_distance) {
1367
+ // these modes slow down the bot, and we want to catch up
1368
+ bot.modes.pause('item_collecting');
1369
+ bot.modes.pause('hunting');
1370
+ bot.modes.pause('torch_placing');
1371
+ }
1372
+ else if (distance_from_player <= ignore_modes_distance) {
1373
+ bot.modes.unpause('item_collecting');
1374
+ bot.modes.unpause('hunting');
1375
+ bot.modes.unpause('torch_placing');
1376
+ }
1377
+
1378
+ if (distance_from_player <= nearby_distance) {
1379
+ clearInterval(doorCheckInterval);
1380
+ doorCheckInterval = null;
1381
+ bot.modes.pause('unstuck');
1382
+ bot.modes.pause('elbow_room');
1383
+ }
1384
+ else {
1385
+ if (!doorCheckInterval) {
1386
+ doorCheckInterval = startDoorInterval(bot);
1387
+ }
1388
+ bot.modes.unpause('unstuck');
1389
+ bot.modes.unpause('elbow_room');
1390
+ }
1391
+ }
1392
+ clearInterval(doorCheckInterval);
1393
+ return true;
1394
+ }
1395
+
1396
+
1397
+ export async function moveAway(bot, distance) {
1398
+ /**
1399
+ * Move away from current position in any direction.
1400
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1401
+ * @param {number} distance, the distance to move away.
1402
+ * @returns {Promise<boolean>} true if the bot moved away, false otherwise.
1403
+ * @example
1404
+ * await skills.moveAway(bot, 8);
1405
+ **/
1406
+ const pos = bot.entity.position;
1407
+ let goal = new pf.goals.GoalNear(pos.x, pos.y, pos.z, distance);
1408
+ let inverted_goal = new pf.goals.GoalInvert(goal);
1409
+ bot.pathfinder.setMovements(new pf.Movements(bot));
1410
+
1411
+ if (bot.modes.isOn('cheat')) {
1412
+ const move = new pf.Movements(bot);
1413
+ const path = await bot.pathfinder.getPathTo(move, inverted_goal, 10000);
1414
+ let last_move = path.path[path.path.length-1];
1415
+ if (last_move) {
1416
+ let x = Math.floor(last_move.x);
1417
+ let y = Math.floor(last_move.y);
1418
+ let z = Math.floor(last_move.z);
1419
+ bot.chat('/tp @s ' + x + ' ' + y + ' ' + z);
1420
+ return true;
1421
+ }
1422
+ }
1423
+
1424
+ await goToGoal(bot, inverted_goal);
1425
+ let new_pos = bot.entity.position;
1426
+ log(bot, `Moved away from ${pos.floored()} to ${new_pos.floored()}.`);
1427
+ return true;
1428
+ }
1429
+
1430
+ export async function moveAwayFromEntity(bot, entity, distance=16) {
1431
+ /**
1432
+ * Move away from the given entity.
1433
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1434
+ * @param {Entity} entity, the entity to move away from.
1435
+ * @param {number} distance, the distance to move away.
1436
+ * @returns {Promise<boolean>} true if the bot moved away, false otherwise.
1437
+ **/
1438
+ let goal = new pf.goals.GoalFollow(entity, distance);
1439
+ let inverted_goal = new pf.goals.GoalInvert(goal);
1440
+ bot.pathfinder.setMovements(new pf.Movements(bot));
1441
+ await bot.pathfinder.goto(inverted_goal);
1442
+ return true;
1443
+ }
1444
+
1445
+ export async function avoidEnemies(bot, distance=16) {
1446
+ /**
1447
+ * Move a given distance away from all nearby enemy mobs.
1448
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1449
+ * @param {number} distance, the distance to move away.
1450
+ * @returns {Promise<boolean>} true if the bot moved away, false otherwise.
1451
+ * @example
1452
+ * await skills.avoidEnemies(bot, 8);
1453
+ **/
1454
+ bot.modes.pause('self_preservation'); // prevents damage-on-low-health from interrupting the bot
1455
+ let enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), distance);
1456
+ while (enemy) {
1457
+ const follow = new pf.goals.GoalFollow(enemy, distance+1); // move a little further away
1458
+ const inverted_goal = new pf.goals.GoalInvert(follow);
1459
+ bot.pathfinder.setMovements(new pf.Movements(bot));
1460
+ bot.pathfinder.setGoal(inverted_goal, true);
1461
+ await new Promise(resolve => setTimeout(resolve, 500));
1462
+ enemy = world.getNearestEntityWhere(bot, entity => mc.isHostile(entity), distance);
1463
+ if (bot.interrupt_code) {
1464
+ break;
1465
+ }
1466
+ if (enemy && bot.entity.position.distanceTo(enemy.position) < 3) {
1467
+ await attackEntity(bot, enemy, false);
1468
+ }
1469
+ }
1470
+ bot.pathfinder.stop();
1471
+ log(bot, `Moved ${distance} away from enemies.`);
1472
+ return true;
1473
+ }
1474
+
1475
+ export async function stay(bot, seconds=30) {
1476
+ /**
1477
+ * Stay in the current position until interrupted. Disables all modes.
1478
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1479
+ * @param {number} seconds, the number of seconds to stay. Defaults to 30. -1 for indefinite.
1480
+ * @returns {Promise<boolean>} true if the bot stayed, false otherwise.
1481
+ * @example
1482
+ * await skills.stay(bot);
1483
+ **/
1484
+ bot.modes.pause('self_preservation');
1485
+ bot.modes.pause('unstuck');
1486
+ bot.modes.pause('cowardice');
1487
+ bot.modes.pause('self_defense');
1488
+ bot.modes.pause('hunting');
1489
+ bot.modes.pause('torch_placing');
1490
+ bot.modes.pause('item_collecting');
1491
+ let start = Date.now();
1492
+ while (!bot.interrupt_code && (seconds === -1 || Date.now() - start < seconds*1000)) {
1493
+ await new Promise(resolve => setTimeout(resolve, 500));
1494
+ }
1495
+ log(bot, `Stayed for ${(Date.now() - start)/1000} seconds.`);
1496
+ return true;
1497
+ }
1498
+
1499
+ export async function useDoor(bot, door_pos=null) {
1500
+ /**
1501
+ * Use the door at the given position.
1502
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1503
+ * @param {Vec3} door_pos, the position of the door to use. If null, the nearest door will be used.
1504
+ * @returns {Promise<boolean>} true if the door was used, false otherwise.
1505
+ * @example
1506
+ * let door = world.getNearestBlock(bot, "oak_door", 16).position;
1507
+ * await skills.useDoor(bot, door);
1508
+ **/
1509
+ if (!door_pos) {
1510
+ for (let door_type of ['oak_door', 'spruce_door', 'birch_door', 'jungle_door', 'acacia_door', 'dark_oak_door',
1511
+ 'mangrove_door', 'cherry_door', 'bamboo_door', 'crimson_door', 'warped_door']) {
1512
+ door_pos = world.getNearestBlock(bot, door_type, 16).position;
1513
+ if (door_pos) break;
1514
+ }
1515
+ } else {
1516
+ door_pos = Vec3(door_pos.x, door_pos.y, door_pos.z);
1517
+ }
1518
+ if (!door_pos) {
1519
+ log(bot, `Could not find a door to use.`);
1520
+ return false;
1521
+ }
1522
+
1523
+ bot.pathfinder.setGoal(new pf.goals.GoalNear(door_pos.x, door_pos.y, door_pos.z, 1));
1524
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1525
+ while (bot.pathfinder.isMoving()) {
1526
+ await new Promise((resolve) => setTimeout(resolve, 100));
1527
+ }
1528
+
1529
+ let door_block = bot.blockAt(door_pos);
1530
+ await bot.lookAt(door_pos);
1531
+ if (!door_block._properties.open)
1532
+ await bot.activateBlock(door_block);
1533
+
1534
+ bot.setControlState("forward", true);
1535
+ await new Promise((resolve) => setTimeout(resolve, 600));
1536
+ bot.setControlState("forward", false);
1537
+ await bot.activateBlock(door_block);
1538
+
1539
+ log(bot, `Used door at ${door_pos}.`);
1540
+ return true;
1541
+ }
1542
+
1543
+ export async function goToBed(bot) {
1544
+ /**
1545
+ * Sleep in the nearest bed.
1546
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1547
+ * @returns {Promise<boolean>} true if the bed was found, false otherwise.
1548
+ * @example
1549
+ * await skills.goToBed(bot);
1550
+ **/
1551
+ const beds = bot.findBlocks({
1552
+ matching: (block) => {
1553
+ return block.name.includes('bed');
1554
+ },
1555
+ maxDistance: 32,
1556
+ count: 1
1557
+ });
1558
+ if (beds.length === 0) {
1559
+ log(bot, `Could not find a bed to sleep in.`);
1560
+ return false;
1561
+ }
1562
+ let loc = beds[0];
1563
+ await goToPosition(bot, loc.x, loc.y, loc.z);
1564
+ const bed = bot.blockAt(loc);
1565
+ await bot.sleep(bed);
1566
+ log(bot, `You are in bed.`);
1567
+ bot.modes.pause('unstuck');
1568
+ while (bot.isSleeping) {
1569
+ await new Promise(resolve => setTimeout(resolve, 500));
1570
+ }
1571
+ log(bot, `You have woken up.`);
1572
+ return true;
1573
+ }
1574
+
1575
+ export async function tillAndSow(bot, x, y, z, seedType=null) {
1576
+ /**
1577
+ * Till the ground at the given position and plant the given seed type.
1578
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1579
+ * @param {number} x, the x coordinate to till.
1580
+ * @param {number} y, the y coordinate to till.
1581
+ * @param {number} z, the z coordinate to till.
1582
+ * @param {string} plantType, the type of plant to plant. Defaults to none, which will only till the ground.
1583
+ * @returns {Promise<boolean>} true if the ground was tilled, false otherwise.
1584
+ * @example
1585
+ * let position = world.getPosition(bot);
1586
+ * await skills.tillAndSow(bot, position.x, position.y - 1, position.x, "wheat");
1587
+ **/
1588
+ let pos = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z));
1589
+ let block = bot.blockAt(pos);
1590
+ log(bot, `Planting ${seedType} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`);
1591
+
1592
+ if (bot.modes.isOn('cheat')) {
1593
+ let to_remove = ['_seed', '_seeds'];
1594
+ for (let remove of to_remove) {
1595
+ if (seedType.endsWith(remove)) {
1596
+ seedType = seedType.replace(remove, '');
1597
+ }
1598
+ }
1599
+ placeBlock(bot, 'farmland', x, y, z);
1600
+ placeBlock(bot, seedType, x, y+1, z);
1601
+ return true;
1602
+ }
1603
+
1604
+ if (block.name !== 'grass_block' && block.name !== 'dirt' && block.name !== 'farmland') {
1605
+ log(bot, `Cannot till ${block.name}, must be grass_block or dirt.`);
1606
+ return false;
1607
+ }
1608
+ let above = bot.blockAt(new Vec3(x, y+1, z));
1609
+ if (above.name !== 'air') {
1610
+ if (block.name === 'farmland') {
1611
+ log(bot, `Land is already farmed with ${above.name}.`);
1612
+ return true;
1613
+ }
1614
+ let broken = await breakBlockAt(bot, x, y+1, z);
1615
+ if (!broken) {
1616
+ log(bot, `Cannot cannot break above block to till.`);
1617
+ return false;
1618
+ }
1619
+ }
1620
+ // if distance is too far, move to the block
1621
+ if (bot.entity.position.distanceTo(block.position) > 4.5) {
1622
+ let pos = block.position;
1623
+ bot.pathfinder.setMovements(new pf.Movements(bot));
1624
+ await goToGoal(bot, new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
1625
+ }
1626
+ if (block.name !== 'farmland') {
1627
+ let hoe = bot.inventory.items().find(item => item.name.includes('hoe'));
1628
+ let to_equip = hoe?.name || 'diamond_hoe';
1629
+ if (!await equip(bot, to_equip)) {
1630
+ log(bot, `Cannot till, no hoes.`);
1631
+ return false;
1632
+ }
1633
+ await bot.activateBlock(block);
1634
+ log(bot, `Tilled block x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`);
1635
+ }
1636
+
1637
+ if (seedType) {
1638
+ if (seedType.endsWith('seed') && !seedType.endsWith('seeds'))
1639
+ seedType += 's'; // fixes common mistake
1640
+ let equipped_seeds = await equip(bot, seedType);
1641
+ if (!equipped_seeds) {
1642
+ log(bot, `No ${seedType} to plant.`);
1643
+ return false;
1644
+ }
1645
+
1646
+ await bot.activateBlock(block);
1647
+ log(bot, `Planted ${seedType} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`);
1648
+ }
1649
+ return true;
1650
+ }
1651
+
1652
+ export async function activateNearestBlock(bot, type) {
1653
+ /**
1654
+ * Activate the nearest block of the given type.
1655
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1656
+ * @param {string} type, the type of block to activate.
1657
+ * @returns {Promise<boolean>} true if the block was activated, false otherwise.
1658
+ * @example
1659
+ * await skills.activateNearestBlock(bot, "lever");
1660
+ * **/
1661
+ let block = world.getNearestBlock(bot, type, 16);
1662
+ if (!block) {
1663
+ log(bot, `Could not find any ${type} to activate.`);
1664
+ return false;
1665
+ }
1666
+ if (bot.entity.position.distanceTo(block.position) > 4.5) {
1667
+ let pos = block.position;
1668
+ bot.pathfinder.setMovements(new pf.Movements(bot));
1669
+ await goToGoal(bot, new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4));
1670
+ }
1671
+ await bot.activateBlock(block);
1672
+ log(bot, `Activated ${type} at x:${block.position.x.toFixed(1)}, y:${block.position.y.toFixed(1)}, z:${block.position.z.toFixed(1)}.`);
1673
+ return true;
1674
+ }
1675
+
1676
+ /**
1677
+ * Helper function to find and navigate to a villager for trading
1678
+ * @param {MinecraftBot} bot - reference to the minecraft bot
1679
+ * @param {number} id - the entity id of the villager
1680
+ * @returns {Promise<Object|null>} the villager entity if found and reachable, null otherwise
1681
+ */
1682
+ async function findAndGoToVillager(bot, id) {
1683
+ id = id+"";
1684
+ const entity = bot.entities[id];
1685
+
1686
+ if (!entity) {
1687
+ log(bot, `Cannot find villager with id ${id}`);
1688
+ let entities = world.getNearbyEntities(bot, 16);
1689
+ let villager_list = "Available villagers:\n";
1690
+ for (let entity of entities) {
1691
+ if (entity.name === 'villager') {
1692
+ if (entity.metadata && entity.metadata[16] === 1) {
1693
+ villager_list += `${entity.id}: baby villager\n`;
1694
+ } else {
1695
+ const profession = world.getVillagerProfession(entity);
1696
+ villager_list += `${entity.id}: ${profession}\n`;
1697
+ }
1698
+ }
1699
+ }
1700
+ if (villager_list === "Available villagers:\n") {
1701
+ log(bot, "No villagers found nearby.");
1702
+ return null;
1703
+ }
1704
+ log(bot, villager_list);
1705
+ return null;
1706
+ }
1707
+
1708
+ if (entity.entityType !== bot.registry.entitiesByName.villager.id) {
1709
+ log(bot, 'Entity is not a villager');
1710
+ return null;
1711
+ }
1712
+
1713
+ if (entity.metadata && entity.metadata[16] === 1) {
1714
+ log(bot, 'This is either a baby villager or a villager with no job - neither can trade');
1715
+ return null;
1716
+ }
1717
+
1718
+ const distance = bot.entity.position.distanceTo(entity.position);
1719
+ if (distance > 4) {
1720
+ log(bot, `Villager is ${distance.toFixed(1)} blocks away, moving closer...`);
1721
+ try {
1722
+ bot.modes.pause('unstuck');
1723
+ const goal = new pf.goals.GoalFollow(entity, 2);
1724
+ await goToGoal(bot, goal);
1725
+
1726
+
1727
+ log(bot, 'Successfully reached villager');
1728
+ } catch (err) {
1729
+ log(bot, 'Failed to reach villager - pathfinding error or villager moved');
1730
+ console.log(err);
1731
+ return null;
1732
+ } finally {
1733
+ bot.modes.unpause('unstuck');
1734
+ }
1735
+ }
1736
+
1737
+ return entity;
1738
+ }
1739
+
1740
+ /**
1741
+ * Show available trades for a specified villager
1742
+ * @param {MinecraftBot} bot - reference to the minecraft bot
1743
+ * @param {number} id - the entity id of the villager to show trades for
1744
+ * @returns {Promise<boolean>} true if trades were shown successfully, false otherwise
1745
+ * @example
1746
+ * await skills.showVillagerTrades(bot, "123");
1747
+ */
1748
+ export async function showVillagerTrades(bot, id) {
1749
+ const villagerEntity = await findAndGoToVillager(bot, id);
1750
+ if (!villagerEntity) {
1751
+ return false;
1752
+ }
1753
+
1754
+ try {
1755
+ const villager = await bot.openVillager(villagerEntity);
1756
+
1757
+ if (!villager.trades || villager.trades.length === 0) {
1758
+ log(bot, 'This villager has no trades available - might be sleeping, a baby, or jobless');
1759
+ villager.close();
1760
+ return false;
1761
+ }
1762
+
1763
+ log(bot, `Villager has ${villager.trades.length} available trades:`);
1764
+ stringifyTrades(bot, villager.trades).forEach((trade, i) => {
1765
+ const tradeInfo = `${i + 1}: ${trade}`;
1766
+ console.log(tradeInfo);
1767
+ log(bot, tradeInfo);
1768
+ });
1769
+
1770
+ villager.close();
1771
+ return true;
1772
+ } catch (err) {
1773
+ log(bot, 'Failed to open villager trading interface - they might be sleeping, a baby, or jobless');
1774
+ console.log('Villager trading error:', err.message);
1775
+ return false;
1776
+ }
1777
+ }
1778
+
1779
+ /**
1780
+ * Trade with a specified villager
1781
+ * @param {MinecraftBot} bot - reference to the minecraft bot
1782
+ * @param {number} id - the entity id of the villager to trade with
1783
+ * @param {number} index - the index (1-based) of the trade to execute
1784
+ * @param {number} count - how many times to execute the trade (optional)
1785
+ * @returns {Promise<boolean>} true if trade was successful, false otherwise
1786
+ * @example
1787
+ * await skills.tradeWithVillager(bot, "123", "1", "2");
1788
+ */
1789
+ export async function tradeWithVillager(bot, id, index, count) {
1790
+ const villagerEntity = await findAndGoToVillager(bot, id);
1791
+ if (!villagerEntity) {
1792
+ return false;
1793
+ }
1794
+
1795
+ try {
1796
+ const villager = await bot.openVillager(villagerEntity);
1797
+
1798
+ if (!villager.trades || villager.trades.length === 0) {
1799
+ log(bot, 'This villager has no trades available - might be sleeping, a baby, or jobless');
1800
+ villager.close();
1801
+ return false;
1802
+ }
1803
+
1804
+ const tradeIndex = parseInt(index) - 1; // Convert to 0-based index
1805
+ const trade = villager.trades[tradeIndex];
1806
+
1807
+ if (!trade) {
1808
+ log(bot, `Trade ${index} not found. This villager has ${villager.trades.length} trades available.`);
1809
+ villager.close();
1810
+ return false;
1811
+ }
1812
+
1813
+ if (trade.disabled) {
1814
+ log(bot, `Trade ${index} is currently disabled`);
1815
+ villager.close();
1816
+ return false;
1817
+ }
1818
+
1819
+ const item_2 = trade.inputItem2 ? stringifyItem(bot, trade.inputItem2)+' ' : '';
1820
+ log(bot, `Trading ${stringifyItem(bot, trade.inputItem1)} ${item_2}for ${stringifyItem(bot, trade.outputItem)}...`);
1821
+
1822
+ const maxPossibleTrades = trade.maximumNbTradeUses - trade.nbTradeUses;
1823
+ const requestedCount = count;
1824
+ const actualCount = Math.min(requestedCount, maxPossibleTrades);
1825
+
1826
+ if (actualCount <= 0) {
1827
+ log(bot, `Trade ${index} has been used to its maximum limit`);
1828
+ villager.close();
1829
+ return false;
1830
+ }
1831
+
1832
+ if (!hasResources(villager.slots, trade, actualCount)) {
1833
+ log(bot, `Don't have enough resources to execute trade ${index} ${actualCount} time(s)`);
1834
+ villager.close();
1835
+ return false;
1836
+ }
1837
+
1838
+ log(bot, `Executing trade ${index} ${actualCount} time(s)...`);
1839
+
1840
+ try {
1841
+ await bot.trade(villager, tradeIndex, actualCount);
1842
+ log(bot, `Successfully traded ${actualCount} time(s)`);
1843
+ villager.close();
1844
+ return true;
1845
+ } catch (tradeErr) {
1846
+ log(bot, 'An error occurred while trying to execute the trade');
1847
+ console.log('Trade execution error:', tradeErr.message);
1848
+ villager.close();
1849
+ return false;
1850
+ }
1851
+ } catch (err) {
1852
+ log(bot, 'Failed to open villager trading interface');
1853
+ console.log('Villager interface error:', err.message);
1854
+ return false;
1855
+ }
1856
+ }
1857
+
1858
+ function hasResources(window, trade, count) {
1859
+ const first = enough(trade.inputItem1, count);
1860
+ const second = !trade.inputItem2 || enough(trade.inputItem2, count);
1861
+ return first && second;
1862
+
1863
+ function enough(item, count) {
1864
+ let c = 0;
1865
+ window.forEach((element) => {
1866
+ if (element && element.type === item.type && element.metadata === item.metadata) {
1867
+ c += element.count;
1868
+ }
1869
+ });
1870
+ return c >= item.count * count;
1871
+ }
1872
+ }
1873
+
1874
+ function stringifyTrades(bot, trades) {
1875
+ return trades.map((trade) => {
1876
+ let text = stringifyItem(bot, trade.inputItem1);
1877
+ if (trade.inputItem2) text += ` & ${stringifyItem(bot, trade.inputItem2)}`;
1878
+ if (trade.disabled) text += ' x '; else text += ' » ';
1879
+ text += stringifyItem(bot, trade.outputItem);
1880
+ return `(${trade.nbTradeUses}/${trade.maximumNbTradeUses}) ${text}`;
1881
+ });
1882
+ }
1883
+
1884
+ function stringifyItem(bot, item) {
1885
+ if (!item) return 'nothing';
1886
+ let text = `${item.count} ${item.displayName}`;
1887
+ if (item.nbt && item.nbt.value) {
1888
+ const ench = item.nbt.value.ench;
1889
+ const StoredEnchantments = item.nbt.value.StoredEnchantments;
1890
+ const Potion = item.nbt.value.Potion;
1891
+ const display = item.nbt.value.display;
1892
+
1893
+ if (Potion) text += ` of ${Potion.value.replace(/_/g, ' ').split(':')[1] || 'unknown type'}`;
1894
+ if (display) text += ` named ${display.value.Name.value}`;
1895
+ if (ench || StoredEnchantments) {
1896
+ text += ` enchanted with ${(ench || StoredEnchantments).value.value.map((e) => {
1897
+ const lvl = e.lvl.value;
1898
+ const id = e.id.value;
1899
+ return bot.registry.enchantments[id].displayName + ' ' + lvl;
1900
+ }).join(' ')}`;
1901
+ }
1902
+ }
1903
+ return text;
1904
+ }
1905
+
1906
+ export async function digDown(bot, distance = 10) {
1907
+ /**
1908
+ * Digs down a specified distance. Will stop if it reaches lava, water, or a fall of >=4 blocks below the bot.
1909
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1910
+ * @param {int} distance, distance to dig down.
1911
+ * @returns {Promise<boolean>} true if successfully dug all the way down.
1912
+ * @example
1913
+ * await skills.digDown(bot, 10);
1914
+ **/
1915
+
1916
+ let start_block_pos = bot.blockAt(bot.entity.position).position;
1917
+ for (let i = 1; i <= distance; i++) {
1918
+ const targetBlock = bot.blockAt(start_block_pos.offset(0, -i, 0));
1919
+ let belowBlock = bot.blockAt(start_block_pos.offset(0, -i-1, 0));
1920
+
1921
+ if (!targetBlock || !belowBlock) {
1922
+ log(bot, `Dug down ${i-1} blocks, but reached the end of the world.`);
1923
+ return true;
1924
+ }
1925
+
1926
+ // Check for lava, water
1927
+ if (targetBlock.name === 'lava' || targetBlock.name === 'water' ||
1928
+ belowBlock.name === 'lava' || belowBlock.name === 'water') {
1929
+ log(bot, `Dug down ${i-1} blocks, but reached ${belowBlock ? belowBlock.name : '(lava/water)'}`)
1930
+ return false;
1931
+ }
1932
+
1933
+ const MAX_FALL_BLOCKS = 2;
1934
+ let num_fall_blocks = 0;
1935
+ for (let j = 0; j <= MAX_FALL_BLOCKS; j++) {
1936
+ if (!belowBlock || (belowBlock.name !== 'air' && belowBlock.name !== 'cave_air')) {
1937
+ break;
1938
+ }
1939
+ num_fall_blocks++;
1940
+ belowBlock = bot.blockAt(belowBlock.position.offset(0, -1, 0));
1941
+ }
1942
+ if (num_fall_blocks > MAX_FALL_BLOCKS) {
1943
+ log(bot, `Dug down ${i-1} blocks, but reached a drop below the next block.`);
1944
+ return false;
1945
+ }
1946
+
1947
+ if (targetBlock.name === 'air' || targetBlock.name === 'cave_air') {
1948
+ log(bot, 'Skipping air block');
1949
+ console.log(targetBlock.position);
1950
+ continue;
1951
+ }
1952
+
1953
+ let dug = await breakBlockAt(bot, targetBlock.position.x, targetBlock.position.y, targetBlock.position.z);
1954
+ if (!dug) {
1955
+ log(bot, 'Failed to dig block at position:' + targetBlock.position);
1956
+ return false;
1957
+ }
1958
+ }
1959
+ log(bot, `Dug down ${distance} blocks.`);
1960
+ return true;
1961
+ }
1962
+
1963
+ export async function goToSurface(bot) {
1964
+ /**
1965
+ * Navigate to the surface (highest non-air block at current x,z).
1966
+ * @param {MinecraftBot} bot, reference to the minecraft bot.
1967
+ * @returns {Promise<boolean>} true if the surface was reached, false otherwise.
1968
+ **/
1969
+ const pos = bot.entity.position;
1970
+ for (let y = 360; y > -64; y--) { // probably not the best way to find the surface but it works
1971
+ const block = bot.blockAt(new Vec3(pos.x, y, pos.z));
1972
+ if (!block || block.name === 'air' || block.name === 'cave_air') {
1973
+ continue;
1974
+ }
1975
+ await goToPosition(bot, block.position.x, block.position.y + 1, block.position.z, 0); // this will probably work most of the time but a custom mining and towering up implementation could be added if needed
1976
+ log(bot, `Going to the surface at y=${y+1}.`);``
1977
+ return true;
1978
+ }
1979
+ return false;
1980
+ }
1981
+
1982
+ export async function useToolOn(bot, toolName, targetName) {
1983
+ /**
1984
+ * Equip a tool and use it on the nearest target.
1985
+ * @param {MinecraftBot} bot
1986
+ * @param {string} toolName - item name of the tool to equip, or "hand" for no tool.
1987
+ * @param {string} targetName - entity type, block type, or "nothing" for no target
1988
+ * @returns {Promise<boolean>} true if action succeeded
1989
+ */
1990
+ if (!bot.inventory.slots.find(slot => slot && slot.name === toolName) && !bot.game.gameMode === 'creative') {
1991
+ log(bot, `You do not have any ${toolName} to use.`);
1992
+ return false;
1993
+ }
1994
+
1995
+ targetName = targetName.toLowerCase();
1996
+ if (targetName === 'nothing') {
1997
+ const equipped = await equip(bot, toolName);
1998
+ if (!equipped) {
1999
+ return false;
2000
+ }
2001
+ await bot.activateItem();
2002
+ log(bot, `Used ${toolName}.`);
2003
+ } else if (world.isEntityType(targetName)) {
2004
+ const entity = world.getNearestEntityWhere(bot, e => e.name === targetName, 64);
2005
+ if (!entity) {
2006
+ log(bot, `Could not find any ${targetName}.`);
2007
+ return false;
2008
+ }
2009
+ await goToPosition(bot, entity.position.x, entity.position.y, entity.position.z);
2010
+ if (toolName === 'hand') {
2011
+ await bot.unequip('hand');
2012
+ }
2013
+ else {
2014
+ const equipped = await equip(bot, toolName);
2015
+ if (!equipped) return false;
2016
+ }
2017
+ await bot.useOn(entity);
2018
+ log(bot, `Used ${toolName} on ${targetName}.`);
2019
+ } else {
2020
+ let block = null;
2021
+ if (targetName === 'water' || targetName === 'lava') {
2022
+ // we want to get liquid source blocks, not flowing blocks
2023
+ // so search for blocks with metadata 0 (not flowing)
2024
+ let blocks = world.getNearestBlocksWhere(bot, block => block.name === targetName && block.metadata === 0, 64, 1);
2025
+ if (blocks.length === 0) {
2026
+ log(bot, `Could not find any source ${targetName}.`);
2027
+ return false;
2028
+ }
2029
+ block = blocks[0];
2030
+ }
2031
+ else {
2032
+ block = world.getNearestBlock(bot, targetName, 64);
2033
+ }
2034
+ if (!block) {
2035
+ log(bot, `Could not find any ${targetName}.`);
2036
+ return false;
2037
+ }
2038
+ return await useToolOnBlock(bot, toolName, block);
2039
+ }
2040
+
2041
+ return true;
2042
+ }
2043
+
2044
+ export async function useToolOnBlock(bot, toolName, block) {
2045
+ /**
2046
+ * Use a tool on a specific block.
2047
+ * @param {MinecraftBot} bot
2048
+ * @param {string} toolName - item name of the tool to equip, or "hand" for no tool.
2049
+ * @param {Block} block - the block reference to use the tool on.
2050
+ * @returns {Promise<boolean>} true if action succeeded
2051
+ */
2052
+
2053
+ const distance = toolName === 'water_bucket' && block.name !== 'lava' ? 1.5 : 2;
2054
+ await goToPosition(bot, block.position.x, block.position.y, block.position.z, distance);
2055
+ await bot.lookAt(block.position.offset(0.5, 0.5, 0.5));
2056
+
2057
+ // if block in view is closer than the target block, it is in our way. try to move closer
2058
+ const viewBlocked = () => {
2059
+ const blockInView = bot.blockAtCursor(5);
2060
+ const headPos = bot.entity.position.offset(0, bot.entity.height, 0);
2061
+ return blockInView &&
2062
+ !blockInView.position.equals(block.position) &&
2063
+ blockInView.position.distanceTo(headPos) < block.position.distanceTo(headPos);
2064
+ }
2065
+ const blockInView = bot.blockAtCursor(5);
2066
+ if (viewBlocked()) {
2067
+ log(bot, `Block ${blockInView.name} is in the way, moving closer...`);
2068
+ // choose random block next to target block, go to it
2069
+ const nearbyPos = block.position.offset(Math.random() * 2 - 1, 0, Math.random() * 2 - 1);
2070
+ await goToPosition(bot, nearbyPos.x, nearbyPos.y, nearbyPos.z, 1);
2071
+ await bot.lookAt(block.position.offset(0.5, 0.5, 0.5));
2072
+ if (viewBlocked()) {
2073
+ const blockInView = bot.blockAtCursor(5);
2074
+ log(bot, `Block ${blockInView.name} is in the way, not using ${toolName}.`);
2075
+ return false;
2076
+ }
2077
+ }
2078
+
2079
+ const equipped = await equip(bot, toolName);
2080
+
2081
+ if (!equipped) {
2082
+ log(bot, `Could not equip ${toolName}.`);
2083
+ return false;
2084
+ }
2085
+ if (toolName.includes('bucket')) {
2086
+ await bot.activateItem();
2087
+ }
2088
+ else {
2089
+ await bot.activateBlock(block);
2090
+ }
2091
+ log(bot, `Used ${toolName} on ${block.name}.`);
2092
+ return true;
2093
+ }