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

# Configuration file

Everything is configured in **`config.lua`**, which is loaded as a `shared_script` (available on client and server) and is listed under `escrow_ignore`, so it stays fully editable.

* [General settings](#general-settings)
* [Mini-game difficulty & rewards](#mini-game-difficulty--rewards)
* [Blips](#blips)
* [Discord logging](#discord-logging)
* [Bins & drop tables](#bins--drop-tables)
* [Items & pricing](#items--pricing)
* [World locations (machines & recycling bins)](#world-locations)
* [Localization](#localization--configlocale)
* [NUI appearance](#nui-appearance-html)
* [Developer API](#developer-api)

***

## General settings

```lua
Config.UseTarget = false            -- true = ox_target/qtarget, false = help notif + E
Config.TargetResource = "auto"      -- "ox_target", "qtarget", or "auto" (auto-detect)

Config.CooldownTime = 300           -- bin search cooldown, in seconds (5 min)
Config.InteractionDistance = 2.0    -- distance (m) to interact with a bin/machine
Config.MaxItems = 5                 -- max distinct item types found per successful search
```

| Option                       | Type       | Effect                                                                                                           |
| ---------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------- |
| `Config.UseTarget`           | boolean    | `true` uses a targeting resource; `false` uses proximity loops with `ShowHelpNotification` + control `38` (`E`). |
| `Config.TargetResource`      | string     | Which target resource to bind. `"auto"` prefers `ox_target`, then `qtarget`.                                     |
| `Config.CooldownTime`        | number (s) | How long a searched bin stays on cooldown before it can be searched again.                                       |
| `Config.InteractionDistance` | number (m) | Proximity radius for both the help-notification loops and target `distance`.                                     |
| `Config.MaxItems`            | number     | Caps how many **non-money** reward entries can drop in a single successful search.                               |

> The client caches `Config.CooldownTime` at load. The **server** enforces cooldown with its own hard-coded `COOLDOWN_TIME = 300`. If you change `Config.CooldownTime`, also update the server value for them to agree (see [Server API](#server-side-serverlua)).

***

## Mini-game difficulty & rewards

```lua
Config.GameDifficulty = {
    maxTime = 10,             -- seconds to complete the mini-game
    maxKeys = 5,              -- number of keys shown on screen at once
    pointsPerKey = 10,        -- progress points added per correct key
    successThreshold = 100,   -- % required to "win" (see note)
    keyDisplayTime = 2.0,     -- (passed to NUI; not used by current script.js)
    keyPool = {'W','A','S','D','R','E','Q'},  -- keys the game can pick from

    rewardMultipliers = {
        perfect   = 2.0,      -- score == 100%
        excellent = 1.5,      -- score >= 90%
        good      = 1.2,      -- score >= 80%
        normal    = 1.0,      -- score >= successThreshold
        failed    = 0.5,      -- score below threshold (search "failed")
    }
}
```

**How scoring maps to rewards** (client `CalculateRewardMultiplier`):

| Score %                  | Multiplier used |
| ------------------------ | --------------- |
| `>= 100`                 | `perfect`       |
| `>= 90`                  | `excellent`     |
| `>= 80`                  | `good`          |
| `>= successThreshold`    | `normal`        |
| below `successThreshold` | `failed`        |

The score % sent from the NUI is `score / (maxKeys * pointsPerKey) * 100`. With the defaults (`maxKeys 5 × pointsPerKey 10 = 100`), completing all keys equals 100%.

> ⚠️ With `successThreshold = 100`, the `normal` tier is effectively unreachable — a run is either **perfect** (100% → `perfect`) or treated as **failed** (< 100% → `failed`, half rewards). Lower `successThreshold` (e.g. `80`) if you want a more forgiving payout curve.

The multiplier only ever **increases** item/money amounts (each amount is `floor(baseAmount * multiplier)`, minimum `1`). A "failed" search still applies the `failed` (0.5×) multiplier via `ProcessBinRewards`, but the flow only reaches reward processing when the NUI reports `success` — a timeout reports `success:false` and grants nothing.

***

## Blips

```lua
Config.Blip = {
    ["recycling_machine"] = { name = "Recycling Machine", Sprite = 467, Display = 4, Scale = 1.0, Colour = 25 },
    ["recycling_bin"]     = { name = "Recycling Bin",     Sprite = 478, Display = 4, Scale = 1.0, Colour = 24 },
}
```

One blip is created per entry in `Config.RecyclingLocations`, using the `recycling_machine` style for `type = "bottlemachine"` and `recycling_bin` for `type = "bin"`. See the [FiveM blip references](https://docs.fivem.net/docs/game-references/blips/) for sprite/colour IDs. All blips are set as short-range.

***

## Discord logging

```lua
Config.DiscordLogs = {
    enabled = true,
    botName = "Recycling System",
    avatar  = "https://cdn.discordapp.com/emojis/267659945086812160.png",

    webhooks = {
        system    = "",  -- resource start / stop
        sales     = "",  -- machine & bin sales
        finds     = "",  -- items / money found while searching
        cooldowns = "",  -- when a bin goes on cooldown
    },

    colors = {
        success = 65280,     -- green
        error   = 16711680,  -- red
        warning = 16776960,  -- yellow
        info    = 3447003,   -- blue
    }
}
```

* Paste a webhook URL into each category you want to receive. **Empty strings disable that category** (the server skips sending). The literal placeholder `"TWOJ_DISCORD_WEBHOOK_URL_TUTAJ"` is also treated as "disabled".
* Set `enabled = false` to turn all logging off globally.

**Which action logs where:**

| Category    | Triggered by                                                                |
| ----------- | --------------------------------------------------------------------------- |
| `system`    | Resource start / stop embeds.                                               |
| `sales`     | `sellAllBinItems` / `sellAllMachineItems` payouts.                          |
| `finds`     | Each item found (`giveItem`) and money found (`giveMoney`) while searching. |
| `cooldowns` | `setBinCooldown` when a searched bin goes on cooldown.                      |

***

## Bins & drop tables

`Config.Bins` is a list of **world bin prop models** that can be searched with the mini-game. Each has a `rewards` table.

```lua
Config.Bins = {
    {
        model = "prop_bin_05a",
        rewards = {
            {item = "plastic_bottle", amount = {1, 3}, chance = 70},
            {item = "can",            amount = {1, 2}, chance = 60},
            -- ...
            {item = "money",          amount = {20, 80}, chance = 100},
        }
    },
    -- more bin models...
}
```

**Reward entry fields:**

| Field    | Meaning                                                        |
| -------- | -------------------------------------------------------------- |
| `item`   | Item `name` to give, or the special string `"money"`.          |
| `amount` | `{min, max}` — a random integer in this range is rolled.       |
| `chance` | Percent chance (1–100) the entry drops on a successful search. |

**Resolution rules (client `ProcessBinRewards`):**

* **Money** is handled first. The loop checks reward entries and pays out the **first** `item == "money"` entry that passes its `chance` roll, then stops looking for money.
* **Items** are then rolled in order. Each entry rolls its `chance`; on success the amount is `floor(random(min,max) * multiplier)` (min 1). Item drops stop once `Config.MaxItems` distinct items have been added.
* If no items dropped, the player sees `found_nothing`.

Shipped bin models include: `prop_bin_05a`, `prop_bin_01a`, `prop_bin_02a`, `prop_bin_03a`, `prop_bin_06a`, `prop_bin_07a`, `prop_bin_07d`, `prop_bin_09a`, `bkr_ware03_bin`, `vw_prop_vw_casino_bin_01a`, `prop_bin_delpiero_b`. Add or remove models freely — any world prop matching one of these models becomes searchable.

> Since these are **world props** (not spawned by the resource), search availability depends on the prop existing near the player. In non-target mode the loop uses `GetClosestObjectOfType` for each configured model.

***

## Items & pricing

There are two separate sell tables — one for **machines**, one for **recycling bins** — plus item labels used for notifications.

### `Config.RecyclingMachineItems`

Items sellable at a **recycling machine** (`type = "bottlemachine"`). Each has a `model` (used for the item's visual/label reference) and a `sellPrice`.

```lua
{ item = "plastic_bottle", label = "Plastic Bottle", model = "prop_cs_shopping_bag", sellPrice = 10 },
{ item = "can",            label = "Can",            model = "prop_food_cb_can01",   sellPrice = 6  },
-- glass_bottle 14, aluminum_can 11, electronic_parts 25, copper_wire 18, battery 22
```

### `Config.RecyclingBinItems`

Items sellable at a **recycling bin** (`type = "bin"`), and the label source for found-item notifications.

| Item              | Label            | Sell price |
| ----------------- | ---------------- | ---------- |
| plastic\_bottle   | Plastic Bottle   | 15         |
| can               | Can              | 12         |
| glass\_bottle     | Glass Bottle     | 18         |
| aluminum\_can     | Aluminum Can     | 20         |
| cardboard         | Cardboard        | 8          |
| paper             | Paper            | 5          |
| plastic\_bag      | Plastic Bag      | 3          |
| bottle\_cap       | Bottle Cap       | 2          |
| cloth             | Cloth            | 6          |
| rubber            | Rubber           | 9          |
| scrap\_metal      | Scrap Metal      | 15         |
| electronic\_parts | Electronic Parts | 30         |
| copper\_wire      | Copper Wire      | 25         |
| old\_phone        | Old Phone        | 45         |
| old\_cd           | Old CD           | 8          |
| battery           | Battery          | 35         |
| gold\_watch       | Gold Watch       | 200        |

> Sell prices differ between machines and bins on purpose (e.g. a plastic bottle sells for `10` at a machine but `15` at a bin). Every item you list here must exist in your inventory database (see [Installation](broken://pages/47bdf541feba8bec6aa6d05b53822c3ec54c1279#3-add-the-items-to-your-database)).

***

## World locations

`Config.RecyclingLocations` places the **spawned props** (machines and recycling bins) and their blips.

```lua
Config.RecyclingLocations = {
    { type = "bottlemachine", model = "mr_recyclingmachine", coords = vector3(76.60, -217.33, 54.62), heading = 160.0 },
    -- ... more machines ...
    { type = "bin",           model = "mr_recyclingbin",     coords = vector3(-435.24, -1727.05, 17.87), heading = 160.0 },
    { type = "bin",           model = "mr_recyclingbin",     coords = vector3(2393.60, 3125.16, 47.22),  heading = 335.0 },
}
```

| Field     | Meaning                                                                                            |
| --------- | -------------------------------------------------------------------------------------------------- |
| `type`    | `"bottlemachine"` (sell at machine) or `"bin"` (sell at recycling bin).                            |
| `model`   | Prop model spawned at the location. Defaults use custom `mr_recyclingmachine` / `mr_recyclingbin`. |
| `coords`  | `vector3` spawn position.                                                                          |
| `heading` | Facing angle in degrees.                                                                           |

The client spawns each prop (frozen, collidable), creates its blip, and — in target mode — registers the appropriate target option. On resource stop / restart all spawned props and blips are cleaned up (`DeleteRecyclingObjects`).

> The `mr_recyclingmachine` and `mr_recyclingbin` models are **custom** and come from a separate stream resource. Swap them for base-game props if you don't have those assets.

***

## Localization / `Config.Locale`

All player-facing strings live in `Config.Locale`. Edit values to translate. Strings with `%s` / `%d` are `string.format` templates — keep the placeholders.

Selected keys:

| Key                                                             | Used for                                             |
| --------------------------------------------------------------- | ---------------------------------------------------- |
| `searching_bin`                                                 | Notification when the search starts.                 |
| `found_nothing`                                                 | Search yielded nothing / failed.                     |
| `bin_already_searched`                                          | `%d` = minutes remaining on cooldown.                |
| `bin_busy` / `bin_in_use`                                       | Bin currently used by another player.                |
| `item_missing`                                                  | No recyclable items in inventory (machine/bin sell). |
| `press_to_search` / `press_to_use_machine` / `press_to_use_bin` | Help notifications (`~INPUT_CONTEXT~` = E).          |
| `target_search_bin` / `target_use_machine` / `target_use_bin`   | Target option labels.                                |
| `item_added`                                                    | `%s` item label, `%d` amount.                        |
| `money_added`                                                   | `%d` money amount.                                   |
| `bonus_good_result`                                             | `%d` bonus percent from the multiplier.              |
| `recycled_total`                                                | `%s` item list, `%d` total earnings.                 |
| `not_enough_items` / `no_items_recycled`                        | Sell failures.                                       |

> Some default values contain Polish text in `html/script.js` messages (`'Czas się skończył!'`, `'Gratulacje!...'`). Those NUI messages are internal and are not shown to players via ESX notifications — the player-facing text all comes from `Config.Locale`.

***

## NUI appearance (`html/`)

The mini-game is a self-contained NUI made of three files, whitelisted in `files` and registered via `ui_page`.

### Files

| File              | Role                                                                 |
| ----------------- | -------------------------------------------------------------------- |
| `html/index.html` | Markup: the key stack, the circular progress ring, and the time bar. |
| `html/style.css`  | All styling and animations.                                          |
| `html/script.js`  | The `RecyclingGame` class + NUI message handling.                    |

### How the NUI communicates

**Lua → NUI** (`SendNUIMessage`):

| Message `type`       | Payload                   | Effect                                          |
| -------------------- | ------------------------- | ----------------------------------------------- |
| `openRecyclingGame`  | `{ config = GameConfig }` | Shows the UI, applies config, resets the round. |
| `closeRecyclingGame` | —                         | Hides the UI.                                   |

**NUI → Lua** (`fetch` → `RegisterNUICallback`):

| Callback     | Body                          | Handled by                                           |
| ------------ | ----------------------------- | ---------------------------------------------------- |
| `gameResult` | `{ success, score, message }` | Computes multiplier, grants rewards, closes NUI.     |
| `closeNUI`   | `{}`                          | Cancels the game (Esc), clears tasks, frees the bin. |

The `config` sent to the NUI is the client `GameConfig` (derived from `Config.GameDifficulty`). `script.js` `updateConfig` honours `keyPool`, `maxKeys`, `maxTime`, and `pointsPerKey`.

### Appearance tuning

Edit `html/style.css`. Notable knobs:

| Selector                                       | Controls                                                                                   |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `.game-container`                              | Key-stack width, background tint, corner radius, padding.                                  |
| `.key-container`                               | Each key tile: size, `#3D3D3D` background, white right border.                             |
| `.keys`                                        | The letter tile: size, `#ffffff` background, font, radius.                                 |
| `.progress-circle svg` circles                 | The circular progress ring (`stroke`, `stroke-width`, radius `52.5`, circumference `330`). |
| `.time-bar` / `.time-bar-background`           | The countdown bar look.                                                                    |
| `@keyframes fadeInFromTop` / `fadeOutToBottom` | Key enter/exit animations (0.1s).                                                          |

> **External assets:** `index.html` loads Font Awesome from a CDN and `style.css` imports the Roboto Google Font. If your server runs without internet access on the client, the trash icon and Roboto font may not load — download and bundle them locally (and add them to `files`) if you need a fully offline UI.

### Behaviour notes

* The countdown only starts on the **first correct key press** (`gameStarted`).
* Pressing the **last** (bottom-most) key in the stack is what scores; wrong keys do nothing.
* Reaching 100% progress → `gameCompleted` → `gameResult { success: true }`.
* Running out of time → `gameOver` → `gameResult { success: false }`.
* `Escape` calls `closeNUI` and hides the UI.
* The hard-coded `keyPool` in `script.js`'s constructor is a fallback; the real key set comes from `Config.GameDifficulty.keyPool` via `openRecyclingGame`.

***

## Developer API

> **Exports:** this resource declares **no `exports`** of its own in `fxmanifest.lua`. Integration is done through the **net events** and **ESX server callbacks** below. (It *consumes* `ox_target` / `qtarget` / `es_extended` exports internally.)

### Client-side (`client.lua`)

**Net events you can trigger** (e.g. from a target option or another script):

| Event                              | Data                                         | Effect                                                                                                      |
| ---------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `mr_recycling:searchBin`           | `{ binObject, binConfig }` (or `{ entity }`) | Starts the search flow + mini-game for a bin entity. Resolves `binConfig` from the entity model if omitted. |
| `mr_recycling:useRecyclingBin`     | `{ binObject }` (or `{ entity }`)            | Sells all recyclable bin items in inventory.                                                                |
| `mr_recycling:useRecyclingMachine` | `{ machineObject }` (or `{ entity }`)        | Sells all recyclable machine items in inventory.                                                            |

**Key client functions** (internal, not exported):

| Function                                                          | Purpose                                            |
| ----------------------------------------------------------------- | -------------------------------------------------- |
| `InitializeTargetSystem()`                                        | Detects/binds `ox_target` or `qtarget` per config. |
| `StartRecyclingGame(model, obj, cb)`                              | Opens the NUI mini-game for a bin.                 |
| `ProcessBinRewards(cfg, obj, cb, success, mult)`                  | Rolls & grants rewards after the game.             |
| `HandleRecyclingBin(obj, cb)` / `HandleRecyclingMachine(obj, cb)` | Instant-sell flows.                                |
| `SpawnRecyclingObjects()` / `DeleteRecyclingObjects()`            | Spawn/cleanup props & blips.                       |
| `CreateRecyclingBlips()`                                          | Creates blips from `Config.RecyclingLocations`.    |

**Server callbacks the client uses:** `mr_recycling:checkBinBusy`, `mr_recycling:checkBinCooldown`, `mr_recycling:getRecyclableBinItems`, `mr_recycling:getRecyclableMachineItems`.

### Server-side (`server.lua`)

**Net events (client → server):**

| Event                                  | Params                         | Effect                                             |
| -------------------------------------- | ------------------------------ | -------------------------------------------------- |
| `mr_recycling:giveItem`                | `itemName, count`              | `addInventoryItem`; logs to `finds`.               |
| `mr_recycling:giveMoney`               | `amount`                       | `addMoney`; logs to `finds`.                       |
| `mr_recycling:removeItem`              | `itemName, count`              | Removes items if count sufficient.                 |
| `mr_recycling:sellItems`               | `itemName, amount, sellPrice`  | Sells a single item stack.                         |
| `mr_recycling:sellAllBinItems`         | `recyclableItems[]`            | Bulk-sells bin items; logs to `sales`.             |
| `mr_recycling:sellAllMachineItems`     | `recyclableItems[]`            | Bulk-sells machine items; logs to `sales`.         |
| `mr_recycling:setBinCooldown`          | `coords`                       | Marks a bin on cooldown; logs to `cooldowns`.      |
| `mr_recycling:setBinBusy`              | `coords, busy`                 | Marks/clears a bin as busy (in use).               |
| `mr_recycling:setBinTemporaryCooldown` | `mode('add'/'remove'), coords` | Temp lock while the mini-game is open.             |
| `mr_recycling:startGameForPlayer`      | `targetId`                     | Triggers `recycling:startGame` on a target player. |

**ESX server callbacks (`ESX.RegisterServerCallback`):**

| Callback                                 | Returns                                  | Purpose                                           |
| ---------------------------------------- | ---------------------------------------- | ------------------------------------------------- |
| `mr_recycling:checkBinCooldown`          | `{ isOnCooldown, remainingTime, binId }` | Cooldown state for a bin at `coords`.             |
| `mr_recycling:checkBinBlocked`           | `boolean`                                | Whether a temporary cooldown is active.           |
| `mr_recycling:checkBinBusy`              | `boolean`                                | Whether the bin is currently in use.              |
| `mr_recycling:getRecyclableBinItems`     | `item[]`                                 | Bin-sellable items in the player's inventory.     |
| `mr_recycling:getRecyclableMachineItems` | `item[]`                                 | Machine-sellable items in the player's inventory. |
| `mr_recycling:getItemLabel`              | `label`                                  | Item label from `ESX.Items`, or the raw name.     |

**State model (in-memory):** cooldowns (`binCooldowns`), temporary locks (`temporaryCooldowns`), and busy status (`busyBins`) are stored in server-side Lua tables keyed by `GetBinId(coords)` (rounded `x_y_z`). Busy entries for a player are cleared automatically on `playerDropped`. The server `COOLDOWN_TIME` is a hard-coded `300` — change it there if you change `Config.CooldownTime`.

See [FAQ & Integration](broken://pages/f332095c7d69c38b2c8436045ab814330167c7ae) for copy-paste examples using these events.
