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

# Configuration

> Every setting in shared/config.lua, organised by section.

All user-tunable settings live in `shared/config.lua`. The file is split into 18 numbered sections - search by number to jump to a section.

<Tip>
  Apartments themselves, motel groups, and NPC positions are **managed in-game** via [`/aptmanager`](admin-panel) - not via config edits.
</Tip>

## Section 1 - Framework

```lua theme={"dark"}
Config.Framework = 'qbox'        -- qbox | qbcore | esx | standalone
Config.Inventory = 'ox'          -- ox | qb | ps | qs | esx | standalone
Config.Target    = 'ox_target'   -- ox_target | qb_target | interact | standalone
Config.Notify    = 'ox_lib'      -- ox_lib | qb | esx | standalone

Config.PoliceJobs = { 'police', 'sheriff', 'swat' }

Config.Debug = false
```

<ParamField path="Config.Framework" type="string" default="qbox">
  Which framework to integrate with.
</ParamField>

<ParamField path="Config.Inventory" type="string" default="ox">
  Which inventory script handles the apartment stash.
</ParamField>

### Inventory options

| Value          | Inventory                                                                                | Extra setup                                                                                                 |
| -------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `'ox'`         | ox\_inventory                                                                            | None - recommended for both qbcore and ESX                                                                  |
| `'qb'`         | qb-inventory                                                                             | None                                                                                                        |
| `'ps'`         | ps-inventory                                                                             | None                                                                                                        |
| `'qs'`         | qs-inventory                                                                             | None                                                                                                        |
| `'esx'`        | esx\_addoninventory + esx\_inventoryhud **OR** base esx\_inventory + esx\_addoninventory | Uses `addon_inventory_items` SQL table. Base esx\_inventory falls back to a built-in `ESX.UI.Menu` text UI. |
| `'standalone'` | None                                                                                     | Stash UI disabled                                                                                           |

<Warning>
  Base `esx_inventory` alone (without `esx_addoninventory`) has no shared-inventory backend and is not supported. Install one of the listed combinations.
</Warning>

To add a custom inventory not in this list, edit `server/inventory.lua` and `client/inventory.lua` - those are the only two files you need to touch.

<ParamField path="Config.PoliceJobs" type="string[]" default="{&#x22;police&#x22;,&#x22;sheriff&#x22;,&#x22;swat&#x22;}">
  Job names treated as law enforcement. Used for PD door entry and MLO item unlock.
</ParamField>

<ParamField path="Config.Debug" type="boolean" default="false">
  Verbose console logging + enables `/aptbill` debug command. Disable for production.
</ParamField>

## Section 2 - Motel

```lua theme={"dark"}
Config.Motel = {
    spawnPos = vector4(151.412292, -1007.599365, -99.000099, 1.720619),
    stash = {
        coords = vector3(150.9780, -1003.1427, -99.2998),
        radius = 1.2,
    },
}
```

`spawnPos` is the fallback exterior spawn for players who decline the "spawn inside" prompt or whose apartment can't be entered.

## Section 3 - Tiers

```lua theme={"dark"}
Config.Tiers = {
    [1] = { label = 'Standard', monthlyCost = 0    },
    [2] = { label = 'Deluxe',   monthlyCost = 1500 },
    [3] = { label = 'Premium',  monthlyCost = 3000 },
}
```

<Note>
  Tier 1 is **always** free regardless of `monthlyCost`. New characters are always assigned to a free Tier 1 slot.
</Note>

You can add more tiers (`[4]`, `[5]`, …) - the admin panel auto-detects them.

## Section 4 - Billing

```lua theme={"dark"}
Config.Billing = {
    enabled       = true,
    checkInterval = 300,         -- seconds between online billing checks
    paymentSource = 'both',      -- bank | cash | both
}
```

| `paymentSource` | Behaviour                               |
| --------------- | --------------------------------------- |
| `'bank'`        | Bank account only                       |
| `'cash'`        | Cash only                               |
| `'both'`        | Bank first, then cash for any remainder |

### Rent cycle

1. Player upgrades to Tier 2+ via a motel NPC → pays transfer fee + first month's rent
2. A billing record is created in `awoken_apartment_billing` with `next_bill_date = now + 30 days` and `slot_index`
3. Every `checkInterval` seconds (and once on login), the server checks if rent is due
4. **If yes + can pay:** deduct rent, reset `next_bill_date` to `now + 30 days`
5. **If yes + can't pay:**
   * Try a free Tier 1 in the same motel → move them there
   * If none → "pay now or be moved" prompt
   * Decline → move to any free Tier 1, or fully evict if none exist

