> For the complete documentation index, see [llms.txt](https://corex-zombies.gitbook.io/corex-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://corex-zombies.gitbook.io/corex-docs/resources/foundation/corex-core.md).

# corex-core

> The foundation of COREX. Every other resource depends on this one.

{% hint style="info" %}
**Required:** ✅ Yes · **Load Order:** 2 (after `oxmysql`) · **Version:** `2.0.0`
{% endhint %}

## Quick Facts

| Dependencies | Server Exports | Client Exports | Events Emitted |
| ------------ | -------------- | -------------- | -------------- |
| `oxmysql`    | 26             | 6              | 12+            |

## Overview

`corex-core` owns the player lifecycle (connect → load → save → drop), the money ledger, the metadata layer, StateBag-driven client sync, a server↔client callback system, and world-interaction targeting. Every other COREX resource calls into it via exports.

## Quick Start

Add to `server.cfg` after `oxmysql`:

```cfg
ensure oxmysql
ensure corex-core
```

Then from any dependent resource:

```lua
-- wait until core is ready
while not exports['corex-core']:IsReady() do Wait(100) end

local Corex = exports['corex-core']:GetCoreObject()
```

## Configuration

All keys live in `corex-core/config.lua`.

| Key                            | Type       | Default                                    | Description                                            |
| ------------------------------ | ---------- | ------------------------------------------ | ------------------------------------------------------ |
| `Config.Debug`                 | `boolean`  | `false`                                    | Enable `COREX.Debug.*` console logging                 |
| `Config.AllowMultiSession`     | `boolean`  | `true`                                     | Allow same identifier to have multiple active sessions |
| `Config.FrameworkName`         | `string`   | `'COREX'`                                  | Display name in logs and notifications                 |
| `Config.Version`               | `string`   | `'2.0.0'`                                  | Framework version string                               |
| `Config.PlayerStates`          | `string[]` | `{'loading','active','dead','spectating'}` | Canonical state set enforced by `SetPlayerState`       |
| `Config.DefaultPlayerState`    | `string`   | `'loading'`                                | State applied on connect before data loads             |
| `Config.CallbackTimeout`       | `number`   | `10000`                                    | Ms before a pending callback is dropped                |
| `Config.EventPrefix`           | `string`   | `'corex'`                                  | Namespace prepended to every framework event           |
| `Config.MaxMoney`              | `number`   | `999999999`                                | Ceiling for any single money type                      |
| `Config.OverrideWorldSettings` | `boolean`  | `true`                                     | Apply opinionated world defaults at boot               |
| `Config.Database.Resource`     | `string`   | `'oxmysql'`                                | Which SQL driver to call                               |

## Server Exports

### Core

#### `GetCoreObject()`

Returns the `Corex` singleton containing `Functions`, `Player`, `State`, and more.

```lua
local Corex = exports['corex-core']:GetCoreObject()
```

#### `IsReady()`

Returns `true` once `corex-core` has finished boot.

```lua
if exports['corex-core']:IsReady() then ... end
```

***

### Players

#### `GetPlayer(source)`

Returns the loaded player table for a server ID, or `nil` if not loaded.

**Params:** `source` (`number`) **Returns:** `Player | nil`

```lua
local p = exports['corex-core']:GetPlayer(source)
if p then print(p.name, p.money.cash) end
```

#### `GetPlayers()`

Returns every currently-loaded player keyed by source ID.

```lua
for src, player in pairs(exports['corex-core']:GetPlayers()) do
    print(src, player.name)
end
```

#### `GetPlayerById(identifier)`

Look up a player by their persistent identifier (e.g., `license:abc...`).

```lua
local p, src = exports['corex-core']:GetPlayerById('license:abc123')
```

#### `GetNearbyPlayers(source, maxDistance)`

Returns players within `maxDistance` of `source`.

```lua
for src, entry in pairs(exports['corex-core']:GetNearbyPlayers(source, 5.0)) do
    print(src, entry.distance)
end
```

#### `BroadcastNearby(source, event, ...)`

Fires a client event to every player within the default broadcast radius around `source` (plus the source itself). Use this to push world events to nearby players without iterating yourself.

```lua
-- play a sound on every player near this source
exports['corex-core']:BroadcastNearby(source, 'myres:client:playSound', 'alarm', 0.8)
```

***

### Money

#### `GetMoney(source, type?)`

If `type` is provided (`'cash'` or `'bank'`), returns the number. Otherwise returns the full money table.

```lua
local cash = exports['corex-core']:GetMoney(source, 'cash')
local all  = exports['corex-core']:GetMoney(source)  -- { cash=?, bank=? }
```

#### `AddMoney(source, type, amount)`

Adds `amount` to `type`. Safe — caps at `Config.MaxMoney`.

**Returns:** `boolean` — `false` if the player is not loaded.

```lua
exports['corex-core']:AddMoney(source, 'cash', 250)
```

#### `RemoveMoney(source, type, amount)`

Removes `amount`. Returns `false` if balance is insufficient.

```lua
local ok = exports['corex-core']:RemoveMoney(source, 'bank', 100)
if not ok then return end
```

#### `HasMoney(source, type, amount)`

Non-destructive balance check.

```lua
if exports['corex-core']:HasMoney(source, 'cash', 500) then ... end
```

***

### Stats & Metadata

#### `GetStat(source, key)` / `SetStat(source, key, value)` / `AddStat(source, key, delta)`

Numeric stats (hunger, thirst, fatigue…) with validation.

```lua
exports['corex-core']:SetStat(source, 'hunger', 100)
exports['corex-core']:AddStat(source, 'hunger', -5)
```

#### `GetMetaData(source, key)` / `SetMetaData(source, key, value)`

Flexible key-value storage persisted as JSON. Use for skin, last position, custom flags.

```lua
exports['corex-core']:SetMetaData(source, 'lastPosition', { x=0, y=0, z=70, heading=0 })
local skin = exports['corex-core']:GetMetaData(source, 'skin')
```

***

### Busy Lock (prevents concurrent long actions)

#### `SetBusy(source, label)` / `TrySetBusy(source, label)` / `ClearBusy(source)` / `IsBusy(source)`

```lua
if exports['corex-core']:TrySetBusy(source, 'using_medkit') then
    -- long action here
    exports['corex-core']:ClearBusy(source)
end
```

***

### Player State Machine

#### `SetPlayerState(source, state)` / `GetPlayerState(source)` / `GetPlayersByState(state)`

State must be one of `Config.PlayerStates`.

```lua
exports['corex-core']:SetPlayerState(source, 'dead')
for _, src in ipairs(exports['corex-core']:GetPlayersByState('active')) do ... end
```

***

### Persistence

#### `SavePlayer(source)`

Forces an immediate DB write. Auto-save runs every 5 minutes regardless.

```lua
exports['corex-core']:SavePlayer(source)
```

***

### Vehicle Catalog

#### `GetVehicleCatalog(catalogId)` / `GetVehicleDefinition(catalogId, model)`

Query the shared vehicle metadata registry defined in `shared/vehicles.lua`.

```lua
local cat = exports['corex-core']:GetVehicleCatalog('civilian')
local def = exports['corex-core']:GetVehicleDefinition('civilian', 'sultan')
```

## Client Exports

| Export                                                      | Purpose                                                        |
| ----------------------------------------------------------- | -------------------------------------------------------------- |
| `GetCoreObject()`                                           | Client-side `Corex` instance with `PlayerData` and `Functions` |
| `IsReady()`                                                 | `true` once local player data has loaded                       |
| `AddTarget(entity, options)`                                | Attach an interact prompt to an entity                         |
| `RemoveTarget(entity)`                                      | Detach prompt                                                  |
| `GetVehicleCatalog(id)` / `GetVehicleDefinition(id, model)` | Same as server                                                 |

```lua
local Corex = exports['corex-core']:GetCoreObject()
while not exports['corex-core']:IsReady() do Wait(100) end

print(Corex.PlayerData.name, Corex.PlayerData.money.cash)
```

## Events

### Emitted ⬆

Server-side:

* `corex:server:coreReady(Corex)` — Core boot complete
* `corex:server:ready` — boot complete (no payload alternate)
* `corex:server:playerReady(source, player)` — Player fully loaded from DB
* `corex:server:playerMoneyChanged(source, type, old, new, action)`
* `corex:server:playerMetaDataChanged(source, key, newValue, oldValue)`
* `corex:playerStateChanged(source, newState, oldState)` — `SetPlayerState` fired

Client-side:

* `corex:client:coreReady(Corex)` — Client core initialized
* `corex:client:playerLoaded(PlayerData)` — Local player data loaded
* `corex:client:ready` — ready signal
* `corex:client:moneyChanged(newMoney)` — derived from StateBag `money`
* `corex:client:metadataChanged(newMetadata)` — derived from StateBag `metadata`

Net (server → client):

* `corex:client:setHealth(health)` — one-shot HP assignment
* `corex:notify(message, type, duration, title)` — notification to UI

### Listened ⬇

* `playerConnecting` (FiveM native) — pre-load validation
* `playerDropped` (FiveM native) — auto-save + cleanup
* `corex:server:loadPlayer` — client requesting (re)load
* `corex:callback:request` / `corex:server:callback` — RPC plumbing (internal)

## StateBag Keys

Every player's `Player(src).state` carries these keys. Server is the sole writer. Clients subscribe via `AddStateBagChangeHandler`.

| Key          | Type                           | Writer | Reader | Notes                       |
| ------------ | ------------------------------ | ------ | ------ | --------------------------- |
| `money`      | `{cash: number, bank: number}` | server | all    | Mirrors DB                  |
| `metadata`   | `table`                        | server | all    | Arbitrary JSON              |
| `name`       | `string`                       | server | all    | Display name                |
| `identifier` | `string`                       | server | all    | Persistent ID (`license:…`) |
| `isLoggedIn` | `boolean`                      | server | all    | `true` after data load      |

Example listener:

```lua
local playerBag = ('player:%d'):format(GetPlayerServerId(PlayerId()))
AddStateBagChangeHandler('money', playerBag, function(_, _, value)
    print('cash is now', value.cash)
end)
```

## Database Schema

Source: `corex-core/sql/corex_framework.sql`.

```sql
CREATE TABLE players (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    identifier  VARCHAR(60) NOT NULL UNIQUE,
    name        VARCHAR(50) NOT NULL,
    money       LONGTEXT,    -- JSON: {"cash":0,"bank":0}
    metadata    LONGTEXT     -- JSON: skin, lastPosition, stats, flags…
);
```

## Troubleshooting

**`GetPlayer(source)` returns `nil` inside my handler.** → You're running before the player is loaded. Listen on `corex:server:playerReady` instead of `playerConnecting`.

**Client callback never responds.** → The server handler may be `nil` or raising. Increase `Config.CallbackTimeout` for debugging and check `corex:callback:request` handler logs.

**Money changes aren't appearing on the client.** → StateBag handlers must register **after** `NetworkIsPlayerActive(PlayerId())` returns `true`. Wait for `corex:client:coreReady` before subscribing.

**`SetPlayerState` throws "invalid state".** → Only the values in `Config.PlayerStates` are accepted. Extend that array in config first.

**Changes are lost on crash.** → Auto-save runs every 5 minutes. For critical paths (trades, deaths), call `SavePlayer(source)` immediately.
