RivalityFX Docsrivalityfx.com ↗

Choosing how to drive a mode

The engine ticks each scene every 500 ms. You give a mode its behaviour in one of three ways, in order of precedence:

  1. mode.tick — a custom hook that replaces everything below (full imperative control).
  2. mode.rules — a declarative block, auto-run on the built-in rules runtime. The common way; no code.
  3. mode.driver — a named native driver registered via registerDriver (an escape hatch for a shape rules can't express).

A mode with none of these does not tick. See the Drivers reference and Hooks.

Most modes use rules. The two classic shapes — elimination rounds and team deathmatch — are rules blocks; so is zone control (King of the Hill, Domination).

rules — elimination rounds

Round-based, where a death takes you out for the round: 1v1 Duel, search-and-destroy styles. Flow: waiting → live → ended → …

  • A round starts when both teams have at least minToStart players — live immediately. The battleground decides the entrance: ground spawns place each player standing on their own spot; areas parachute them in and the fight can start mid-air.
  • The round ends on a team wipe, or when the live timer expires (more survivors wins; tie = draw).
  • A team reaching win.roundWins takes the match.
  • Dead players spectate their living teammates until the next round.
rules = {
    structure = 'rounds',
    phases    = { live = 150, post = 5 },
    roundEnd  = { 'elimination', 'timeLimit' },
    win       = { roundWins = 5 },
},
-- plus: teamCount, perTeam, armor, loadout

rules — continuous team deathmatch

Players respawn and keep fighting; the match is won on score.

  • Continuous live; dead players respawn after phases.respawn seconds. Set respawns = true so the engine does not send the dead to spectate — the runtime brings them back.
  • Team score = the sum of its members' kills, accrued into each team's score so the default HUD/board render it unchanged. First to win.score (or the lead at win.timeLimit) wins.
respawns = true,
rules = {
    structure = 'continuous',
    phases    = { live = 600, post = 8, respawn = 3 },
    score     = { kill = 1 },
    win       = { score = 50, timeLimit = 600 },
},
-- plus: teamCount, perTeam, armor, loadout

Add a zones block (a radius a lone team controls to score score.zoneTick) and the same continuous shape becomes King of the Hill / Domination — see the mode descriptor reference for the full rules vocabulary.

When to write a custom tick

Reach for a tick hook only when no rules shape fits — objective modes with client interaction, bespoke scoring, phase logic the runtime doesn't model. A tick replaces rules: the engine calls it every 500 ms with the scene id and a read-only snapshot, and you drive the match yourself through the write-exports:

local MODE = {
    id = 'myobjective', label = 'My Objective Mode',
    teamCount = 2, perTeam = 4,
    tick = function(sceneId, snap)
        if snap.state == 'waiting' and snap.bothPresent then
            exports.engine:spawnAll(sceneId, { alive = true })
            exports.engine:setState(sceneId, 'live', 300)
            exports.engine:announce(sceneId, 'engine:live')
        elseif snap.state == 'live' then
            -- your scoring / win logic here, using snap + setVar/setRound/...
        end
    end,
}

A tick hook runs in your mode's VM, so it can hold private state and call your own helpers — but your resource must stay started for it to keep running (the engine drops the mode when its resource stops).

You can also register a shared native driver other modes can select by name with exports.engine:registerDriver(name, { tick = ... }). See Drivers.

Quick comparison

rules roundsrules continuouscustom tick
Respawn during playNo (spectate)Yes (respawns = true)You decide
Win conditionwin.roundWinswin.score / timeLimitYou decide
Phaseslivepostcontinuous liveYou decide
Code in the modenonenonea tick