> ## 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.

# Installation

> Drop-in install in under five minutes.

## Asset download

<Info>
  To find the asset, you must have made the purchase using your own keymaster account. Otherwise, you can use the transfer system to move the asset to a different keymaster account.
</Info>

Once the purchase is made on our official website, you will receive your asset directly in your [cfx portal](https://portal.cfx.re/) in your own panel.

## Dependencies

Install these **before** Awoken Apartments:

| Resource                                    | Required for                              |
| ------------------------------------------- | ----------------------------------------- |
| `ox_lib`                                    | Always                                    |
| `oxmysql`                                   | Always (billing + ESX stash)              |
| `ox_target` *(or `qb-target` / `interact`)* | Door interactions                         |
| `qbx_core` / `qb-core` / `es_extended`      | Whichever framework you use               |
| `ox_inventory` *(recommended)*              | Apartment stash (works on ESX and qbcore) |

<Steps>
  <Step title="Drop in the resource">
    Extract into `resources/[awoken]/awoken_apartments`.

    <Warning>
      The folder name **must** be exactly `awoken_apartments` (with underscore). The resource refuses to start otherwise.
    </Warning>
  </Step>

  <Step title="Add to server.cfg">
    ```cfg theme={"dark"}
    ensure oxmysql
    ensure ox_lib
    ensure ox_target
    ensure awoken_apartments

    # accent colour for markers + admin panel (any hex value)
    setr awoken:primaryColor "#00E5FF"

    # admin permission for /aptmanager (gated by the 'apartments.admin' ace)
    add_ace group.admin apartments.admin allow
    ```

    Make sure `oxmysql` and `ox_lib` load **before** `awoken_apartments`.
  </Step>

  <Step title="Disable conflicting resources">
    If you're using Awoken Apartments as your housing system, stop these:

    <Tabs>
      <Tab title="qbox / qbcore">
        * `qb-apartments`
        * `qbx_properties` *(optional - keep only if you also want owned houses)*

        Also set `startingApartment = false` in `qbx_core/config/client.lua` to stop qbx\_properties from auto-creating GTAO apartments for new characters.
      </Tab>

      <Tab title="ESX">
        * `esx_apartments`
        * `esx_property`
        * `loaf_housing`
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure the framework">
    Open `shared/config.lua`, section 1:

    ```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
    ```

    Defaults match a stock qbox + ox\_inventory + ox\_target server. Adjust for your stack.
  </Step>

  <Step title="Restart server">
    The `awoken_apartment_billing` table is created automatically on first start. Players are auto-assigned a Tier 1 room on connect.
  </Step>

  <Step title="Verify in-game">
    1. Run `/aptmanager` - admin panel should open
    2. Walk near a configured apartment door - target prompt should appear
    3. Enter your apartment - fade + teleport
  </Step>
</Steps>

## Database

<Note>
  Tables are created automatically on resource start. No SQL setup required.
</Note>

The only table created by the core resource:

```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`)
);
```

If `Config.Inventory = 'esx'`, the apartment stash also reads/writes to `addon_inventory` and `addon_inventory_items` (created by `esx_addoninventory`).

## Apartments data

Apartments and motel groups are stored as JSON in:

* `data/apartments.json`
* `data/motel_groups.json`

These files are **auto-managed by the in-game admin panel** ([`/aptmanager`](admin-panel)). Don't edit them manually unless the panel is unavailable.

## Spawn selector coexistence

If you have a spawn selector installed (`qbx_spawn`, `qb-spawn`, `um-spawn`, `vms_spawnselector`, etc.), Awoken auto-detects it and steps back so the selector handles new-character spawn choice instead of force-teleporting them into their apartment.

Configure in [`shared/config.lua` section 5b](configuration#section-5b--spawn-selector-integration):

```lua theme={"dark"}
Config.SpawnSelector = {
    type            = 'auto',   -- auto | none | qbx_spawn | qb-spawn | um-spawn | vms_spawnselector | okokSpawnSelector | lc_spawnselector | esx_skin
    deferToSelector = true,
    exposeCallback  = true,
}
```

| Setting                  | What it does                                                                                                                                                               |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type = 'auto'`          | Detect from installed resources. Recommended.                                                                                                                              |
| `type = 'none'`          | Ignore spawn selectors. Awoken force-spawns new characters.                                                                                                                |
| `deferToSelector = true` | Disable our force-spawn when a selector is detected. Selector handles the choice.                                                                                          |
| `exposeCallback = true`  | Lets your spawn selector offer the apartment as a spawn choice. Set `false` to hide it from selectors. (Actually showing it in the menu also needs the small patch below.) |

