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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://corex-zombies.gitbook.io/corex-docs/resources/foundation/corex-core.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
