Game Modes

Four game modes, four config files. They share the same building blocks — a centre, a radius, a list of spawn points — so once you have placed one map you can place all of them.

ModeFileShipped contentDimension
FFAconfig/ffa.lua16 maps5000 + map index
Lobbiesconfig/lobby.lua9 maps, 2 teamsper lobby
Arena modesconfig/arena.luaBF mode, Air Fight999 / 888
Gungameconfig/gungame.lua5 arenas, 25 levels6000 + map index

Each mode also has a tab in the menu that can be switched off entirely with Config.Tabs in config.lua.

FFA

Free for all: everybody against everybody inside one zone. Config.FFA.maps is a plain list — one entry per map.

{
    name        = "rooftop_1",
    label       = "Rooftop",
    image       = "rooftop",
    coords      = vec3(339.4624, -41.4019, 153.2948),
    radius      = 50.0,
    maxPlayers  = 10,
    spawnPoints = {
        vector4(349.5204, -72.9325, 153.2951, 22.0677),
        vector4(310.5954, -58.6632, 153.2977, 299.0320),
        …
    },
    weapons     = {
        [`weapon_pistol50`] = {
            `COMPONENT_PISTOL50_CLIP_02`
        }
    }
}
FieldTypeDescription
namestringInternal id. Must be unique across all maps
labelstringName shown in the menu
imagestringFile name in frontend/images/ffa/, without the extension
coordsvec3Centre of the zone
radiusnumberZone radius in metres. Leaving it ends the fight
maxPlayersnumberSlots on this map
spawnPointslist of vector4Where players spawn; the fourth value is the heading
weaponstableOptional. Loadout for this map only

Spawn points decide how a map plays. Give a map enough of them for its player count — the shipped maps run from 5 points at 10 players (Rooftop) up to 15 points at 15 players.

Per-map loadouts

Without a weapons table a map uses the global Config.Weapons from config.lua. With one, it uses exactly what is listed there.

That is how the shipped config builds variants of the same location: 5 of the 16 maps carry their own loadout, which is why the list has both Ölfelder and Ölfelder 50. — same coordinates, but the second one is restricted to the Pistol .50.

To build such a pair, copy the entry, give it a new name, change the label, and add or remove the weapons table.

Lobbies

Private matches: a player creates a lobby, others join, two teams fight several rounds inside a shrinking zone. Config.Lobby holds the rules, Config.Lobby.maps the locations.

Rules and limits

limits = {
    minPerTeam  = 1,
    maxPerTeam  = 10,
    minRounds   = 1,
    maxRounds   = 10,
    passwordLen = 32,
},

scoreboard = {
    delay    = 2000,
    duration = 5000,
    gap      = 1000,
    maxRows  = 5,
},

limits is the range a player can pick from when creating a lobby — team size, number of rounds, and the maximum length of the password. The scoreboard values control the board between rounds: delay before it appears, duration on screen, gap until the next round starts, maxRows players listed.

The zone

zone = {
    shrinkInterval    = 3000,
    shrinkAmount      = 3.0,
    minRadius         = 50.0,
    orbitSpeed        = 6.0,
    orbitRadiusFactor = 0.35,
    startPadding      = 35.0,
    damagePerTick     = 5,
    tickInterval      = 1000,
    blipColor         = 1,
    blipAlpha         = 128,
},
OptionDefaultDescription
shrinkInterval3000Milliseconds between two shrink steps
shrinkAmount3.0Metres the radius loses per step
minRadius50.0The zone never gets smaller than this
startPadding35.0Extra metres the zone starts with on top of the map radius
orbitSpeed6.0How fast the zone centre drifts
orbitRadiusFactor0.35How far it drifts, as a fraction of the map radius
damagePerTick5Damage per tick outside the zone
tickInterval1000Milliseconds between two damage ticks
blipColor / blipAlpha1 / 128Colour and transparency of the zone on the map

