# Southwest Roleplay — FiveM Server

This resource is a TypeScript FiveM port of the Southwest Roleplay game-server (originally written for RageMP). It shares the same database layer, REST API, and feature module structure as the original. The goal is a 1-to-1 feature parity port: one feature at a time, minimal API surface changes.

---

## Table of Contents

1. [Prerequisites](#prerequisites)
2. [Getting Started](#getting-started)
3. [Environment Variables](#environment-variables)
4. [Project Structure](#project-structure)
5. [Architecture Overview](#architecture-overview)
6. [Build System](#build-system)
7. [Boot Sequence](#boot-sequence)
8. [Key Systems](#key-systems)
9. [REST API](#rest-api)
10. [Porting a Feature](#porting-a-feature)
11. [RageMP → FiveM Cheatsheet](#ragemp--fivem-cheatsheet)
12. [Porting Status](#porting-status)

---

## Prerequisites

| Tool | Version |
|---|---|
| Node.js | 18+ |
| npm | 8+ |
| MySQL | 8.0 |
| Redis | 6+ |
| FiveM server artifact | build 7290+ |

---

## Getting Started

```bash
# 1. Install dependencies
npm install

# 2. Build both server and client bundles
npm run build

# 3. Drop the compiled resource into your FiveM server resources folder
#    and add `ensure swrp-fivem-server` to server.cfg
```

For active development, use watch mode — both bundles rebuild automatically on save:

```bash
npm run watch
```

Lint the TypeScript source:

```bash
npm run lint
```

---

## Environment Variables

All variables have sensible defaults for the dev environment. Override them in your FiveM `server.cfg` using `set` convars, or export them in your shell when running outside FiveM.

| Variable | Default | Description |
|---|---|---|
| `DB_HOST` | `play.southwest-roleplay.dev` | MySQL host |
| `DB_PORT` | `3306` | MySQL port |
| `DB_USER` | `username` | MySQL user |
| `DB_PASS` | *(see `database.ts`)* | MySQL password |
| `DB_NAME` | `game` | MySQL database name |
| `REDIS_HOST` | `swrp-redis` | Redis host |
| `REDIS_PORT` | `6379` | Redis port |
| `REDIS_URL` | *(unset)* | Full Redis URL (overrides host/port) |
| `LSRAGE_MONGO_QUEUE` | `mongo:jobs` | Redis list key used for the Mongo write queue |
| `SWRP_UCP_URL` | `http://127.0.0.1` | Comma-separated allowed origins for the REST API CORS policy |

> **Note:** The database does not auto-migrate (`synchronize: false`). Schema migrations are managed separately.

---

## Project Structure

```
fivem-server/
├── fxmanifest.lua          # FiveM resource manifest
├── package.json
├── webpack.server.js       # Server bundle config (Node target)
├── webpack.client.js       # Client bundle config (browser target)
├── gulpfile.js             # Orchestrates webpack via gulp
├── tsconfig.server.json
├── tsconfig.client.json
├── ormconfig.js            # TypeORM CLI config (uses compiled entities)
├── PORTING.md              # Feature porting checklist and guide
│
├── src/
│   ├── server/
│   │   ├── index.ts            # Entry point — boot sequence
│   │   ├── core/
│   │   │   ├── mp-shim.ts      # Global `mp` object (RageMP compat layer)
│   │   │   ├── database.ts     # TypeORM / MySQL
│   │   │   ├── redis.ts        # Redis client
│   │   │   ├── mongo.ts        # Mongo write queue (via Redis list)
│   │   │   ├── preload.ts      # Eager DB cache (game entity types)
│   │   │   ├── feature-system.ts  # Base class for all feature modules
│   │   │   ├── commands/       # Command system + guards
│   │   │   ├── log/            # Structured logging + log events
│   │   │   ├── rpc/            # Bidirectional client ↔ server RPC
│   │   │   ├── player/         # PlayerWrapper + player registry
│   │   │   └── vehicle/        # VehicleWrapper + vehicle registry
│   │   ├── entities/           # TypeORM entities (207 tables)
│   │   ├── features/           # Feature modules (one folder per feature)
│   │   └── rest/               # Express REST API (external service hooks)
│   │
│   ├── client/
│   │   ├── index.ts            # Client entry point
│   │   ├── core/
│   │   │   ├── mp-shim.ts      # Client-side `mp` compat layer
│   │   │   └── rpc/            # Client RPC (call server procs)
│   │   └── features/           # Ported client features
│   │
│   └── shared/
│       └── const/              # Constants shared between server and client
│
├── dist/                   # Compiled output (gitignored, produced by build)
│   ├── server/index.js
│   └── client/index.js
│
└── ui/                     # Pre-built Vue NUI (served as the resource's ui_page)
```

---

## Architecture Overview

```
┌──────────────────────────────────────────────────┐
│                  FiveM Server                    │
│                                                  │
│  ┌──────────────┐   ┌──────────────────────────┐ │
│  │  mp shim     │   │  FeatureSystem modules   │ │
│  │  (global mp) │   │  (player, login, chat…)  │ │
│  └──────┬───────┘   └──────────────────────────┘ │
│         │                                         │
│  ┌──────▼───────┐   ┌──────────────────────────┐ │
│  │ CommandSystem│   │  REST API (Express :3001) │ │
│  └──────────────┘   └──────────────────────────┘ │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌────────────────┐ │
│  │  MySQL   │  │  Redis   │  │  Mongo queue   │ │
│  │ (TypeORM)│  │ (cache)  │  │ (via Redis)    │ │
│  └──────────┘  └──────────┘  └────────────────┘ │
└──────────────────────────────────────────────────┘
         ▲ net events / RPC ▼
┌──────────────────────────────────────────────────┐
│                  FiveM Client                    │
│                                                  │
│  ┌──────────────┐   ┌────────────────────────┐  │
│  │  mp shim     │   │  Feature modules       │  │
│  │  (global mp) │   │  (login, vehicle, …)   │  │
│  └──────────────┘   └────────────────────────┘  │
│                                                  │
│  ┌──────────────────────────────────────────┐   │
│  │           NUI (Vue — ui/index.html)      │   │
│  └──────────────────────────────────────────┘   │
└──────────────────────────────────────────────────┘
```

### The `mp` compatibility shim

Both server and client expose a global `mp` object that mirrors the RageMP API. This means ported feature files compile and run with minimal changes. Functions that have no FiveM equivalent are stubbed and log a `console.warn` so they are easy to find.

Key mappings:

| Concept | RageMP | FiveM (via shim) |
|---|---|---|
| Player state | `player.setVariable(k, v)` | FiveM StateBag |
| Server → client event | `player.call('event', [...])` | `TriggerClientEvent` |
| Client → server event | `mp.events.callRemote('event')` | `TriggerServerEvent` |
| Server proc (RPC) | `mp.events.addProc('name', fn)` | `rpc.register('name', fn)` |
| Config values | `mp.config.key` | `GetConvar('key', ...)` |

---

## Build System

Webpack bundles the TypeScript source into two CommonJS files that FiveM loads at runtime:

| Bundle | Entry | Output | Notes |
|---|---|---|---|
| Server | `src/server/index.ts` | `dist/server/index.js` | `target: node`; npm deps are external |
| Client | `src/client/index.ts` | `dist/client/index.js` | Runs inside FiveM's V8 sandbox |

npm packages are left as external `require()` calls in the server bundle — FiveM resolves them from the resource's `node_modules/` directory at runtime.

Source maps are emitted alongside each bundle for stack-trace readability.

---

## Boot Sequence

`src/server/index.ts` orchestrates startup in this order:

1. **`mp-shim`** — must be the first import; builds the global `mp` object before anything else.
2. **`databaseSystem.init()`** — opens the MySQL connection (retries up to 5 times).
3. **`mongoSystem.init()`** — verifies the Redis-backed Mongo write queue is available.
4. **`redisSystem.init()`** — connects to Redis and flushes all keys.
5. **`preloadSystem.init()`** — eager-loads game entity types from the DB into memory.
6. **Parallel features** — `featuresParallel` array; all `init()` calls run concurrently.
7. **Series features** — `featuresSeries` array; each `init()` runs in order (use for features with dependencies).
8. **`postInit` pass** — optional second-pass for cross-feature wiring.
9. **`commandSystem.init()`** — collects commands from all features and registers them with FiveM's `RegisterCommand()`.

To add a newly-ported feature to the boot sequence, import it in `src/server/index.ts` and push it onto `featuresParallel` or `featuresSeries`.

---

## Key Systems

### FeatureSystem

Every feature extends `FeatureSystem`:

```ts
import { FeatureSystem } from '../../core/feature-system';

class MyFeature extends FeatureSystem {
    commands = [MyCommand];   // optional — registers with CommandSystem

    async init(): Promise<void> {
        await super.init();
        // set up event handlers, load DB data, etc.
    }

    async postInit(): Promise<void> {
        // cross-feature wiring that requires other features to be initialised
    }
}

export const myFeature = new MyFeature();
```

### CommandSystem

Commands are classes that extend `BaseCommand`. They declare their name, parameters, guards, and a `run()` method. The system validates parameters automatically and runs guards before `run()`.

Guards live in `src/server/core/commands/guards/` and return `true` on success or an error string on failure. Available guards:

- `PermissionsCommandGuard` — checks player permission group
- `PlayerStateCommandGuard` — checks alive/dead state
- `PrisonCommandGuard` — blocks commands while imprisoned
- `NoSpectateCommandGuard` — blocks commands while spectating
- `PhoneCommandGuard` — requires a phone
- `VehicleCommandGuard` — requires the player to be in a vehicle

### RPC System

Two-way remote procedure calls between server and client, replacing `mp.events.addProc` / `mp.events.callRemoteProc`.

**Server registers a handler:**
```ts
import { rpc } from '../core/rpc';
rpc.register('getPlayerInfo', async (player, ...args) => {
    return { name: player.name };
});
```

**Client calls the server:**
```ts
import { rpc } from '../core/rpc';
const info = await rpc.call('getPlayerInfo');
```

**Server calls a client proc:**
```ts
const result = await rpc.callClient(player, 'getPedPosition');
```

The wire protocol uses two net events (`_rpc:call` / `_rpc:result`) and auto-times out client calls after 10 seconds.

### Player Registry

`PlayerWrapper` wraps a FiveM net ID and exposes a RageMP-compatible player API (`.name`, `.id`, `.notify()`, `.call()`, `.setVariable()`, `.getVariable()`, etc.). The registry is maintained by the mp-shim on `playerConnecting` / `playerDropped`.

---

## REST API

An Express server starts on port **3001** alongside the FiveM resource. It is used by external services (UCP, Discord bots, admin tooling) to interact with the live game server.

All requests must include an `rpckey` header matching a key stored in the `rpc_keys` database table. Optionally the key can be IP-restricted to a CIDR range.

### Routes

| Prefix | Module | Purpose |
|---|---|---|
| `/account` | AccountRpcModule | Bans, admin notes, session invalidation |
| `/character` | CharacterRpcModule | Spawn items/money, refunds, in-game checks |
| `/chat` | ChatRpcModule | Post messages to in-game chat channels |
| `/company` | CompanyRpcModule | Manage company employees and metadata |
| `/faction` | FactionRpcModule | Faction creation / kick events |
| `/group` | GroupRpcModule | Permission group and frequency changes |
| `/motd` | MotdRpcModule | Push new MOTD / force reload |
| `/players` | PlayersRpcModule | Online player list / admin list |
| `/property` | PropertyRpcModule | Reload property parameters |
| `/vehicle` | VehicleRpcModule | Reload vehicle parameters, spawn checks |

---

## Porting a Feature

See `PORTING.md` for the full guide and suggested porting order. The short version:

### Server feature

1. Copy `game-server/src/features/<name>/` → `fivem-server/src/server/features/<name>/`
2. Fix relative imports (`../../entities/`, `../../core/` paths stay the same).
3. Replace `mp.colshapes.*` with the polygon position-tracking system.
4. Replace `mp.objects.new` / `mp.peds.new` with FiveM `CreateObject` / `CreatePed` natives.
5. `player.call(...)` works unchanged via `PlayerWrapper.call()`.
6. Uncomment the import in `src/server/index.ts` and add to `featuresParallel` or `featuresSeries`.

### Client feature

1. Copy `game-client/src/<name>/` → `fivem-server/src/client/features/<name>/`
2. Fix imports.
3. Replace `mp.events.addProc('name', fn)` with `rpc.registerClientProc('name', fn)`.
4. Replace `mp.browsers.new(url).execute(js)` with `nui.send(type, data)` (PostMessage to Vue).
5. Replace `mp.game.invoke('0xHASH', ...)` with `Citizen.invokeNative('0xHASH', ...)` or the named native.
6. Uncomment the import in `src/client/index.ts`.

---

## RageMP → FiveM Cheatsheet

| RageMP (server) | FiveM (server) |
|---|---|
| `mp.events.add('playerJoin', fn)` | Shim maps → `playerConnecting` |
| `mp.events.add('playerQuit', fn)` | Shim maps → `playerDropped` |
| `mp.events.add('myEvent', fn)` | Shim registers net event; player injected from `source` |
| `player.call('event', [a, b])` | `TriggerClientEvent('event', netId, a, b)` |
| `player.notify(msg)` | `TriggerClientEvent('_mp:notify', netId, msg)` |
| `player.setVariable(k, v)` | FiveM StateBag: `Player(id).state.set(k, v, true)` |
| `mp.vehicles.new(model, pos, opts)` | `vehicleRegistry.create(model, pos, opts)` |
| `mp.events.addProc('name', fn)` | `rpc.register('name', fn)` |
| `mp.colshapes.newSphere(...)` | Polygon position-tracking system |
| `mp.players.broadcast(msg)` | `TriggerClientEvent('_mp:chatMessage', -1, msg)` |

| RageMP (client) | FiveM (client) |
|---|---|
| `mp.events.add('render', fn)` | Shim adds to tick loop |
| `mp.events.callRemote('event', ...args)` | `TriggerServerEvent('event', ...args)` |
| `mp.events.addProc('name', fn)` | `rpc.registerClientProc('name', fn)` |
| `mp.players.local.setComponentVariation(...)` | `SetPedComponentVariation(PlayerPedId(), ...)` |
| `mp.game.invoke('0xHASH', ...args)` | `Citizen.invokeNative('0xHASH', ...args)` |
| `mp.browsers.new(url)` | FiveM NUI (`ui/index.html`) |
| `mp.gui.chat.push(msg)` | `TriggerEvent('chat:addMessage', { args: [msg] })` |

---

## Porting Status

| Layer | Status | Notes |
|---|---|---|
| TypeORM entities (207) | Done | Copied verbatim, no changes needed |
| Express REST API | Done | Copied verbatim, no changes needed |
| Shared constants | Done | |
| Core log / DB / Redis / Mongo | Done | |
| `mp` server shim | Done | Events, players, vehicles, config |
| `mp` client shim | Done | Events, game, gui, NUI |
| RPC system | Done | Replaces `mp.events.addProc` |
| Player wrapper | Done | Wraps FiveM net ID into `PlayerMp`-compatible object |
| Vehicle wrapper | Done | Wraps FiveM entity handle |
| CommandSystem | Done | Uses `RegisterCommand()` per command |
| Server features (101) | Not started | See `PORTING.md` for suggested order |
| Client features (~80) | Not started | See `PORTING.md` for suggested order |

The recommended first features to port (in order) are: `time`, `weather`, `chat`, `login`, `session`, `player`, `permissions`, `money`, `inventory`, `vehicle`.

Defer features with heavy colshape or server-object dependencies (`properties`, `furniture`, `drugs`, `graffiti`) until the polygon position-tracking system is fully integrated.
