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.
- package/FAQ.md +38 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/andy.json +6 -0
- package/bin/mindcraft.js +80 -0
- package/keys.example.json +19 -0
- package/main.js +80 -0
- package/package.json +78 -0
- package/patches/minecraft-data+3.97.0.patch +13 -0
- package/patches/mineflayer+4.33.0.patch +54 -0
- package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
- package/patches/mineflayer-pvp+1.3.2.patch +13 -0
- package/patches/prismarine-viewer+1.33.0.patch +13 -0
- package/patches/protodef+1.19.0.patch +15 -0
- package/profiles/andy-4-reasoning.json +14 -0
- package/profiles/andy-4.json +7 -0
- package/profiles/azure.json +19 -0
- package/profiles/claude.json +7 -0
- package/profiles/claude_thinker.json +15 -0
- package/profiles/deepseek.json +7 -0
- package/profiles/defaults/_default.json +256 -0
- package/profiles/defaults/assistant.json +14 -0
- package/profiles/defaults/creative.json +14 -0
- package/profiles/defaults/god_mode.json +14 -0
- package/profiles/defaults/survival.json +14 -0
- package/profiles/freeguy.json +7 -0
- package/profiles/gemini.json +9 -0
- package/profiles/gpt.json +12 -0
- package/profiles/grok.json +7 -0
- package/profiles/llama.json +10 -0
- package/profiles/mercury.json +9 -0
- package/profiles/mistral.json +5 -0
- package/profiles/qwen.json +17 -0
- package/profiles/tasks/construction_profile.json +42 -0
- package/profiles/tasks/cooking_profile.json +11 -0
- package/profiles/tasks/crafting_profile.json +71 -0
- package/profiles/vllm.json +10 -0
- package/settings.js +64 -0
- package/src/agent/action_manager.js +177 -0
- package/src/agent/agent.js +561 -0
- package/src/agent/coder.js +229 -0
- package/src/agent/commands/actions.js +504 -0
- package/src/agent/commands/index.js +259 -0
- package/src/agent/commands/queries.js +347 -0
- package/src/agent/connection_handler.js +96 -0
- package/src/agent/conversation.js +353 -0
- package/src/agent/history.js +122 -0
- package/src/agent/library/full_state.js +89 -0
- package/src/agent/library/index.js +23 -0
- package/src/agent/library/lockdown.js +32 -0
- package/src/agent/library/skill_library.js +93 -0
- package/src/agent/library/skills.js +2093 -0
- package/src/agent/library/world.js +431 -0
- package/src/agent/memory_bank.js +25 -0
- package/src/agent/mindserver_proxy.js +136 -0
- package/src/agent/modes.js +446 -0
- package/src/agent/npc/build_goal.js +80 -0
- package/src/agent/npc/construction/dirt_shelter.json +38 -0
- package/src/agent/npc/construction/large_house.json +230 -0
- package/src/agent/npc/construction/small_stone_house.json +42 -0
- package/src/agent/npc/construction/small_wood_house.json +42 -0
- package/src/agent/npc/controller.js +261 -0
- package/src/agent/npc/data.js +50 -0
- package/src/agent/npc/item_goal.js +355 -0
- package/src/agent/npc/utils.js +126 -0
- package/src/agent/self_prompter.js +146 -0
- package/src/agent/settings.js +7 -0
- package/src/agent/speak.js +150 -0
- package/src/agent/tasks/construction_tasks.js +1104 -0
- package/src/agent/tasks/cooking_tasks.js +358 -0
- package/src/agent/tasks/tasks.js +594 -0
- package/src/agent/templates/execTemplate.js +6 -0
- package/src/agent/templates/lintTemplate.js +10 -0
- package/src/agent/vision/browser_viewer.js +8 -0
- package/src/agent/vision/camera.js +78 -0
- package/src/agent/vision/vision_interpreter.js +82 -0
- package/src/mindcraft/index.js +28 -0
- package/src/mindcraft/mcserver.js +154 -0
- package/src/mindcraft/mindcraft.js +111 -0
- package/src/mindcraft/mindserver.js +328 -0
- package/src/mindcraft/public/index.html +1253 -0
- package/src/mindcraft/public/settings_spec.json +145 -0
- package/src/mindcraft/userconfig.js +72 -0
- package/src/mindcraft-py/example.py +27 -0
- package/src/mindcraft-py/init-mindcraft.js +24 -0
- package/src/mindcraft-py/mindcraft.py +99 -0
- package/src/models/_model_map.js +89 -0
- package/src/models/azure.js +32 -0
- package/src/models/cerebras.js +61 -0
- package/src/models/claude.js +87 -0
- package/src/models/deepseek.js +59 -0
- package/src/models/gemini.js +176 -0
- package/src/models/glhf.js +71 -0
- package/src/models/gpt.js +147 -0
- package/src/models/grok.js +82 -0
- package/src/models/groq.js +95 -0
- package/src/models/huggingface.js +86 -0
- package/src/models/hyperbolic.js +114 -0
- package/src/models/lmstudio.js +74 -0
- package/src/models/mercury.js +95 -0
- package/src/models/mistral.js +94 -0
- package/src/models/novita.js +71 -0
- package/src/models/ollama.js +115 -0
- package/src/models/openrouter.js +77 -0
- package/src/models/prompter.js +366 -0
- package/src/models/qwen.js +80 -0
- package/src/models/replicate.js +60 -0
- package/src/models/vllm.js +81 -0
- package/src/process/agent_process.js +84 -0
- package/src/process/init_agent.js +54 -0
- package/src/utils/examples.js +83 -0
- package/src/utils/keys.js +34 -0
- package/src/utils/math.js +13 -0
- package/src/utils/mcdata.js +572 -0
- package/src/utils/text.js +78 -0
- 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
|
+
}
|