### Slot persistence

While billing is active (`next_bill_date` in the future):

| Scenario                   | Result                                                 |
| -------------------------- | ------------------------------------------------------ |
| Disconnect → reconnect     | Same paid slot returned                                |
| Server restart → reconnect | Same paid slot restored from DB                        |
| Free Tier 1 disconnect     | Slot becomes available, random new Tier 1 on reconnect |

### Transfers between motels

| Action                     | Cost                         | Billing record                                 |
| -------------------------- | ---------------------------- | ---------------------------------------------- |
| Same tier, different motel | `transferCost` only          | Slot index updated, **30-day cycle preserved** |
| Upgrade tier               | `transferCost + monthlyCost` | New 30-day cycle                               |
| Downgrade to Tier 1        | Free                         | Billing record deleted                         |

### SQL schema (auto-created)

```sql theme={"dark"}
CREATE TABLE `awoken_apartment_billing` (
    `identifier`     VARCHAR(60) NOT NULL,
    `next_bill_date` BIGINT      NOT NULL DEFAULT 0,
    `tier`           INT         NOT NULL DEFAULT 2,
    `slot_index`     INT         DEFAULT NULL,
    PRIMARY KEY (`identifier`)
);
```

`identifier` = citizenid on qbox/qbcore, license on ESX.

### Manual fixes

<AccordionGroup>
  <Accordion title="Player stuck on Tier 2 they shouldn't have">
    ```sql theme={"dark"}
    DELETE FROM awoken_apartment_billing WHERE identifier = 'YOUR_IDENTIFIER';
    ```

    Restart resource - they'll fall to a free Tier 1 on next connect.
  </Accordion>

  <Accordion title="Reset all billing data">
    ```sql theme={"dark"}
    TRUNCATE TABLE awoken_apartment_billing;
    ```
  </Accordion>

  <Accordion title="Force-bill a player (debug)">
    With `Config.Debug = true`:

    ```
    /aptbill          # bill yourself
    /aptbill <id>     # bill another player
    ```
  </Accordion>
</AccordionGroup>

## Section 5 - New Character Spawn

```lua theme={"dark"}
Config.NewCharacterSpawn = {
    enabled                = true,
    forceSpawnInApartment  = true,
    showPrompt             = false,
    promptDelay            = 2000,
}
```

<ParamField path="forceSpawnInApartment" type="boolean" default="true">
  Universal mode - bypass any spawn selector, force-teleport new characters directly inside their assigned apartment.
</ParamField>

<ParamField path="showPrompt" type="boolean" default="false">
  Only used when `forceSpawnInApartment = false`. Shows an "enter your apartment?" dialog.
</ParamField>

<ParamField path="promptDelay" type="number" default="2000">
  Milliseconds before the prompt fires (ignored when `forceSpawnInApartment = true`).
</ParamField>

### New character detection by framework

| Framework     | How it works                                     | Setup needed                                                                             |
| ------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| qbox / qbcore | Reads `PlayerData.newPlayer`                     | None                                                                                     |
| ESX           | Reads `playerData.firstSpawn` / `isNewCharacter` | Recommended: trigger `awoken_apartments:newCharacter` client-side from your char creator |
| Standalone    | No automatic detection                           | Manual trigger only                                                                      |

Manual trigger works on any framework:

```lua theme={"dark"}
TriggerEvent('awoken_apartments:newCharacter')
```

## Section 5b - Spawn Selector Integration

