# mineflayer-crafting-util

Crafting planner and execution helpers for Mineflayer bots.

The library can be used in two ways:

- As a Mineflayer plugin, which adds planning and crafting methods to `bot`.
- As a static planner, which builds plans from a `minecraft-data` registry without connecting a bot.

## Installation

```sh
npm i mineflayer-crafting-util
```

```sh
yarn add mineflayer-crafting-util
```

## Bot Usage

```js
const mineflayer = require('mineflayer')
const { plugin: craftingUtil } = require('mineflayer-crafting-util')

const bot = mineflayer.createBot({
  host: 'localhost',
  port: 25565,
  username: 'bot'
})

bot.loadPlugin(craftingUtil())

bot.once('spawn', async () => {
  const stick = bot.registry.itemsByName.stick
  const craftingTable = bot.findBlock({
    matching: bot.registry.blocksByName.crafting_table.id,
    maxDistance: 4
  })

  const plan = bot.planCraftInventory({ id: stick.id, count: 4 })

  if (!plan.success) {
    console.log('Missing base items:', plan.itemsRequiredBase)
    return
  }

  await bot.craftPlan(plan, craftingTable)
})
```

For a one-call inventory-backed craft:

```js
const pickaxe = bot.registry.itemsByName.iron_pickaxe
const craftingTable = bot.findBlock({
  matching: bot.registry.blocksByName.crafting_table.id,
  maxDistance: 4
})

await bot.craftItem(pickaxe.id, 1, craftingTable)
```

## Static Usage

```js
const mcData = require('minecraft-data')('1.21.4')
const { buildStatic } = require('mineflayer-crafting-util')

async function main () {
  const { Recipe } = require('prismarine-recipe')(mcData)
  const crafter = await buildStatic(Recipe)
  const pickaxe = mcData.itemsByName.stone_pickaxe

  const plan = crafter(
    { id: pickaxe.id, count: 1 },
    {
      availableItems: [
        { id: mcData.itemsByName.cobblestone.id, count: 3 },
        { id: mcData.itemsByName.oak_log.id, count: 1 }
      ],
      multipleRecipes: true
    }
  )

  console.log(plan.success)
  console.log(plan.recipesToDo)
}

main()
```

## Exports

### `plugin`

```ts
plugin(recipe?: Recipe): (bot: Bot, botOptions: BotOptions) => Promise<void>
```

Mineflayer plugin factory export. Call it to create a plugin, then use with `bot.loadPlugin(plugin())`.

The default export is the same plugin factory:

```js
const craftingUtil = require('mineflayer-crafting-util')
bot.loadPlugin((craftingUtil.default ?? craftingUtil.plugin)())
```

### `buildStatic`

```ts
buildStatic(registry: IndexedData): Promise<CraftingFunc>
```

Builds a version-specific planner from a `minecraft-data` registry.

```js
const mcData = require('minecraft-data')('1.21.4')
const { Recipe } = require('prismarine-recipe')(mcData)
const crafter = await buildStatic(Recipe)
const plan = crafter({ id: mcData.itemsByName.stick.id, count: 4 })
```

### `craftPlan`

```ts
craftPlan(
  bot: Bot,
  plan: CraftingPlan,
  craftingTable: Block,
  options?: CraftPlanOptions
): Promise<CraftingPlan>
```

Executes every recipe step in an existing plan with `bot.craft`.

Rules:

- `craftingTable` is required.
- Throws if `plan.success` is false.
- Returns the same plan after crafting.
- By default, each plan step is sent to Mineflayer as one `bot.craft(recipe, recipeApplications, craftingTable)` call.
- With `{ strict: true }`, large recipe application counts are split by the crafted item stack size.

### `craftItem`

```ts
craftItem(
  bot: Bot,
  itemId: number,
  count: number,
  craftingTable: Block,
  options?: CraftOptions,
  craftOptions?: CraftPlanOptions
): Promise<CraftingPlan>
```

Plans and executes a craft for `itemId`.

Defaults used by `craftItem`:

- `availableItems`: current bot inventory, unless provided.
- `careAboutExisting`: `false`.
- `includeRecursion`: `true`.
- `multipleRecipes`: `true`.

### `setupActualCrafting`

```ts
setupActualCrafting(bot: Bot): void
```

Adds `bot.craftPlan` and `bot.craftItem` to a bot. The plugin calls this automatically, so most users do not need to call it directly.

## Bot Methods

Loading the plugin adds these methods to `bot`.

### `bot.planCraft`

```ts
bot.planCraft(wantedItem: Item, options?: CraftOptions): CraftingPlan
```

Creates a crafting plan for `wantedItem`.

Without `availableItems`, this answers "what would be required to craft this?" and does not validate against a specific inventory.

