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

# TERRITORIES

Gang territories captured by armed presence inside polygon zones, with fights, rankings, workshops, drug markets, laundering and a timed Black Zone.

Gang territories captured by presence, with fights between organizations, rankings, stations for weapons, drugs and laundering, and a Black Zone that only opens at night.

{% embed url="<https://www.youtube.com/watch?v=VIDEO_ID>" %}

***

## ✅ Requirements

|                  |                                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **Frameworks**   | ESX, QBCore, QBX (detected automatically). Standalone works with a gang resolver in `config/framework/sv-framework.lua`. |
| **Dependencies** | [ox\_lib](https://github.com/overextended/ox_lib), [oxmysql](https://github.com/overextended/oxmysql)                    |
| **Inventories**  | ox\_inventory, qs, ps, lj, qb-inventory, codem, or the framework's own inventory                                         |

***

## 📥 Installation

```
ensure ox_lib
ensure oxmysql
ensure s_territories
```

There is no SQL file. The script creates `s_t_territories` and `s_t_org_stats` on first start.

The items in `Activities` (`weapon_parts`, `weed_bag`, `black_money`, and so on) are examples. Replace them with items that exist in your inventory before players use a station.

***

## ⚙️ Configuration

Everything in `config/` is open. Every option in `config/config.lua` is commented in the file; this page covers the ones that need more than a comment.

### Organizations

The keys of `Organizations` are job names on ESX, and gang names or job names on QBCore and QBX (the gang is checked first). The `type` decides which station the org operates and which pool of `Activities` it gets.

```lua
Organizations = {
    ballas  = { label = 'Ballas',  color = '#a855f7', color_blip = 27, type = 'drugs' },
    madrazo = { label = 'Madrazo', color = '#a593ad', color_blip = 14, type = 'weapons' },
},
```

`PoliceJobs` takes a plain list of job names, or a map with the label and colour the fights tab shows:

```lua
PoliceJobs = { 'police', 'sheriff' }
PoliceJobs = { police = { label = 'LSPD', color = '#3b82f6' } }
```

Cops inside a territory drain influence and can join fights. They never capture and never score.

### Territories

Every territory is two polygons. The inner one (`zone`) is where presence counts for the capture and where defenders stage for a fight; the outer one (`zoneOuter`) is the whole territory for the HUD and the ring of a fight. Without `zoneOuter` the inner polygon serves as both.

```lua
{
    id              = 'marine',
    label           = 'Marine Port',
    coords          = vec3(-387.65, -2679.51, 6.00),   -- blip and map pin
    station_coords  = vec3(-480.38, -2773.93, 5.00),
    station_prop    = `prop_tool_bench02`,             -- optional
    station_heading = 227.73,
    zone      = { thickness = 60.0, points = { vec3(...), vec3(...), vec3(...) } },
    zoneOuter = { thickness = 60.0, points = { vec3(...), vec3(...), vec3(...) } },
},
```

Draw the polygons in game with the ox\_lib zone creator: run `/zone poly`, press Space on each vertex, Shift and scroll to set the height, Enter to export. Paste the `vec3` list into `points`. `thickness` is the height of the zone; the server only checks X and Y, so a tall zone on a mountain costs nothing.

{% hint style="warning" %}
A territory without `zone.points` prints a warning on start and has no capture, no HUD and no fights. Only its station works.
{% endhint %}

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-03819a6855580bde4f4e8b18c37972c83e333894%2Fmap.jpg?alt=media" alt="The territories tab: pins coloured by owner, the Black Zone closed, the tooltip of a territory with its leader and influence."><figcaption><p>The territories tab: pins coloured by owner, the Black Zone closed, the tooltip of a territory with its leader and influence.</p></figcaption></figure>

Any organization can capture any territory and operates its own activity at its station. Add `activity = 'drugs'` to an entry to reserve it for orgs of that type.

The tooltip image of each territory is `web/imgs/territories/<id>.webp`. Add one with the id of a new territory.

### Capture

Influence moves once per `TickIntervalMs` by the difference between the two biggest orgs in the inner zone, as long as the biggest has `MinMembersToCapture` members. At 100% the territory is controlled. An org that already controls `MaxTerritoriesPerOrg` territories can still push a third one, but its influence stops at 99%.

A player only counts while holding a weapon of one of `CaptureWeaponGroups` and, with `CaptureOnFoot = true`, on foot. The HUD says why a player is not counting. Set `CaptureWeaponGroups = {}` to count everybody.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-30a4b0d16f08f21e814c41eb5eed3183cfebb066%2Fhud.jpg?alt=media" alt="Inside a controlled territory: the corner HUD with the owner, the influence bar, the orgs present and whether your presence counts."><figcaption><p>Inside a controlled territory: the corner HUD with the owner, the influence bar, the orgs present and whether your presence counts.</p></figcaption></figure>

### Black Zone

The entry with `kind = 'blackzone'` is a territory that only exists inside its time window, in the server's local time. Outside the window it is neutral and frozen and the blip is gone. Inside it, any org captures it, and its station only opens for the org that controls it, with the numbers in its `bonus` table.

```lua
{
    id = 'stab_city', label = 'Stab City', kind = 'blackzone',
    open_hour = 20, open_minute = 0, close_hour = 0, close_minute = 0,   -- 20:00 to 00:00
    bonus = { craft_output_mult = 2, drug_payout_mult = 2, launder_amount = 100000, launder_loss_pct = 0.05 },
    -- coords, station_coords, zone, zoneOuter as any territory
},
```

Remove the entry to run without a Black Zone.

### Stations

`Activities` holds one pool per org type: recipes for `workshop`, items for `market`, the batch for `bank`. The station of a territory opens the pool of the player's org, so a weapons org crafts weapons at every station.

The numbers depend on who controls the ground:

| Situation                                       | Table used            |
| ----------------------------------------------- | --------------------- |
| Black Zone, open, controlled by the org         | the entry's `bonus`   |
| Territory controlled by the org                 | `TerritoryOwnerBonus` |
| Territory controlled by another org, or neutral | `NonOwnerPenalty`     |

`craft_input_pct` multiplies the components (0.75 is 25% fewer, 1.10 is 10% more; a component of 1 never changes), `craft_output_mult` multiplies what the craft gives, `drug_payout_mult` multiplies the payout, `launder_amount` and `launder_loss_pct` replace the batch. A missing key means the base value. Remove `NonOwnerPenalty` to charge base values to everyone who does not control the territory.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-74d73bf044854a721460fd2a415662572d77a1c3%2Fcraft.jpg?alt=media" alt="The workshop tab: the recipes of the org with their components, and the craft queue with one item processing."><figcaption><p>The workshop tab: the recipes of the org with their components, and the craft queue with one item processing.</p></figcaption></figure>

`StationMinRank` is the minimum job or gang grade to use a station. `ItemImageUrl = 'auto'` reads the item images of your inventory; set a template like `'nui://my_inventory/images/%s.png'` for another one.

The station opens with a text prompt and the key in `StationInteractKey`, within `Marker.interact_distance`. `Marker.enabled = true` draws a marker as well. To use a target or any other interaction, replace `RegisterStationInteraction` and `RemoveStationInteraction` in `config/cl-functions.lua`; an ox\_target version is in the file, commented. The script only decides who may use the station, the hook decides how.

### Fights

A fight is a last team standing between two sides inside a territory. Defenders stage in the inner zone, attackers in the outer zone. The first player to join creates the lobby; the countdown of `ArmCountdownSec` starts when everyone pressed Ready and each side has `MinPerSide`. Leaving the ring during the fight gives `LeaveGraceMs` to come back, then the player dies. When the fight ends the territory is immune to new fights for `CooldownSec`; the winner does not take the territory.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-af69b83fb3f9ffb24eb84d1e765a946008e445b7%2Ffights.jpg?alt=media" alt="The fights tab in the lobby: defenders almost ready, attackers joining, the five per side warning and the scoreboard in the corner."><figcaption><p>The fights tab in the lobby: defenders almost ready, attackers joining, the five per side warning and the scoreboard in the corner.</p></figcaption></figure>

The death on leaving the ring is `Framework.KillPlayer` in `config/framework/sv-framework.lua`. By default it zeroes the player's health, which any ambulance script picks up. Point it at your medical script if it needs the cause.

### Rankings

The ranking tab counts, per organization and over the last `RankingDays`: fights won, hours in control of territories, and value produced at stations. Drug sales and laundering count their payout. A craft counts `ItemValue[output] * qty` minus the value of its components; without `ItemValue`, crafts count zero.

<figure><img src="https://3876653196-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwqRMsiuM4yL1gQLB2nlA%2Fuploads%2Fgit-blob-98f92f9c17e614bd4ae4b2033f6cb120de24a7e6%2Franking.jpg?alt=media" alt="The ranking tab over the last 30 days: fights won, time in control and value produced per organization."><figcaption><p>The ranking tab over the last 30 days: fights won, time in control and value produced per organization.</p></figcaption></figure>

***

## 💬 Commands

| Command                                      | Who      | What it does                                |
| -------------------------------------------- | -------- | ------------------------------------------- |
| `/territories`                               | Everyone | Opens the tablet                            |
| `/territories_reset <id>`                    | Admin    | Territory back to neutral                   |
| `/territories_setowner <id> <org>`           | Admin    | Territory to 100% for an org                |
| \`/territories\_bz open                      | close\`  | Admin                                       |
| `/territories_fight_cooldown <id> <minutes>` | Admin    | Sets or clears (`0`) the immunity to fights |
| \`/territories\_ranking\_reset \<wins        | time     | produced                                    |
| `/territories_ranking_remove <kind> <org>`   | Admin    | Takes one org out of a ranking              |

The tablet command is `Command` in `config/config.lua`. The admin commands use the ACE in `AdminGroup`, registered by the script.

***

## 🔌 Integration

### Client hooks (`config/cl-functions.lua`)

| Function                                                   | What it is for                                                                                                             |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `Notify(msg, type, duration)`                              | Every notification. Default ox\_lib.                                                                                       |
| `OnTerritoryCaptured(t)`                                   | Runs on every client when a territory gets an owner. `t.owner`, `t.previousOwner`, `t.label`.                              |
| `OnTerritoryLost(t)`                                       | Same, when a territory goes neutral.                                                                                       |
| `IsLocalPlayerBlocked()`                                   | Return `true` to keep the player out of the system (arenas, instances).                                                    |
| `RegisterStationInteraction(def, canInteract, onInteract)` | How a station is opened. Default text prompt; swap for ox\_target, qb-target or your interaction script. Returns a handle. |
| `RemoveStationInteraction(handle)`                         | Undoes the above on resource stop.                                                                                         |

### Server hooks (`config/sv-functions.lua`, `config/framework/sv-framework.lua`)

| Function                    | What it is for                                                                                     |
| --------------------------- | -------------------------------------------------------------------------------------------------- |
| `Webhook.url`               | One Discord webhook for captures, station activity and fights. Empty disables it.                  |
| `Framework.GetGangId(src)`  | The gang of a player, a key of `Organizations`. Edit the standalone branch for a custom framework. |
| `Framework.KillPlayer(src)` | Kills a player who left a fight ring.                                                              |

### State bags

Other scripts can read the state of a territory from `GlobalState['s_t_terr:<id>']` (`owner_gang`, `state`, `influence`, `cooldown_until`) and the Black Zone window from `GlobalState['s_t_market']`.

***

## 🗄️ Database

`s_t_territories` keeps one row per territory id: `owner_gang`, `current_leader`, `influence`, `state`, `captured_at`, `cooldown_until`, `controlled_since`. `s_t_org_stats` keeps one row per org and day with `wins`, `seconds` and `produced`, pruned after `RankingDays`.
