> ## Documentation Index
> Fetch the complete documentation index at: https://runelite.zip/llms.txt
> Use this file to discover all available pages before exploring further.

# Production and skilling

> Make-X actions, tanning, skills reads, the production workflow builder, minigame teleports, and sailing.

This playbook covers the production interfaces, skill reads, minigame teleports, and the sailing surface.

## Production actions

`ProductionActions` interacts with the Make-X production interface (widget group 270) and the legacy smithing interface (widget group 312). All writes are paced and return `InteractionResult`; a closed interface returns `PRODUCTION_NOT_OPEN`.

| Method                               | Behavior                                                                                |
| ------------------------------------ | --------------------------------------------------------------------------------------- |
| `chooseOption(int index)`            | Clicks the item button at the column index                                              |
| `chooseOption(String itemName)`      | Clicks the first item whose name contains the string (case-insensitive, tags stripped)  |
| `chooseOption(Predicate<String>)`    | Clicks the first item matching the predicate; `PRODUCTION_OPTION_NOT_FOUND` on no match |
| `selectQuantity(ProductionQuantity)` | Clicks the quantity button                                                              |
| `selectMakeXQuantity(int qty)`       | Clicks the X button, then submits the custom amount in the same call                    |
| `enterAmount(int amount)`            | Submits the amount directly when the input box is already open                          |

```java theme={null}
if (ProductionActions.isOpen()) {
    ProductionActions.chooseOption("Iron bar");
    ProductionActions.selectQuantity(ProductionQuantity.ALL);
}
```

The `ProductionQuantity` enum provides `ONE` (1), `FIVE` (5), `TEN` (10), `ALL`, and `X` (custom). `selectMakeXQuantity` bundles the button click and the count resume into one call; the interface must accept the count packet in the same server tick.

Reads live on the `Production` query surface (`isOpen()`, `products()`, `findProduct(...)`, `selectedQuantity()`); see the [widgets playbook](/guides/widgets).

<Warning>
  Verify the production outcome through your own postconditions (inventory gained the product, animation started). An accepted dispatch does not confirm an item was consumed or produced.
</Warning>

## Production workflow builder

`ProductionWorkflowBuilder.create(ProductionPlan)` returns a `TypesafeCarouselStateMachine<ProductionState>` for the full production loop. Configure exactly one product selector (`optionName` or `optionIndex`) and exactly one quantity selector (`quantity` or `customQuantity`); `build()` rejects a missing pair. An optional `InventoryLoadout` enables the `BANK_RESTOCK` state.

```java theme={null}
ProductionPlan plan = ProductionPlan.builder()
    .optionName("Willow longbow")
    .quantity(ProductionQuantity.ALL)
    .restockLoadout(loadout)
    .build();

TypesafeCarouselStateMachine<ProductionWorkflowBuilder.ProductionState> machine =
    ProductionWorkflowBuilder.create(plan);
```

Pulse once per client tick. States: `WAIT_FOR_PRODUCTION_OPEN`, `SELECT_OPTION`, `SELECT_QUANTITY`, `WAIT_FOR_PRODUCTION_COMPLETE`, and `BANK_RESTOCK`. Inspect `machine.snapshot()` for progress and failure details, and `reset()` before reuse. Keep single item-on-item interactions in the action layer instead of wrapping them in a production workflow. See [workflow builders](/guides/workflow-builders) for the enchanting and pouch variants.

## Tanning

`TanningActions` covers the tanner interface (widget group 324). Writes are paced; a closed interface returns `TANNING_NOT_OPEN` and an ambiguous hide lookup returns `TANNING_SLOT_NOT_FOUND`.

```java theme={null}
if (TanningActions.isOpen()) {
    InteractionResult result = TanningActions.tan("Green dragon leather",
        TanningActions.TanningQuantity.ALL);
    if (result.getStatus() == InteractionStatus.PACED) {
        return; // retry from the next tick
    }
}
```