Got a spawn selector? (That's the screen where players pick where to spawn - `qbx_spawn`, `qb-spawn`, `um-spawn`, etc.) This section makes Awoken play nice with it instead of force-teleporting new characters straight into their apartment.

```lua theme={"dark"}
Config.SpawnSelector = {
    type            = 'auto',
    deferToSelector = true,
    exposeCallback  = true,
}
```

<ParamField path="type" type="string" default="auto">
  Which spawn selector you use. `'auto'` figures it out for you (recommended). Only set it by hand if auto-detect guesses wrong: `'none'`, `'qbx_spawn'`, `'qb-spawn'`, `'um-spawn'`, `'vms_spawnselector'`, `'okokSpawnSelector'`, `'lc_spawnselector'`, or `'esx_skin'`.
</ParamField>

<ParamField path="deferToSelector" type="boolean" default="true">
  When `true` and a spawn selector is found, Awoken stops force-spawning new characters and lets your selector decide where they go. They still get assigned a room quietly in the background.
</ParamField>

<ParamField path="exposeCallback" type="boolean" default="true">
  When `true`, your spawn selector is allowed to offer the player's apartment as one of the spawn choices. Set it to `false` to hide the apartment from the selector completely.
</ParamField>

<Note>
  Out of the box (`deferToSelector = true`) this just works - new characters go through your spawn selector like normal. You only need the extra setup below if you want **"My Apartment"** to actually show up as a pick *inside* the selector.
</Note>

### Making the apartment show up as a spawn choice

Listing the apartment in the selector's menu needs a small one-time patch to your spawn selector (it has to ask Awoken for the player's room). Step-by-step patches for each popular selector are in [Installation → Spawn selector coexistence](installation#spawn-selector-coexistence).

<Warning>
  In those patches, always send players inside with `TriggerEvent('awoken_apartments:teleportInside', slotId)` - **not** `animateEnter`. The animation version gets cut off by the spawn screen's fade and the player never actually teleports.
</Warning>

## Section 6 - Doors & Doorbell

```lua theme={"dark"}
Config.Door = {
    radius      = 1.2,
    blip        = false,
    marker      = false,
    markerType  = 1,
    markerAlpha = 80,
}

Config.Doorbell = {
    enabled = true,
    sound   = { name = 'Doorbell_1', set = 'DLC_ApartmentContrabandSounds' },
}
```

Marker colour is automatically derived from the `awoken:primaryColor` convar.

## Section 7 - Keys

```lua theme={"dark"}
Config.Keys = {
    storage = 'globalstate',     -- oxmysql | globalstate
}
```

| Value           | Behaviour                               |
| --------------- | --------------------------------------- |
| `'oxmysql'`     | Persists across restarts                |
| `'globalstate'` | In-memory only, faster, lost on restart |

## Section 8 - Stash

```lua theme={"dark"}
Config.Stash = {
    prefix       = 'awoken_stash_',
    slots        = 50,
    maxWeight    = 100000,        -- grams
    marker       = false,
    markerType   = 1,
    markerAlpha  = 80,
}
```

The `prefix` is prepended to the resident's identifier to form the unique stash ID (e.g., `awoken_stash_ABC1234`).

## Section 9 - Blips

```lua theme={"dark"}
Config.Blips = {
    enabled        = true,
    sprite         = 40,
    colour         = 0,
    scale          = 0.8,
    shortRange     = true,
    label          = '',          -- empty = use apartment label
    hideWhenInside = true,
}
```

## Section 10 - /aptshow Command

```lua theme={"dark"}
Config.ShowApartmentCommand = {
    enabled  = true,
    command  = 'aptshow',
    duration = 60000,             -- ms; 0 = toggle
    marker = {
        type   = 2,
        alpha  = 180,
        width  = 0.8,
        height = 0.8,
    },
    blip = {
        sprite    = 40,
        colour    = 5,
        scale     = 1.0,
        label     = 'My Apartment',
        showRoute = true,
    },
}
```

`/aptshow` drops a temporary blip + 3D marker on the player's own apartment door.

## Section 11 - Motel NPC Blips

```lua theme={"dark"}
Config.MotelNpcBlips = {
    enabled    = true,
    sprite     = 40,
    scale      = 0.8,
    shortRange = true,
    colours = { 2, 17, 38, 46, 5, 8, 29, 42 },
}
```

Each motel group's clerk NPC gets a unique blip colour cycled from the list (groups sorted alphabetically for consistency).

## Section 12 - Animations

```lua theme={"dark"}
Config.Animations = {
    enabled   = true,
    canCancel = false,

    lock     = { duration = 1500, dict = 'anim@mp_player_intmenu@key_fob@', clip = 'fob_click', flag = 49 },
    enter    = { duration = 1800, dict = 'anim@mp_player_intmenu@key_fob@', clip = 'fob_click', flag = 49 },
    exit     = { duration = 1200, dict = 'anim@mp_player_intmenu@key_fob@', clip = 'fob_click', flag = 49 },
    transfer = { duration = 3000 },
}
```

Each action (lock, enter, exit, transfer) uses `lib.progressBar` with the matching duration + animation.

## Section 13 - MLO Support

```lua theme={"dark"}
Config.MLO = {
    enabled          = true,
    pdItem           = 'lockpick',
    pdUnlockDuration = 300,       -- seconds
    autoRegisterItem = true,
    doorSearchRadius = 5.0,       -- metres
}
```

<ParamField path="autoRegisterItem" type="boolean" default="true">
  Auto-hook ox\_inventory and qb-inventory so the `pdItem` triggers MLO door unlock for police. For other inventories, fire `TriggerClientEvent('awoken_apartments:usePdItem', src)` from your item-use callback.
</ParamField>

## Section 14 - Police Access

```lua theme={"dark"}
Config.PdAccess = {
    item  = 'lockpick',
    knock = {
        enabled   = true,
        duration  = 10000,         -- ms
        canCancel = false,
        label     = 'Knocking on door...',
        dict      = 'missheistfbi3b_ig7',
        clip      = 'lift_fibagent_loop',
        flag      = 1,
    },
}
```

Set `item = ''` to remove the item requirement entirely. Set `knock.enabled = false` to skip the knock animation and go straight to the access request.

## Section 15 - Discord Logging

```lua theme={"dark"}
Config.Discord = {
    enabled    = false,
    footerText = 'awoken_apartments',
    serverName = '',
    webhooks   = {
        general  = '',
        security = '',
        admin    = '',
    },
}
```

| Webhook    | What it logs                                               |
| ---------- | ---------------------------------------------------------- |
| `general`  | Slot assignments, transfers                                |
| `security` | PD access events                                           |
| `admin`    | Admin panel actions (add/delete rooms, transfer residents) |

Webhooks can point at the same or different channels.

## Section 16 - Locale

```lua theme={"dark"}
Config.Locale = 'en'
```

## Section 18 - Locale Strings

All in-game text lives under `Config.Locales[Config.Locale]`. To add a translation:

```lua theme={"dark"}
Config.Locales.fr = {
    no_access = "Vous n'avez pas accès à cet appartement.",
    -- ... etc
}

Config.Locale = 'fr'
```

<Warning>
  Keep the `%s` placeholders in place - they're filled in by the script with dynamic values like apartment labels and prices.
</Warning>

## Primary colour convar

In your `server.cfg`:

```cfg theme={"dark"}
setr awoken:primaryColor "#00E5FF"
```

This drives the accent colour for:

* All 3D world markers (door zones, stash markers, /aptshow chevron)
* The admin panel UI (tabs, buttons, borders, glow)

Change the convar and restart the resource - everything re-themes.

## Wardrobes & clothing

Awoken Apartments does **not** come with its own wardrobe or clothing menu. That's on purpose - it keeps out of your appearance system so the two never clash. If you want players to change outfits inside their apartment, you add the wardrobe yourself using **your own clothing script** (illenium-appearance, fivem-appearance, qb-clothing, etc.).

The normal way is to add a small interaction point inside the apartment interior that opens your clothing menu. Here's the shape of it using `ox_target`:

```lua theme={"dark"}
exports.ox_target:addBoxZone({
    coords = vec3(-- your interior wardrobe spot --),
    size   = vec3(1.0, 1.0, 2.0),
    rotation = 0.0,
    options = {
        {
            label    = 'Open Wardrobe',
            icon     = 'fa-solid fa-shirt',
            onSelect = function()
                -- replace this with YOUR clothing script's wardrobe command:
                exports['illenium-appearance']:openOutfitMenu()
            end,
        },
    },
})
```

<Steps>
  <Step title="Get the interior coordinates">
    In `/aptmanager`, the interior spawn point is set per room (MLO) or per tier (IPL). Use those same coords for the wardrobe spot. Stand where you want it and read off your position, or grab it with the panel's `🎯 Pick`.
  </Step>

  <Step title="Swap in your clothing script's command">
    Replace the `exports['illenium-appearance']:openOutfitMenu()` line with whatever your clothing script uses to open its wardrobe - check that script's own docs for the exact export or event name.
  </Step>

  <Step title="Put it in your own resource">
    Add the code to one of your own resources (or your clothing script's config), not inside the encrypted awoken files. Restart it and the wardrobe point appears.
  </Step>
</Steps>

<Note>
  IPL apartments share one interior per tier (each player is isolated in their own routing bucket), so a single wardrobe point at that interior covers every room of that tier automatically. MLO apartments each have their own physical interior, so add one per MLO.
</Note>
