Files
fivem/README.md

383 lines
17 KiB
Markdown

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