### Detection only (default - works out of the box)

With `deferToSelector = true`, new characters go through your spawn selector normally. Awoken still assigns them a Tier 1 room in the background - they just won't auto-teleport into it.

### Apartment as a spawn choice (optional - requires a patch)

To make your apartment appear as a SPAWN OPTION in the selector's UI, the spawn selector needs to call `awoken_apartments:getMySpawnInfo`. Patches for each:

<Tabs>
  <Tab title="qbx_spawn">
    To add the player's apartment as a choice in the qbx\_spawn heist-map UI, patch `qbx_spawn/client/main.lua`:

    <Steps>
      <Step title="Add the awoken spawn entry">
        Inside `RegisterNetEvent('qb-spawn:client:setupSpawns', ...)`, after the properties loop:

        ```lua theme={"dark"}
        if GetResourceState('awoken_apartments') == 'started' then
            local apt
            for _ = 1, 10 do
                apt = lib.callback.await('awoken_apartments:getMySpawnInfo', false)
                if apt then break end
                Wait(100)
            end
            if apt then
                spawns[#spawns + 1] = {
                    label        = apt.label,
                    coords       = apt.coords,
                    awokenSlotId = apt.idx,
                }
            end
        end
        ```
      </Step>

      <Step title="Handle the new field in inputHandler()">
        Replace the existing confirm if/elseif with:

        ```lua theme={"dark"}
        if spawnData.awokenSlotId then
            TriggerEvent('awoken_apartments:teleportInside', spawnData.awokenSlotId)
        elseif spawnData.propertyId then
            TriggerServerEvent('qbx_properties:server:enterProperty', { id = spawnData.propertyId, isSpawn = true })
        else
            SetEntityCoords(cache.ped, spawnData.coords.x, spawnData.coords.y, spawnData.coords.z, false, false, false, false)
            SetEntityHeading(cache.ped, spawnData.coords.w or 0.0)
        end
        ```

        <Note>
          Use `teleportInside` directly here, **not** `animateEnter`. `teleportInside`
          handles the screen fade, the routing-bucket instance and interior placement
          on its own. `animateEnter` gates the teleport behind a key-fob progress bar,
          which gets interrupted by qbx\_spawn's spawn fade/camera teardown - so the
          teleport never fires and the player is left standing at the door.
        </Note>
      </Step>

      <Step title="Disable the force-spawn">
        In [`shared/config.lua`](configuration#section-5--new-character-spawn):

        ```lua theme={"dark"}
        Config.NewCharacterSpawn.enabled = false
        ```

        qbx\_spawn now handles the spawn choice.
      </Step>
    </Steps>
  </Tab>

  <Tab title="qb-spawn">
    Similar pattern - `qb-spawn` uses spawn locations defined in its config plus apartment lookups. Add to its spawn list builder:

    ```lua theme={"dark"}
    if GetResourceState('awoken_apartments') == 'started' then
        local apt = lib.callback.await('awoken_apartments:getMySpawnInfo', false)
        if apt then
            table.insert(locations, {
                identifier  = 'awoken_apt',
                name        = apt.label,
                coords      = apt.coords,
                _awokenApt  = apt.idx,
            })
        end
    end
    ```

    And in the confirm handler:

    ```lua theme={"dark"}
    if location._awokenApt then
        TriggerEvent('awoken_apartments:teleportInside', location._awokenApt)
        return
    end
    ```

    <Note>
      Use `teleportInside`, not `animateEnter` - the latter's progress bar fights
      the spawn fade/camera teardown and the teleport never fires. `teleportInside`
      handles the fade and interior placement itself.
    </Note>
  </Tab>

  <Tab title="um-spawn">
    `um-spawn` (the uyuyorum {um} multichar + spawn selector) ships with an open bridge system, so adding awoken takes 3 file edits plus 1 new file. All inside `um-spawn`'s `escrow_ignore` list, so they're safe to edit.

    <Steps>
      <Step title="Add awoken to the apartments catalog">
        Create `um-spawn/bridge/apartments/list/awoken_apartments.lua`:

        ```lua theme={"dark"}
        if GetResourceState('awoken_apartments') ~= 'started' then return end
        if IsDuplicityVersion() then return end

        Debug('awoken_apartments for apartments list ready', 'debug')

        CreateThread(function()
            local apt
            for _ = 1, 30 do
                Wait(500)
                local ok, info = pcall(function()
                    return lib.callback.await('awoken_apartments:getMySpawnInfo', false)
                end)
                if ok and info then apt = info break end
            end
            if not apt then return end

            UM_apartments = UM_apartments or {}
            UM_apartments[apt.label] = {
                type     = 'awoken',
                id       = apt.idx,
                coords   = apt.coords,
                text     = apt.label,
                image    = 'https://files.fivemerr.com/images/2fec38dd-47bc-41d8-b146-2a7e8e726039.png',
                features = { beds = '1', bath = '1', sqft = '1' },
                desc     = 'Your assigned apartment.',
                star     = 4,
                tag      = 'rent',
            }
        end)
        ```
      </Step>

      <Step title="Register awoken with the apartments fetcher">
        Edit `um-spawn/bridge/apartments/fetch.lua`. At the bottom of the `APARTMENT_SYSTEMS` table, add:

        ```lua theme={"dark"}
        ['awoken_apartments'] = {
            queryType = 'export',
            exportFunction = function(_, src)
                return exports['awoken_apartments']:getPlayerRoom(src)
            end,
            validator = function(result) return result ~= nil end
        }
        ```

        Then replace the `getApartments` callback at the bottom of the file with:

        ```lua theme={"dark"}
        lib.callback.register('getApartments', function(source)
            local citizenid = GetCitizenID(GetPlayer(source))
            local result = queryApartments(citizenid, source)
            local found = activeSystem.config.validator(result)

            if found then
                Debug('Apartments: Found Apartments (' .. activeSystem.name .. ')')
                return result
            end

            if activeSystem.name ~= 'awoken_apartments' and GetResourceState('awoken_apartments') == 'started' then
                local ok, room = pcall(function() return exports['awoken_apartments']:getPlayerRoom(source) end)
                if ok and room then
                    Debug('Apartments: Found Awoken Apartment (fallback)')
                    return room
                end
            end

            Debug('Apartments: Not Found')
            return false
        end)
        ```
      </Step>

      <Step title="Merge awoken into the houses list">
        Edit `um-spawn/bridge/house/fetch.lua`. Update `queryHouses` to also accept `src`:

        ```lua theme={"dark"}
        local function queryHouses(citizenid, src)
            local config = activeSystem.config
            if config.queryType == 'export' then
                return config.exportFunction(citizenid, src)
            elseif config.queryType == 'sql' then
                return MySQL.query.await(config.query, { citizenid })
            end
            return false
        end
        ```

        Then replace the `getHouses` callback with:

        ```lua theme={"dark"}
        lib.callback.register('getHouses', function(source)
            local citizenid = GetCitizenID(GetPlayer(source))
            local houses = queryHouses(citizenid, source) or {}
            if type(houses) ~= 'table' then houses = {} end

            if GetResourceState('awoken_apartments') == 'started' then
                local ok, room = pcall(function() return exports['awoken_apartments']:getPlayerRoom(source) end)
                if ok and room then
                    houses[#houses + 1] = {
                        id            = 'awoken_' .. tostring(room.slotId),
                        property_name = room.roomLabel or ('Apartment ' .. tostring(room.slotId)),
                        coords        = json.encode({ x = room.coords.x, y = room.coords.y, z = room.coords.z, w = room.coords.w }),
                        type          = 'awoken',
                    }
                    Debug('Houses: Merged Awoken apartment')
                end
            end

            local found = houses[1] ~= nil
            Debug(found and 'Houses: Found Houses' or 'Houses: Not Found')
            return found and houses or false
        end)
        ```
      </Step>

      <Step title="Route the spawn click to awoken">
        Edit `um-spawn/main/client/spawn.lua`. Inside the `placeType == 'properties'` branch of `setPlace()`, add this **before** the existing `if data.id ~= nil then` block:

        ```lua theme={"dark"}
        local awokenPrefixId   = type(data.id)   == 'string' and data.id:match('^awoken_(%d+)$')
        local awokenPrefixType = type(data.type) == 'string' and data.type:match('^awoken_(%d+)$')
        if data.type == 'awoken' or data.system == 'awoken' or awokenPrefixId or awokenPrefixType then
            local slotId = tonumber(awokenPrefixId) or tonumber(awokenPrefixType)
            if not slotId then slotId = tonumber(data.id) or tonumber(data.type) end
            if not slotId then
                local apt = lib.callback.await('awoken_apartments:getMySpawnInfo', false)
                slotId = apt and apt.idx
            end
            if slotId then
                TriggerEvent('awoken_apartments:teleportInside', slotId)
            end
            Wait(500)
            return
        end
        ```

        <Note>
          We use `teleportInside` directly instead of `animateEnter` because um-spawn already runs its own fade/camera sequence - the progress bar in `animateEnter` fights that timing and the teleport never fires.
        </Note>
      </Step>

      <Step title="Configure awoken">
        In [`shared/config.lua`](configuration#section-5b--spawn-selector-integration):

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

        Restart both resources and reconnect (clients cache the old spawn.lua otherwise).
      </Step>
    </Steps>

    <Warning>
      Future `um-spawn` updates may overwrite these edits. Keep copies of the 4 modified files outside the resource so you can re-apply after upgrades.
    </Warning>
  </Tab>

  <Tab title="vms_spawnselector / okokSpawnSelector / lc_spawnselector">
    These are paid escrowed scripts - source code isn't editable. **Use detection-only mode**: set `deferToSelector = true` and configure spawn locations inside the selector's own config. Awoken still assigns rooms; the player just doesn't pick "apartment" from the spawn menu.

    If you want a future-version request added to those scripts, contact their developers and point them at the `getMySpawnInfo` callback.
  </Tab>

  <Tab title="esx_skin">
    `esx_skin` is character creation, not a spawn selector. No spawn integration needed - set `Config.SpawnSelector.type = 'esx_skin'` or `'none'` and use Awoken's force-spawn after character creation.
  </Tab>
</Tabs>

## Optional - MDT / dispatch lookup

Two callbacks for MDT scripts to look up resident addresses:

```lua theme={"dark"}
-- By identifier (license-based)
local addr = lib.callback.await('awoken_apartments:getAddress', false, identifier)
-- returns { slotId, label, citizenid } or nil

-- By citizenid (qbox/qbcore)
local addr = lib.callback.await('awoken_apartments:getAddressByCitizenId', false, citizenid)
-- returns { identifier, slotId, label } or nil
```

For server-to-server access, see [Exports](exports).