The pace of a match comes out of the first two values: 3.0 metres every 3 seconds is 60 metres per minute. Halve shrinkInterval for a faster round, raise minRadius if the endgame gets too cramped.

Spectating

spectate = {
    keyPrev    = 174, keyPrevLabel = "PFEIL LINKS",
    keyNext    = 175, keyNextLabel = "PFEIL RECHTS",
    keyFreecam = 22,  keyFreecamLabel = "LEERTASTE",
    refresh    = 1000,
},

Dead players spectate. The three keys are FiveM control ids, each with the label shown on screen next to it — change both together, otherwise the hint lies to your players. refresh is how often the spectator list updates, in milliseconds.

Weapons, teams and clothing

weapons = {
    {
        name       = "weapon_pistol",
        label      = "Pistole",
        components = { "COMPONENT_PISTOL_CLIP_02" }
    },
    …
},

colors = {
    ["blue"] = { 0, 0, 255 },
    ["red"]  = { 255, 0, 0 },
},

weapons is what the lobby creator can choose from — the weapon, the label shown in the UI, and the attachments handed out with it. Pictures come from frontend/images/lobbys/weapons/.

colors defines the two teams as RGB triplets, used for blips, markers and the scoreboard.

clothing = {
    enabled  = true,
    ["blue"] = {
        male   = { ["torso_1"] = 47, ["tshirt_1"] = 15, ["pants_1"] = 5, … },
        female = { ["torso_1"] = 608, ["pants_1"] = 231, … }
    },
    ["red"]  = { male = { … }, female = { … } },
},

Team outfits, keyed by team colour and gender. The keys are the ESX skin component names (torso_1, pants_1, helmet_1, …) with the drawable number as the value, and the _2 variants for the texture. Set enabled = false to let players keep their own clothes.

vehicles = {
    { name = "revolter",  label = "Revolter" },
    { name = "schafter3", label = "Schafter" },
    { name = "komoda",    label = "Komoda" },
},

The cars a lobby can be played with. Images come from frontend/images/lobbys/cars/.

Lobby maps

{
    name   = "mirrorpark",
    label  = "Mirrorpark",
    image  = "mirrorpark_lobby",
    coords = vec3(1166.6749, -602.2207, 63.9432),
    radius = 1000.0,
    points = {
        { players = vec4(955.1973, -127.5772, 74.3719, 213.7775),   garage = vec4(962.7661, -135.5553, 74.3968, 147.3366) },
        { players = vec4(1202.9913, -1305.4982, 35.2255, 358.0665), garage = vec4(1203.9659, -1288.2582, 35.2243, 262.9063) },
    }
},
FieldDescription
coords / radiusCentre and size of the playing field. The zone starts at radius + startPadding
pointsOne entry per team. players is where that team spawns, garage where their vehicle is handed out

All nine shipped maps have exactly two points, because a lobby is always two teams. Their radius runs from 30.0 (Würfelparkdach, a rooftop) up to 1000.0 (Mirrorpark, a whole district).

A map without vehicles simply parks the garage out of the way — Würfelparkdach uses vec4(0, 0, 0, 199.4724) for both points.

Arena modes

config/arena.lua holds the two fixed modes. Both share the same frame:

FieldBF modeAir Fight
enabledtruetrue
labelBF ModusAir Fight
imagebfmodeairfight
dimension999888
maxPlayers3030
radius200.0400.0
centervec3(1358.78, 3128.79, 40.97)vec3(263.93, 6484.48, 30.67)

description is the text on the card in the Arena tab, image a file in frontend/images/modes/.

BF mode

bfMode = {
    vehicle     = "bf400",
    spawnPoints = {
        vec4(1545.0197, 3082.7642, 40.6412, 107.0504),
        … 17 points in a ring around the centre
    }
}

A motorcycle deathmatch — everybody spawns on a bf400, and going down off the bike is what kills you. The 17 spawn points form a ring around the centre so nobody starts in the middle. vehicle takes any model name.

Air Fight

airFight = {
    dropOffset = 10.0,
    dropSpread = 15.0,
}