With `availableItems`, this answers "can this be crafted from these items?" and returns a concrete ordered recipe plan when possible.

### `bot.planCraftInventory`

```ts
bot.planCraftInventory(wantedItem: Item): CraftingPlan
```

Plans using the bot inventory as `availableItems`.

Internally this uses:

```ts
{
  availableItems: bot inventory items,
  careAboutExisting: false,
  includeRecursion: true,
  multipleRecipes: true
}
```

### `bot.craftPlan`

```ts
bot.craftPlan(
  plan: CraftingPlan,
  craftingTable: Block,
  options?: CraftPlanOptions
): Promise<CraftingPlan>
```

Executes an existing plan. This is the bot-bound wrapper around the exported `craftPlan`.

### `bot.craftItem`

```ts
bot.craftItem(
  itemId: number,
  count: number,
  craftingTable: Block,
  options?: CraftOptions,
  craftOptions?: CraftPlanOptions
): Promise<CraftingPlan>
```

Plans from inventory and executes the result. This is the bot-bound wrapper around the exported `craftItem`.

## Types

### `Item`

```ts
interface Item {
  id: number
  count: number
}
```

`id` is the numeric item id from the active Minecraft registry. `count` is the item count.

### `CraftOptions`

```ts
interface CraftOptions {
  availableItems?: Item[]
  careAboutExisting?: boolean
  includeRecursion?: boolean
  multipleRecipes?: boolean
}
```

| Option | Default | Description |
| --- | --- | --- |
| `availableItems` | `undefined` | Inventory constraints for the planner. When provided, the planner attempts to produce a craftable ordered plan from those items. |
| `careAboutExisting` | `false` | When `false`, matching copies of the wanted item inside `availableItems` are ignored so the planner crafts the requested count fresh. When `true`, existing wanted items can satisfy the request. |
| `includeRecursion` | `false` | Keeps recursive planning behavior enabled for inventory-backed plans. `bot.planCraftInventory` and `craftItem` enable this. |
| `multipleRecipes` | `false` | Allows the planner to try alternate recipes and recipe families when building a plan from available items. Recommended with `availableItems`. |

Note: `availableItems` may be normalized by the planner. Pass a cloned array if you need to preserve the original object identities and counts.

### `CraftingPlan`

```ts
interface CraftingPlan {
  success: boolean
  itemsRequiredBase: Item[]
  itemsRequiredImmediate: Item[]
  itemsRemaining: Item[]
  itemsCreated: Item[]
  recipesToDo: RecipeInfo[]
  requiresCraftingTable: boolean
}
```

| Property | Description |
| --- | --- |
| `success` | Whether the planner found a valid plan. |
| `itemsRequiredBase` | Base items needed to complete the request, after accounting for `recipesToDo`. |
| `itemsRequiredImmediate` | Direct recipe inputs needed at the point planning stopped. |
| `itemsRemaining` | Requested output that is still not satisfied by the partial plan. |
| `itemsCreated` | Net positive items produced by `recipesToDo`. On partial available-item plans, this shows what can be created even when `success` is `false`. |
| `recipesToDo` | Ordered recipe applications to execute. |
| `requiresCraftingTable` | Whether any step in `recipesToDo` requires a crafting table. |

### `RecipeInfo`

```ts
interface RecipeInfo {
  recipe: Recipe
  recipeApplications: number
}
```

`recipe` is a `prismarine-recipe` recipe object. `recipeApplications` is the number of times to apply that recipe.

### `CraftPlanOptions`

```ts
interface CraftPlanOptions {
  strict?: boolean
}
```

| Option | Default | Description |
| --- | --- | --- |
| `strict` | `false` | When `true`, execution splits each plan step into multiple `bot.craft` calls by the result item stack size. This is useful as a workaround for Mineflayer craft-count issues with non-stackable outputs. |

### `CraftingFunc`

```ts
type CraftingFunc = (item: Item, options?: CraftOptions) => CraftingPlan
```

Returned by `buildStatic` and used internally for `bot.planCraft`.

## Working With Plans

To print recipe steps, prefer `recipe.delta` because some Minecraft versions expose empty `recipe.ingredients` arrays for shaped recipes:

```js
function recipeInputs (recipe) {
  return recipe.ingredients && recipe.ingredients.length > 0
    ? recipe.ingredients
    : recipe.delta.filter(item => item.count < 0)
}

for (const step of plan.recipesToDo) {
  const inputs = recipeInputs(step.recipe)
  console.log(inputs, '=>', step.recipe.result, 'x', step.recipeApplications)
}
```

## Test And Smoke Commands

```sh
npm run build
npm test
npm run smoke:matrix
```

Manual static smoke example:

```sh
node scripts/staticPlanSmoke.js --version 1.21.4 --wanted-item stone_pickaxe --available cobblestone:3,oak_log:1
```
