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
  2. Getting Started
  3. Environment Variables
  4. Project Structure
  5. Architecture Overview
  6. Build System
  7. Boot Sequence
  8. Key Systems
  9. REST API
  10. Porting a Feature
  11. RageMP → FiveM Cheatsheet
  12. Porting Status

Prerequisites

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

Getting Started

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

npm run watch

Lint the TypeScript source:

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 featuresfeaturesParallel array; all init() calls run concurrently.
  7. Series featuresfeaturesSeries 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:

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:

import { rpc } from '../core/rpc';
rpc.register('getPlayerInfo', async (player, ...args) => {
    return { name: player.name };
});

Client calls the server:

import { rpc } from '../core/rpc';
const info = await rpc.call('getPlayerInfo');

Server calls a client proc:

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.

Description
No description provided
Readme 6.6 MiB
Languages
CSS 56.7%
JavaScript 31.1%
TypeScript 12.2%