A parachute drop: the fight starts in the air. Air Fight has no spawnPoints — players are placed above center instead.

OptionDefaultDescription
dropOffset10.0Height above the centre where players are released
dropSpread15.0How far they are spread horizontally, so nobody drops onto somebody else

Gungame

Everybody starts on level 1, a kill moves you up a level and hands you the next weapon, and whoever clears the last level wins the round.

Config.Gungame.enabled         = true
Config.Gungame.baseDimension   = 6000
Config.Gungame.roundTime       = 15 * 60
Config.Gungame.spawnProtection = 1000
Config.Gungame.resultTime      = 8
Config.Gungame.freezeOnEnd     = true
OptionDefaultDescription
enabledtrueMaster switch for the whole mode
baseDimension6000Routing bucket base, plus the map index
roundTime15 * 60Round length in seconds — written as minutes × 60 for readability
spawnProtection1000Spawn protection after a respawn, in milliseconds
resultTime8Seconds the result screen stays up before the next round
freezeOnEndtrueFreezes players while the result is shown

Arenas

Config.Gungame.maps = {
    { ffaMap = 7,  maxPlayers = 20 }, -- Oelfelder
    { ffaMap = 8,  maxPlayers = 20 }, -- Alienberg
    { ffaMap = 9,  maxPlayers = 15 }, -- Hafen
    { ffaMap = 12, maxPlayers = 15 }, -- Skaterpark
    { ffaMap = 14, maxPlayers = 20 }, -- Windpark
}

Gungame does not define its own maps — it reuses the FFA ones. ffaMap is the position in Config.FFA.maps, counted from 1, so ffaMap = 7 is the seventh entry in ffa.lua.

This is the one place where two config files depend on each other. If you insert or remove an FFA map above index 14, every number below it shifts and Gungame silently starts on the wrong map. Add new FFA maps at the end of the list, or re-check these five numbers afterwards.

maxPlayers overrides the player limit of the FFA map for the Gungame round.

Levels

Config.Gungame.levels = {
    { name = "WEAPON_CARBINERIFLE",   label = "Carbine Rifle" },
    { name = "WEAPON_SPECIALCARBINE", label = "Special Carbine" },
    …
    { name = "WEAPON_KNIFE",          label = "Messer" },
}

The ladder, in order: rifles first, then MGs, SMGs, shotguns, pistols, snipers, and the knife as the final level. 25 levels ship by default.

name is the weapon, label the text shown in the level-up message. Add, remove or reorder entries freely — the number of levels is simply the length of this list, and clearing the last entry wins the round.

Gungame text

Config.Gungame.locales = {
    joined      = "Du bist dem Gungame auf %s beigetreten",
    levelUp     = "Level %s von %s - %s",
    roundWinner = "Gungame vorbei: %s gewinnt mit Level %s",
    …
}

Gungame keeps its strings here instead of in Config.Translations. An English set:

Config.Gungame.locales = {
    joined      = "You joined the Gungame on %s",
    left        = "You left the Gungame",
    levelUp     = "Level %s of %s - %s",
    roundWinner = "Gungame over: %s wins on level %s",
    roundNobody = "Gungame over: nobody got a kill",
    roundNew    = "New round - everyone back to level 1",
    finished    = "%s cleared all %s levels and wins",
    full        = "This Gungame arena is full",
    disabled    = "The Gungame is currently disabled",
}

Mind the placeholders: levelUp takes three (current level, total, weapon), roundWinner and finished take two.

Adding a map

The same four steps for every mode:

  1. Fly to the spot and write down the centre as vec3 and the spawn points as vector4 — the fourth value is the heading the player faces.
  2. Copy an existing entry in the matching config file and replace name, label, coords, radius and spawnPoints.
  3. Drop a picture into the folder for that mode — frontend/images/ffa/ for FFA, frontend/images/lobbys/ for lobbies — and put the file name without the extension into image.
  4. Restart the resource and check the zone in game. Config.Debug = true helps while you align the radius.