Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/craft.js
1467 views
1
const assert = require('assert')
2
const { once } = require('../promise_utils')
3
4
module.exports = inject
5
6
function inject (bot) {
7
const Item = require('prismarine-item')(bot.registry)
8
const Recipe = require('prismarine-recipe')(bot.registry).Recipe
9
let windowCraftingTable
10
11
async function craft (recipe, count, craftingTable) {
12
assert.ok(recipe)
13
count = parseInt(count ?? 1, 10)
14
if (recipe.requiresTable && !craftingTable) {
15
throw new Error('Recipe requires craftingTable, but one was not supplied: ' + JSON.stringify(recipe))
16
}
17
18
try {
19
for (let i = 0; i < count; i++) {
20
await craftOnce(recipe, craftingTable)
21
}
22
23
if (windowCraftingTable) {
24
bot.closeWindow(windowCraftingTable)
25
windowCraftingTable = undefined
26
}
27
} catch (err) {
28
if (windowCraftingTable) {
29
bot.closeWindow(windowCraftingTable)
30
windowCraftingTable = undefined
31
}
32
throw new Error(err)
33
}
34
}
35
36
async function craftOnce (recipe, craftingTable) {
37
if (craftingTable) {
38
if (!windowCraftingTable) {
39
bot.activateBlock(craftingTable)
40
const [window] = await once(bot, 'windowOpen')
41
windowCraftingTable = window
42
}
43
if (!windowCraftingTable.type.startsWith('minecraft:crafting')) {
44
throw new Error('crafting: non craftingTable used as craftingTable: ' + windowCraftingTable.type)
45
}
46
await startClicking(windowCraftingTable, 3, 3)
47
} else {
48
await startClicking(bot.inventory, 2, 2)
49
}
50
51
async function startClicking (window, w, h) {
52
const extraSlots = unusedRecipeSlots()
53
let ingredientIndex = 0
54
let originalSourceSlot = null
55
let it
56
if (recipe.inShape) {
57
it = {
58
x: 0,
59
y: 0,
60
row: recipe.inShape[0]
61
}
62
await clickShape()
63
} else {
64
await nextIngredientsClick()
65
}
66
67
function incrementShapeIterator () {
68
it.x += 1
69
if (it.x >= it.row.length) {
70
it.y += 1
71
if (it.y >= recipe.inShape.length) return null
72
it.x = 0
73
it.row = recipe.inShape[it.y]
74
}
75
return it
76
}
77
78
async function nextShapeClick () {
79
if (incrementShapeIterator()) {
80
await clickShape()
81
} else if (!recipe.ingredients) {
82
await putMaterialsAway()
83
} else {
84
await nextIngredientsClick()
85
}
86
}
87
88
async function clickShape () {
89
const destSlot = slot(it.x, it.y)
90
const ingredient = it.row[it.x]
91
if (ingredient.id === -1) return nextShapeClick()
92
if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
93
(ingredient.metadata != null &&
94
window.selectedItem.metadata !== ingredient.metadata)) {
95
// we are not holding the item we need. click it.
96
const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
97
if (!sourceItem) throw new Error('missing ingredient')
98
if (originalSourceSlot == null) originalSourceSlot = sourceItem.slot
99
await bot.clickWindow(sourceItem.slot, 0, 0)
100
}
101
await bot.clickWindow(destSlot, 1, 0)
102
await nextShapeClick()
103
}
104
105
async function nextIngredientsClick () {
106
const ingredient = recipe.ingredients[ingredientIndex]
107
const destSlot = extraSlots.pop()
108
if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
109
(ingredient.metadata != null &&
110
window.selectedItem.metadata !== ingredient.metadata)) {
111
// we are not holding the item we need. click it.
112
const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
113
if (!sourceItem) throw new Error('missing ingredient')
114
if (originalSourceSlot == null) originalSourceSlot = sourceItem.slot
115
await bot.clickWindow(sourceItem.slot, 0, 0)
116
}
117
await bot.clickWindow(destSlot, 1, 0)
118
if (++ingredientIndex < recipe.ingredients.length) {
119
await nextIngredientsClick()
120
} else {
121
await putMaterialsAway()
122
}
123
}
124
125
async function putMaterialsAway () {
126
const start = window.inventoryStart
127
const end = window.inventoryEnd
128
await bot.putSelectedItemRange(start, end, window, originalSourceSlot)
129
await grabResult()
130
}
131
132
async function grabResult () {
133
assert.strictEqual(window.selectedItem, null)
134
// Causes a double-emit on 1.12+ --nickelpro
135
// put the recipe result in the output
136
const item = new Item(recipe.result.id, recipe.result.count, recipe.result.metadata)
137
window.updateSlot(0, item)
138
await bot.putAway(0)
139
await updateOutShape()
140
}
141
142
async function updateOutShape () {
143
if (!recipe.outShape) {
144
for (let i = 1; i <= w * h; i++) {
145
window.updateSlot(i, null)
146
}
147
return
148
}
149
const slotsToClick = []
150
for (let y = 0; y < recipe.outShape.length; ++y) {
151
const row = recipe.outShape[y]
152
for (let x = 0; x < row.length; ++x) {
153
const _slot = slot(x, y)
154
let item = null
155
if (row[x].id !== -1) {
156
item = new Item(row[x].id, row[x].count, row[x].metadata || null)
157
slotsToClick.push(_slot)
158
}
159
window.updateSlot(_slot, item)
160
}
161
}
162
for (const _slot of slotsToClick) {
163
await bot.putAway(_slot)
164
}
165
}
166
167
function slot (x, y) {
168
return 1 + x + w * y
169
}
170
171
function unusedRecipeSlots () {
172
const result = []
173
let x
174
let y
175
let row
176
if (recipe.inShape) {
177
for (y = 0; y < recipe.inShape.length; ++y) {
178
row = recipe.inShape[y]
179
for (x = 0; x < row.length; ++x) {
180
if (row[x].id === -1) result.push(slot(x, y))
181
}
182
for (; x < w; ++x) {
183
result.push(slot(x, y))
184
}
185
}
186
for (; y < h; ++y) {
187
for (x = 0; x < w; ++x) {
188
result.push(slot(x, y))
189
}
190
}
191
} else {
192
for (y = 0; y < h; ++y) {
193
for (x = 0; x < w; ++x) {
194
result.push(slot(x, y))
195
}
196
}
197
}
198
return result
199
}
200
}
201
}
202
203
function recipesFor (itemType, metadata, minResultCount, craftingTable) {
204
minResultCount = minResultCount ?? 1
205
const results = []
206
Recipe.find(itemType, metadata).forEach((recipe) => {
207
if (requirementsMetForRecipe(recipe, minResultCount, craftingTable)) {
208
results.push(recipe)
209
}
210
})
211
return results
212
}
213
214
function recipesAll (itemType, metadata, craftingTable) {
215
const results = []
216
Recipe.find(itemType, metadata).forEach((recipe) => {
217
if (!recipe.requiresTable || craftingTable) {
218
results.push(recipe)
219
}
220
})
221
return results
222
}
223
224
function requirementsMetForRecipe (recipe, minResultCount, craftingTable) {
225
if (recipe.requiresTable && !craftingTable) return false
226
227
// how many times we have to perform the craft to achieve minResultCount
228
const craftCount = Math.ceil(minResultCount / recipe.result.count)
229
230
// false if not enough inventory to make all the ones that we want
231
for (let i = 0; i < recipe.delta.length; ++i) {
232
const d = recipe.delta[i]
233
if (bot.inventory.count(d.id, d.metadata) + d.count * craftCount < 0) return false
234
}
235
236
// otherwise true
237
return true
238
}
239
240
bot.craft = craft
241
bot.recipesFor = recipesFor
242
bot.recipesAll = recipesAll
243
}
244
245