244 lines
7.4 KiB
JavaScript
244 lines
7.4 KiB
JavaScript
const assert = require('assert')
|
|
const { once } = require('events')
|
|
|
|
module.exports = inject
|
|
|
|
function inject (bot) {
|
|
const Item = require('prismarine-item')(bot.registry)
|
|
const Recipe = require('prismarine-recipe')(bot.registry).Recipe
|
|
let windowCraftingTable
|
|
|
|
async function craft (recipe, count, craftingTable) {
|
|
assert.ok(recipe)
|
|
count = parseInt(count ?? 1, 10)
|
|
if (recipe.requiresTable && !craftingTable) {
|
|
throw new Error('recipe requires craftingTable')
|
|
}
|
|
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
await craftOnce(recipe, craftingTable)
|
|
}
|
|
|
|
if (windowCraftingTable) {
|
|
bot.closeWindow(windowCraftingTable)
|
|
windowCraftingTable = undefined
|
|
}
|
|
} catch (err) {
|
|
if (windowCraftingTable) {
|
|
bot.closeWindow(windowCraftingTable)
|
|
windowCraftingTable = undefined
|
|
}
|
|
throw new Error(err)
|
|
}
|
|
}
|
|
|
|
async function craftOnce (recipe, craftingTable) {
|
|
if (craftingTable) {
|
|
if (!windowCraftingTable) {
|
|
bot.activateBlock(craftingTable)
|
|
const [window] = await once(bot, 'windowOpen')
|
|
windowCraftingTable = window
|
|
}
|
|
if (!windowCraftingTable.type.startsWith('minecraft:crafting')) {
|
|
throw new Error('crafting: non craftingTable used as craftingTable')
|
|
}
|
|
await startClicking(windowCraftingTable, 3, 3)
|
|
} else {
|
|
await startClicking(bot.inventory, 2, 2)
|
|
}
|
|
|
|
async function startClicking (window, w, h) {
|
|
const extraSlots = unusedRecipeSlots()
|
|
let ingredientIndex = 0
|
|
let originalSourceSlot = null
|
|
let it
|
|
if (recipe.inShape) {
|
|
it = {
|
|
x: 0,
|
|
y: 0,
|
|
row: recipe.inShape[0]
|
|
}
|
|
await clickShape()
|
|
} else {
|
|
await nextIngredientsClick()
|
|
}
|
|
|
|
function incrementShapeIterator () {
|
|
it.x += 1
|
|
if (it.x >= it.row.length) {
|
|
it.y += 1
|
|
if (it.y >= recipe.inShape.length) return null
|
|
it.x = 0
|
|
it.row = recipe.inShape[it.y]
|
|
}
|
|
return it
|
|
}
|
|
|
|
async function nextShapeClick () {
|
|
if (incrementShapeIterator()) {
|
|
await clickShape()
|
|
} else if (!recipe.ingredients) {
|
|
await putMaterialsAway()
|
|
} else {
|
|
await nextIngredientsClick()
|
|
}
|
|
}
|
|
|
|
async function clickShape () {
|
|
const destSlot = slot(it.x, it.y)
|
|
const ingredient = it.row[it.x]
|
|
if (ingredient.id === -1) return nextShapeClick()
|
|
if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
|
|
(ingredient.metadata != null &&
|
|
window.selectedItem.metadata !== ingredient.metadata)) {
|
|
// we are not holding the item we need. click it.
|
|
const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
|
|
if (!sourceItem) throw new Error('missing ingredient')
|
|
if (originalSourceSlot == null) originalSourceSlot = sourceItem.slot
|
|
await bot.clickWindow(sourceItem.slot, 0, 0)
|
|
}
|
|
await bot.clickWindow(destSlot, 1, 0)
|
|
await nextShapeClick()
|
|
}
|
|
|
|
async function nextIngredientsClick () {
|
|
const ingredient = recipe.ingredients[ingredientIndex]
|
|
const destSlot = extraSlots.pop()
|
|
if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
|
|
(ingredient.metadata != null &&
|
|
window.selectedItem.metadata !== ingredient.metadata)) {
|
|
// we are not holding the item we need. click it.
|
|
const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
|
|
if (!sourceItem) throw new Error('missing ingredient')
|
|
if (originalSourceSlot == null) originalSourceSlot = sourceItem.slot
|
|
await bot.clickWindow(sourceItem.slot, 0, 0)
|
|
}
|
|
await bot.clickWindow(destSlot, 1, 0)
|
|
if (++ingredientIndex < recipe.ingredients.length) {
|
|
await nextIngredientsClick()
|
|
} else {
|
|
await putMaterialsAway()
|
|
}
|
|
}
|
|
|
|
async function putMaterialsAway () {
|
|
const start = window.inventoryStart
|
|
const end = window.inventoryEnd
|
|
await bot.putSelectedItemRange(start, end, window, originalSourceSlot)
|
|
await grabResult()
|
|
}
|
|
|
|
async function grabResult () {
|
|
assert.strictEqual(window.selectedItem, null)
|
|
// Causes a double-emit on 1.12+ --nickelpro
|
|
// put the recipe result in the output
|
|
const item = new Item(recipe.result.id, recipe.result.count, recipe.result.metadata)
|
|
window.updateSlot(0, item)
|
|
await bot.putAway(0)
|
|
await updateOutShape()
|
|
}
|
|
|
|
async function updateOutShape () {
|
|
if (!recipe.outShape) {
|
|
for (let i = 1; i <= w * h; i++) {
|
|
window.updateSlot(i, null)
|
|
}
|
|
return
|
|
}
|
|
const slotsToClick = []
|
|
for (let y = 0; y < recipe.outShape.length; ++y) {
|
|
const row = recipe.outShape[y]
|
|
for (let x = 0; x < row.length; ++x) {
|
|
const _slot = slot(x, y)
|
|
let item = null
|
|
if (row[x].id !== -1) {
|
|
item = new Item(row[x].id, row[x].count, row[x].metadata || null)
|
|
slotsToClick.push(_slot)
|
|
}
|
|
window.updateSlot(_slot, item)
|
|
}
|
|
}
|
|
for (const _slot of slotsToClick) {
|
|
await bot.putAway(_slot)
|
|
}
|
|
}
|
|
|
|
function slot (x, y) {
|
|
return 1 + x + w * y
|
|
}
|
|
|
|
function unusedRecipeSlots () {
|
|
const result = []
|
|
let x
|
|
let y
|
|
let row
|
|
if (recipe.inShape) {
|
|
for (y = 0; y < recipe.inShape.length; ++y) {
|
|
row = recipe.inShape[y]
|
|
for (x = 0; x < row.length; ++x) {
|
|
if (row[x].id === -1) result.push(slot(x, y))
|
|
}
|
|
for (; x < w; ++x) {
|
|
result.push(slot(x, y))
|
|
}
|
|
}
|
|
for (; y < h; ++y) {
|
|
for (x = 0; x < w; ++x) {
|
|
result.push(slot(x, y))
|
|
}
|
|
}
|
|
} else {
|
|
for (y = 0; y < h; ++y) {
|
|
for (x = 0; x < w; ++x) {
|
|
result.push(slot(x, y))
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
function recipesFor (itemType, metadata, minResultCount, craftingTable) {
|
|
minResultCount = minResultCount ?? 1
|
|
const results = []
|
|
Recipe.find(itemType, metadata).forEach((recipe) => {
|
|
if (requirementsMetForRecipe(recipe, minResultCount, craftingTable)) {
|
|
results.push(recipe)
|
|
}
|
|
})
|
|
return results
|
|
}
|
|
|
|
function recipesAll (itemType, metadata, craftingTable) {
|
|
const results = []
|
|
Recipe.find(itemType, metadata).forEach((recipe) => {
|
|
if (!recipe.requiresTable || craftingTable) {
|
|
results.push(recipe)
|
|
}
|
|
})
|
|
return results
|
|
}
|
|
|
|
function requirementsMetForRecipe (recipe, minResultCount, craftingTable) {
|
|
if (recipe.requiresTable && !craftingTable) return false
|
|
|
|
// how many times we have to perform the craft to achieve minResultCount
|
|
const craftCount = Math.ceil(minResultCount / recipe.result.count)
|
|
|
|
// false if not enough inventory to make all the ones that we want
|
|
for (let i = 0; i < recipe.delta.length; ++i) {
|
|
const d = recipe.delta[i]
|
|
if (bot.inventory.count(d.id, d.metadata) + d.count * craftCount < 0) return false
|
|
}
|
|
|
|
// otherwise true
|
|
return true
|
|
}
|
|
|
|
bot.craft = craft
|
|
bot.recipesFor = recipesFor
|
|
bot.recipesAll = recipesAll
|
|
}
|