`tan(TanningSlot, TanningQuantity)` clicks a mapped slot (`SLOT_A`..`SLOT_H`); `tan(String hideName, TanningQuantity)` resolves the slot from the hide label. `enterAmount(int)` submits the custom amount after choosing X, and `close()` closes the interface. Because a tan click only queues the menu action, verify the inventory gained leather before repeating.

## Skills reads

`SkillsActions` reads skill levels, experience, and boost state. All methods return safe defaults (0, false, empty) when the client is not ready, and null skill arguments short-circuit:

| Method                                  | Returns                                                   |
| --------------------------------------- | --------------------------------------------------------- |
| `getLevel(Skill)`                       | Real (unboosted) level                                    |
| `getBoostedLevel(Skill)`                | Current boosted level                                     |
| `getExperience(Skill)`                  | XP as `long`                                              |
| `getVirtualLevel(Skill)`                | Level 1-126 from XP                                       |
| `getExperienceToNextLevel(Skill)`       | XP until the next virtual level; 0 at cap                 |
| `getTotalLevel()`                       | Sum of real levels across all skills (excludes `OVERALL`) |
| `getReducedSkills()`                    | Skills where boosted is below real                        |
| `isBoosted(Skill)` / `isDrained(Skill)` | Boost direction checks                                    |

```java theme={null}
if (SkillsActions.isBoosted(Skill.STRENGTH)) {
    log.debug("Strength boost active: {}", SkillsActions.getBoostedLevel(Skill.STRENGTH));
}
```

## Minigame teleports

`MinigameTeleportActions` manages the minigame teleport menus and the shared cooldown. Reads: `isOpen()`, `canTeleport()`, and `getLastMinigameTeleportUsage()`. The `MinigameTeleport` enum covers Pest Control, Last Man Standing, Nightmare Zone, Barbarian Assault, Tithe Farm, Blast Furnace, Castle Wars, Fishing Trawler, Soul Wars, Trouble Brewing, Volcanic Mine, and Guardians of the Rift.

Two execution routes exist:

* **Grouping**: `open()` opens the entry surface, `openDropdown()` expands it, `selectDestination(...)` picks the row, and the final teleport widget executes.
* **Magic**: open and observe the Magic tab yourself, then `open(EntryPath.MAGIC)` casts the minigame teleport spell; on a later tick, selecting a destination row executes the teleport. A closed Magic tab returns `WIDGET_HIDDEN` targeting `tab/MAGIC` without changing tabs.

```java theme={null}
if (MinigameTeleportActions.canTeleport()) {
    if (!TabActions.isOpen(Tab.MAGIC)) {
        TabActions.open(Tab.MAGIC);
        return;
    }
    MinigameTeleportActions.open(MinigameTeleportActions.EntryPath.MAGIC);
    // On a later tick, after the destination list is visible:
    MinigameTeleportActions.teleport(
            MinigameTeleport.CASTLE_WARS,
            MinigameTeleportActions.EntryPath.MAGIC);
}
```

Status codes: `MINIGAME_NOT_OPEN` (interface closed), `WIDGET_HIDDEN` (Magic entry requested with the tab closed), `MINIGAME_ON_COOLDOWN`, `WIDGET_NOT_FOUND`, and `DISPATCHED`. The teleport cooldown applies account-wide across both entry surfaces.

## Sailing

`SailingActions` currently exposes an eight-heading `Direction` enum and the planned API surface; the write and read implementations are placeholders:

* Write methods (`setDirection`, `increaseSpeed`, `decreaseSpeed`, `setSails`, `unsetSails`) return a failed `InteractionResult` with status `UNSUPPORTED`. `setDirection(null)` returns `TARGET_NULL`.
* Read methods (`isNavigating()`, `isMoving()`, `getDirection()`) return neutral placeholders.

Check the result rather than treating a return value as evidence of a direction, speed, or sail-state change.
