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

# Crafting

Everything in <mark style="color:$primary;">config/crafting.lua</mark>. Crafting happens at stations in the world, queued items keep crafting while the player is away, and every station can have its own recipes, XP progression and skill tree. Stations can also be created and edited live from the admin panel's World tab.

### Global settings

How many crafts one player can have queued per station.

```lua
Config.CraftingQueueSize = 5
```

`true` = recipes the player has not unlocked yet are shown greyed out. `false` = they are hidden completely.

```lua
Config.ShowLockedRecipes = true
```

The shared crafting settings. `enabled = false` turns the whole system off (stations, blueprints, skill trees, craft commands) - saved progress is kept, so turning it back on restores everything.

* `maxBatch` - the biggest amount one craft click can queue.
* `dailyResetHour` - server hour when recipe daily limits reset.
* `levelsPerPoint` - a skill point every this many levels (a station tree can override it).
* `respec` - what a skill reset costs and from which account (a station tree can override it).

```lua
Config.Crafting = {
    enabled = true,
    xp = { ... },
    maxBatch = 10,
    dailyResetHour = 4,
    levelsPerPoint = 2,
    respec = { price = 500, account = "cash" }
}
```

#### XP and levels

Crafting XP is per player per station. `enabled = false` = no XP and no levels, crafting still works normally.

* `maxLevel` - the level cap.
* `curve` - XP needed per level: `base * growth^(level - 1)`.
* `levels` - explicit XP list per level, beats the curve when set.
* `failedXpPercent` - percent of the recipe XP a failed craft still gives.
* `logXp` - write XP gains to the craft logs (spammy).
* `successPerLevel` / `speedPerLevel` - passive percent success and speed gained per level.

```lua
xp = {
    enabled = true,
    maxLevel = 50,
    curve = { base = 100, growth = 1.25 },
    levels = nil,
    failedXpPercent = 0,
    logXp = false,
    successPerLevel = 0.0,
    speedPerLevel = 0.0
}
```

### Stations

Each entry in `Config.CraftingStations` is one station. `label` is the name on screen, `locations` is where it exists in the world, and `categories` are optional tabs that recipes reference by id.

```lua
Config.CraftingStations = {
    workshop = {
        label = "Workshop",
        locations = {
            vector3(1088.6, -2004.3, 30.9)
        },
        categories = {
            { id = "tools", label = "Tools" },
            { id = "medical", label = "Medical" },
            { id = "weapons", label = "Weapons" }
        },
        recipes = { ... },
        tree = { ... }
    }
}
```

### Recipes

A simple recipe only needs `id`, `result`, `time`, `chance` and `ingredients`. Everything else is optional.

* `id` - unique name, used by blueprints, skill nodes and exports.
* `category` - which tab it appears under.
* `result` + `amount` - what the craft produces.
* `time` - seconds per craft.
* `chance` - percent success chance.
* `xp` - XP given on success.
* `ingredients` - list of `{ name, amount }` consumed per craft.

```lua
{
    id = "bandage",
    category = "medical",
    result = "bandage",
    amount = 2,
    time = 6,
    chance = 100,
    xp = 10,
    ingredients = {
        { name = "cloth", amount = 3 }
    }
}
```

#### Optional recipe fields

Locks - who can craft it at all:

* `minLevel` - needs this crafting level at the station.
* `job` + `minGrade` - needs this job (and at least that grade).
* `blueprint = "learn"` - must learn it first by using a blueprint item, then it is known forever.
* `blueprint = "carry"` - must have the matching blueprint item in the inventory every time.
* `hours = { from = 20, to = 6 }` - only craftable between these real server hours.
* `dailyLimit` - most crafts per player per day, resets at `dailyResetHour`.

Result quality:

* `quality` - `{ min, max }` durability range the crafted item rolls.
* `rarity` - a fixed tier (`"common"`) or a weighted list to roll per crafted item.
* `craftedBy` - `true` = the crafter's name is stamped on the item.
* `refundPercent` - percent of ingredients returned when the craft fails.
* `tool` - an item that must be carried but is not consumed.

```lua
{
    id = "suppressor",
    rarity = {
        { id = "common", chance = 85 },
        { id = "rare", chance = 12 },
        { id = "epic", chance = 3 }
    },
    category = "weapons",
    result = "at_suppressor_light",
    amount = 1,
    time = 40,
    chance = 80,
    xp = 50,
    quality = { min = 60, max = 100 },
    craftedBy = true,
    tool = "toolbox",
    ingredients = {
        { name = "scrap_metal", amount = 5 },
        { name = "cloth", amount = 2 }
    }
}
```

#### Minigames

An optional skill check at the start of a craft. Two types ship: `hold` (stop a moving marker inside a zone) and `sequence` (repeat a shown key sequence).

* `mode` - `"modify"` = the result improves or worsens the success chance, `"replace"` = the minigame result replaces the base chance.
* `bonus` / `perfectBonus` - percent added on a hit or a perfect hit.
* `qualityBonus` - extra durability on a perfect hit.
* `speed` / `zone` - marker speed and zone size (`hold`).
* `length` / `showTime` - sequence length and how long it is shown, in milliseconds (`sequence`).

```lua
minigame = {
    type = "hold",
    mode = "modify",
    bonus = 10,
    perfectBonus = 15,
    qualityBonus = 10,
    speed = 1.0,
    zone = 14
}
```

### Skill tree

Optional per station. Players earn a skill point every `levelsPerPoint` levels and spend them on nodes. The tree layout is pure data - the `requires` list draws the connection lines, so chains, branches and merges are just config.

* A node with no `requires` and no `category` is the root skill.
* `cost` - skill points to buy the node.
* `requires` - node ids that must be owned first. Only the root or nodes of the same category may be required.
* `minLevel` - optional level gate on top of the requirements.
* `icon` - built in glyph names (`star`, `bolt`, `refresh`, `layers`, `book`, `gem`, `flask`, `target`, `shield`, `crown`, `clock`, `plus`) or `"item:<name>"` for an item image.

```lua
tree = {
    categories = {
        { id = "efficiency", label = "Efficiency" },
        { id = "mastery", label = "Mastery" }
    },
    nodes = {
        {
            id = "apprentice",
            label = "Apprentice",
            description = "The basics of the workshop.",
            icon = "star",
            cost = 1,
            effects = {
                { type = "success", value = 3 }
            }
        },
        {
            id = "quick_hands",
            label = "Quick Hands",
            description = "Work faster at the bench.",
            icon = "bolt",
            category = "efficiency",
            cost = 1,
            requires = { "apprentice" },
            effects = {
                { type = "speed", value = 10 }
            }
        }
    }
}
```

#### Node effects

Each effect is `{ type, value }`. The catalog is closed - unknown types print a warning on start and are ignored.

* `success` - percent success chance added.
* `speed` - percent craft speed added.
* `xp` - percent extra XP.
* `queueSlots` - extra queue slots.
* `batchSize` - bigger max batch.
* `saveIngredient` - percent chance an ingredient survives the craft.
* `quality` - extra durability on results.
* `rarity` - better rarity rolls.
* `refund` - percent of ingredients returned on failure.
* `unlockRecipe` - `{ type = "unlockRecipe", recipe = "id" }` - the recipe is only craftable with this node.
* `unlockCategory` - same, for a whole category.
* `custom` - does nothing in the inventory but fires the `craftNodeUnlocked` events with its `data`, so your own scripts can build perks on it.

```lua
effects = {
    { type = "unlockRecipe", recipe = "suppressor" },
    { type = "custom", data = { perk = "workshop_discount" } }
}
```
