> For the complete documentation index, see [llms.txt](https://docs.lostdev.store/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lostdev.store/territories/server-modules.md).

# Server Modules

Load order (`fxmanifest.lua` → `server_scripts`):

```
@oxmysql/lib/MySQL.lua
server/bridge.lua
server/validation.lua
server/cache.lua
server/persistence.lua
server/capture.lua
server/admin.lua
server/events.lua
server/main.lua
server/stats.lua
server/dev_commands.lua   -- temporary, remove in production
```

## `bridge.lua` — `ServerBridge`

Abstracts access to the active framework (QBCore, QBX, ESX, or standalone).

* `ServerBridge.DetectFramework()` — detects by the presence of the `qb-core` / `qbx_core` / `es_extended` resources.
* `ServerBridge.Initialize()` — initializes the bridge for the configured (`Config.Framework.type`) or detected framework.
* `ServerBridge.GetPlayerData(source)` — returns `{ source, citizenid/identifier, name, gang, gangLabel, job, jobLabel }`.
* `ServerBridge.GetPlayerGroup(source)` — returns the player's **effective faction**: `{ type = 'gang'|'job', name, label }`. Prioritizes gang over job (job only counts if the player has no gang, or it's `'none'`).
* `ServerBridge.HasFrameworkPermission(source, groups)` — checks whether the player belongs to one of the framework's permission groups (used by the admin panel).
* `ServerBridge.GiveMoneyReward(source, amount)` / `GiveItemReward(source, itemName, amount)` — reward delivery (not called automatically by `Capture.lua`, which fires `territories:reward:*` events instead of calling this directly — see [Territories § rewards](broken://pages/f5f562e03d137b96e09a107d3fa511d6208e8d30#rewards)).

{% hint style="info" %}
**Historical note (fixed bug):** QBX uses `exports['qbx_core']:GetPlayer(source)` directly — there is **no** "core object" to cache like in QBCore. QBCore is imported as `exports['qb-core']` (with a hyphen), not `qbcore`. See [`ADMIN_AUTH_GUIDE.md`](broken://pages/b95ea9d3317d902fbca11ec2078e9e5653313ef6) for the full history.
{% endhint %}

## `validation.lua` — `Validation`

Central layer for security and state checking.

* `Validation.IsValidSource(source)` / `IsPlayerOnline` — player is actually connected.
* `Validation.CanPlayerCapture(source, territoryId)` — full check before starting a capture: territory exists, capture is enabled, player is inside the zone, no cooldown blocking, not already the owner, and is authorized (`allowedCapturers`). Returns `(false, Constants.ERROR_CODES.X)` on failure.
* `Validation.IsPlayerAuthorizedForTerritory(source, territoryId)` — applies `allowedCapturers.custom` / `.gangs` / `.jobs` for the territory.
* `Validation.GetPlayerGroupSync(source)` — **preferred entry point** for getting a player's faction; goes through `PlayerCache` instead of hitting the framework each time.
* `Validation.FetchPlayerGroupFromFramework(source)` — the only place "allowed" to hit the framework directly (used by `PlayerCache` to populate the cache).
* `Validation.GetOnlinePlayersInFaction(factionName)` — online `source`s for a faction (via `PlayerCache`'s reverse index, no `GetPlayers()`).
* `Validation.DetectExploit(source)` — simple event-spam detection (>10/second).
* `Validation.IsValidCoordinate` / `IsValidRadius` — used when creating zones via Admin.

## `cache.lua` — `PlayerCache`

In-memory cache of each online player's faction (gang/job), with a reverse index by faction — avoids hitting the framework or scanning `GetPlayers()` on every tick.

* `PlayerCache.Get(source)` — reads from the cache; if empty, calls `Refresh` automatically.
* `PlayerCache.Refresh(source)` — forces a fresh framework lookup, updates the cache **and** re-sends the faction to the client via `territories:receiveMyGroup` (needed for the client-side blip blacklist).
* `PlayerCache.Set(source, group)` — updates the cache + reverse index.
* `PlayerCache.Remove(source)` — called on `playerDropped`.
* `PlayerCache.GetOnlineInFaction(factionName)` — reads the reverse index.

Invalidated automatically by: `playerDropped`, `QBCore:Server:OnJobUpdate`, `QBCore:Server:OnGangUpdate`, `esx:setJob`.

## `persistence.lua` — `Persistence`

All MySQL interaction (via `oxmysql`/`MySQL.*.await`).

* `Persistence.Initialize()` — creates the 3 tables (`CREATE TABLE IF NOT EXISTS`) and runs column migrations (`Persistence.EnsureColumn`) for older installs.
* `Persistence.SaveTerritory` / `SaveAllTerritories` — `INSERT ... ON DUPLICATE KEY UPDATE` for `territories_data`.
* `Persistence.LoadTerritories()` — loads saved state on top of the territories already registered from `config/territories.lua`.
* `Persistence.LoadCustomZones` / `SaveCustomZone` / `DeleteCustomZone` — zones created via Admin.
* `Persistence.LogHistoryEvent(territoryId, eventType, faction, previousOwner, isAdmin, reason)` — **single source of truth** for the tablet's My Faction/Statistics/History tabs. `eventType` ∈ `'captured' | 'defended' | 'lost' | 'admin_override'`.
* `Persistence.GetRecentActivity` / `GetTerritoryHistory` — activity feeds.
* `Persistence.GetFactionStats(faction)` — captures, defenses, losses, win rate.
* `Persistence.GetFactionStreak(faction)` — consecutive win streak (excludes `admin_override`).
* `Persistence.GetFactionLastCaptureTimes` / `GetFactionDailyActivity` — used by [`stats.lua`](#statslua--stats) for "how long have you held X" and the daily-activity chart.

## `capture.lua` — `Capture`

Logic for starting/ending a capture, cooldown, and rewards.

* `Capture.StartCapture(source, territoryId)` — validates with `Validation.CanPlayerCapture`, switches `state` to `CAPTURING`, fires `territories:captureStarted`, and notifies the owning faction (`NotifyUnderAttack`).
* `Capture.StopCapture(territoryId)` — reverts to `NEUTRAL`.
* `Capture.CompleteCapture` — an alternate variant used by other flows (the "official" capture-completion logic lives in `TerritoryManager.CompleteCapture`, in `main.lua`; this function exists for the same effect when called directly from another flow).
* `Capture.StartCooldown` / `EndCooldown` / `GetCooldownRemaining`.
* `Capture.GiveRewards(territoryId)` — for each owning-faction player present in the zone, calls `GivePlayerRewards`, which fires the `territories:reward:money` / `:reward:item` / `:reward:experience` events (see [Territories § rewards](broken://pages/f5f562e03d137b96e09a107d3fa511d6208e8d30#rewards) regarding the need for an external listener).
* `Capture.NotifyUnderAttack(territoryId, territory, attackerGroup)` — warns **every online player of the owning faction**, wherever they are on the map (banner + sound + flashing blip).
* `Capture.NotifyError(source, errorCode)` — translates a `Constants.ERROR_CODES.*` into the localized message and sends it via `territories:notification`.

## `admin.lua` — `Admin`

See dedicated documentation in [Admin Panel](broken://pages/00311225da68fbeb0db4688dff6f5a62a0475741).

## `events.lua`

Registers the "core" network events that connect client ↔ server:

`territories:requestTerritoryData`, `territories:requestMyGroup`, `territories:getTerritoryInfo`, `territories:playerEntered`/`playerExited`, `territories:startCapture`, among others.

See the full list in [Events & Exports](broken://pages/45047c47c6f21c126c33711bdc4c80b000b95d77).

## `main.lua` — `TerritoryManager`

The core engine. See [Architecture](broken://pages/0932a24e8dc1ab5a3a9ed15f0a944d7ec4671178#server-main-loop) for the processing loop.

Key functions:

* `TerritoryManager.Initialize()` — starts the bridge, loads territories (config + custom zones + database), starts the monitoring loop, and registers listeners.
* `TerritoryManager.RegisterTerritory(id, config)` — builds the runtime state of a territory from its config (used both at startup and by `Admin.CreateZone`).
* `TerritoryManager.ProcessTerritories()` / `ProcessCapture` / `ProcessCooldown` — the system's "tick".
* `TerritoryManager.CalculateProgressDelta` — the capture-speed math (see the extensive comment in the source code about `progress`'s semantics depending on whether the territory is neutral or owned).
* `TerritoryManager.CompleteCapture` / `LoseOwnership` — state transitions that change the `owner`.
* `TerritoryManager.BroadcastBlipState` — syncs blip appearance to **all** clients (see [Architecture](broken://pages/0932a24e8dc1ab5a3a9ed15f0a944d7ec4671178#data-distribution-client-sync)).
* **Exports**: `GetTerritory`, `GetTerritories`, `GetTerritoryOwner`, `IsPlayerInsideTerritory`, `IsTerritoryContested` — see [Events & Exports](broken://pages/45047c47c6f21c126c33711bdc4c80b000b95d77#exports).

## `stats.lua` — `Stats`

Builds the tablet's data (My Faction, Statistics, Activity Feed) **entirely** from `Persistence` (`territories_history`) — nothing is invented; if the table is empty, the tablet shows an empty state.

* `Stats.BuildActivityPayload(rows)` — converts raw SQL rows into the shape the `ActivityFeed.jsx` component expects.
* `Stats.BuildFactionStats(faction)` — aggregates captures/defenses/losses/win rate/streak/longest control time/daily activity.
* Events: `territories:requestTabletExtras` (main tablet payload), `territories:requestTerritoryHistory` (history for one specific territory).

## `dev_commands.lua`

{% hint style="warning" %}
**Development/solo-testing only.** See [Development Commands](broken://pages/a71202d04b21c0138903137e62454654fcdc8780).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.lostdev.store/territories/server-modules.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
