Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cc-scripts.com/llms.txt

Use this file to discover all available pages before exploring further.

The Frequency minigame is the only one in cc_minigames that requires the server. Two players collaborate: a viewer sees a target waveform, and a tuner adjusts probe frequency and amplitude until both axes are within tolerance, then both hold for holdSeconds to win. The server holds the canonical state and ticks the hold-timer at 100 ms — neither client can spoof a “locked” frame to fake a win.

Server export

local p = exports.cc_minigames:FrequencyStart(viewerSrc, tunerSrc, {
    difficulty   = 'medium',
    -- Per-tier defaults: tolerance, holdSeconds, drift, timeLimit.
    -- All of these can be overridden:
    tolerance    = 8,    -- 1–40, percent of axis range
    holdSeconds  = 3.0,  -- 0.5–15
    drift        = 0.6,  -- 0–5; how aggressively the target drifts mid-game
    timeLimit    = 75,   -- 0–600 seconds; 0 = none
    viewerVariant = 'screen-4x3', -- defaults to 'screen-4x3'; pass 'bare' for a non-DUI viewer
    viewerEmbed   = false, -- set true if the viewer is rendered into a DUI/iframe by another resource
})

p:next(function(success)
    if success then
        -- both players cleared the puzzle
    end
end)
FrequencyStart returns a promise that resolves with the success boolean. From inside a server thread you can also Citizen.Await(p) to block on it.

Per-difficulty defaults

DifficultytoleranceholdSecondsdrifttimeLimit
easy12%2.5090 s
medium8%3.00.675 s
hard5%3.51.460 s

Server export reference

ExportReturnsDescription
FrequencyStart(viewerSrc, tunerSrc, opts)promiseStart a session. Both clients receive a join event and run the local minigame.
FrequencyGetToken(src)string | nilLook up the active session token for a given player source. Useful for cross-resource code that needs to address a running session.

Cross-resource event

When a session ends, the server fires:
TriggerEvent('cc_minigames:sv:frequency:finished', token, success, viewerSrc, tunerSrc)
This is a regular Lua event (server-local, not net), so any other server resource can AddEventHandler it and react when a frequency session resolves — useful when the server resource that started the session was in a different lifecycle than the one that needs to react to the result.

What happens on the wire

1

Server picks targets, seeds probes off-target

targetFreq and targetAmp are randomized within their bounds. probeFreq and probeAmp are re-rolled until they’re at least 25% off-target on each axis, so the game never spawns already-locked.
2

Server fans out the join event

Both clients receive cc_minigames:cl:frequency:join carrying the token and their assigned role. Each client runs the local Frequency minigame export.
3

Tuner inputs go to the server

A/D/W/S on the tuner client fire cc_minigames:sv:frequency:input with { axis, dir }. Server clamps and applies the step. Viewer inputs are silently ignored.
4

Server ticks at 100 ms

On each tick: drift advances the target on harder tiers, the timer counts down, and the hold-meter accumulates while both axes are within tolerance and decays 1.5× faster when out. State is broadcast to both clients.
5

Hold target met → success

When holdMs reaches holdSeconds × 1000, the server fires cc_minigames:cl:frequency:finish to both clients with success = true, resolves the promise, and emits cc_minigames:sv:frequency:finished for cross-resource listeners.
6

Anything else → fail

Time limit hit, either player escapes the local minigame, or either player drops — the session resolves false with the same fan-out.

Variants

SideVariant optionsNotes
Viewerscreen-4x3 (default), barescreen-4x3 is the layout intended for a DUI prop (in-world CRT). bare is for a player-facing screen where you just want the waveform card.
Tunerbare (forced)The tuner is dial + readout — terminal chrome would fight the panel.

Embedding the viewer in a DUI

A common heist setup: the viewer’s waveform is on a wall-mounted CRT prop, the tuner is a player at a console nearby. Use the DUI session helper for the viewer side and pass viewerEmbed = true so the server doesn’t try to push a fullscreen NUI to that player.
-- Server-side
local token = -- (reserve a token before starting; or use FrequencyGetToken after)
exports.cc_minigames:FrequencyStart(viewerSrc, tunerSrc, {
    difficulty    = 'medium',
    viewerEmbed   = true,    -- viewer's NUI is rendered by another resource via DUI
})
-- Client (viewer side, in the DUI-hosting resource)
local controller = exports.cc_minigames:CreateDuiSession({
    minigame    = 'frequency',
    variant     = 'screen-4x3',
    opts        = { role = 'viewer', sessionToken = token, difficulty = 'medium' },
    sendMessage = function(payload) myDui:sendMessage(payload) end,
    onResult    = function(success) end,
})
myDui:sendMessage(controller.startMessage)
The server’s per-tick state events are forwarded into the DUI automatically because the DUI session registered its sessionToken.

Test it solo

For visual debugging without a partner the local /minigame command bypasses the server session entirely and renders both halves in one card:
/minigame frequency bare medium both
/minigame frequency bare medium both ABCD-1234           # display this code on success
/minigame frequency bare medium both ABCD-1234 skip      # skip puzzle, show code
This drives a fake state loop in the NUI — useful for tuning visuals or layout without two clients.