> For the complete documentation index, see [llms.txt](https://atiysus-organization.gitbook.io/aty-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://atiysus-organization.gitbook.io/aty-scripts/inventory/features/crafting.md).

# Crafting

## Crafting

Drop one ingredient onto another and the recipe lights up.

#### The gesture

Crafting has no menu. You drag an item onto another item in your own inventory; if the pair names a recipe, the interface shows what it would make, what it costs, which tools are needed and the chance of failure. Confirm and the hands get busy for `time` milliseconds.

Two constraints follow from making it a gesture rather than a menu:

**A drag names two items.** A recipe can need any number of ingredients, but a drag has two ends, so any two of a recipe's ingredients start it — scrap onto plastic or plastic onto scrap, the same recipe answers. What else it needs is taken from the rest of the bag, and the confirm window lists every line with what you have beside it.

**A pair belongs to one recipe.** If scrap onto plastic could make either a lockpick or a crowbar, the drag would have to ask which — and then it is a menu again. The admin panel refuses to save a recipe that shares a pair with another one, and names the recipe it clashes with. `data/recipes.lua` is not checked the same way, so keep the pairs in the file distinct.

{% hint style="info" %}
Anything larger than this — multi-step production, blueprints, timed queues — is a crafting resource's job. This inventory hands it items through the same API every other resource uses.
{% endhint %}

#### Defining a recipe

`data/recipes.lua`:

```lua
Recipes = {
    {
        id          = 'lockpick',
        ingredients = {
            { item = 'scrap',   count = 2 },
            { item = 'plastic', count = 1 },
        },
        results = { { item = 'lockpick', count = 1 } },
        time    = 2500,
    },
}
```

| Field             | Meaning                                                                                                                      |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `id`              | Unique name                                                                                                                  |
| `ingredients`     | Each `{ item, count }`. Add `consume = false` to make it a **tool** — needed, not spent                                      |
| `results`         | What comes out. More than one entry is allowed                                                                               |
| `time`            | Milliseconds the hands are busy. Left out, it is `3000`, as in ox\_inventory. `0` is instant (see below)                     |
| `chance`          | Percent success. Below 100, failure costs the materials                                                                      |
| `keepOnFail`      | Return the ingredients when it fails                                                                                         |
| `label`           | The name shown for it; defaults to the first result's label                                                                  |
| `job` / `grade`   | Only that job, at that grade or above                                                                                        |
| `licence`         | A licence the player must hold                                                                                               |
| `skill` / `level` | A skill level, answered by whichever resource registered `RegisterSkillProvider`; with none registered the recipe is refused |
| `bench`           | `true` = only at a `Config.Workbench` location                                                                               |
| `enabled`         | `false` turns it off without deleting it                                                                                     |

The shipped first-aid recipe demonstrates the long form: three ingredients, one of which is a tool, a failure chance, and waste out the other end.

#### The time it takes

A recipe with a `time` is a timed action, as it is in ox\_inventory. Confirming it closes the inventory and runs a progress bar with ox's crafting animation (`Config.Anim.craft`), on the same bar a meal or a body search uses, so `Config.Consume.progress` picks it too. Nothing is spent while the bar runs. The cancel key (`X`) or dying stops it, and the materials stay where they were.

The server keeps the clock. When the bar reports it has finished, the server checks that the full time has passed, then asks every rule again: the pair must still make the same recipe, the materials must still be there, the bench must still be in reach and the result must still fit. A bar that finished early, or a bag that changed underneath it, crafts nothing.

#### Carrying the result

A craft that would put you over weight is refused **before** the materials are spent, and so is one whose result your job may not hold (`restrictJobs`). Free slots are not reserved ahead: if the bag runs out of slots while the results go in, what was spent stays spent, what was made stays made, and the ledger records exactly that.

#### Two places a recipe can live

| Source                    | Where it is stored  | Who edits it               |
| ------------------------- | ------------------- | -------------------------- |
| `data/recipes.lua`        | The file            | A developer                |
| Admin panel → **Recipes** | `aty_recipes` table | An admin, live, no restart |

Panel recipes are **never written back to the file**. The file stays the developer's copy; the table is the layer on top. Both are read by the same loader, so a recipe behaves identically whichever it came from.

```
invrecipes          list what is loaded, and where each came from
invcraft            SELF-TEST — crafts the shipped recipes in a throwaway
                    stash and checks every way a craft must refuse
invrecipewrite      SELF-TEST — exercises the editor through the panel's
                    own endpoint. It writes nothing to disk.
```

{% hint style="warning" %}
`invrecipewrite` is a test, not an exporter, despite the name. It creates and deletes throwaway recipes to prove the panel's endpoint actually works — a form that posts perfectly to an endpoint that refuses is a form that looks fine and does nothing.
{% endhint %}
