Developer Api

Developer API

Everything here is server-side.

n4_queue parity

Same names and arguments, so migrating is a rename. See
Migrating from n4_queue for the one behaviour difference.

Export Returns
queueList() Array of waiting players, in order. Adds tier and score to n4's fields.
queueSize() Number waiting.
connectingList() Map keyed by identifier of players past the queue but not yet loaded.
connectingSize() How many of those there are.
addPrio(identifier, points, category?, opts?) New row id, or nil.
removePrio(id) true if a row was deleted.
local id = exports['rivo_queue']:addPrio('discord:1234…', 2500, 'tebex', {
    label     = 'Gold',
    tier      = 'vip',
    expiresAt = os.time() + 30 * 86400,
})

opts is an addition; n4's three-argument form still works.

Priority

Export Returns
listPrio() Every stored row, expired included.
prunePrio() Deletes expired rows now; returns the count.
getQueueInfo(identifier) Position, points, rows, tier, waited, estimate. nil if not queued.
getPosition(identifier) Position, or nil.

Bans and blocks

Export Returns
addBan(identifier, reason?, expiresAt?) Row id. Omit expiresAt for permanent.
removeBan(id) true if lifted.
listBans() Every ban row.
getBans(identifier) Live bans for one identifier. Non-empty means banned right now.
blockIdentifier(identifier, seconds, reason?) Temporary, memory-only.
unblockIdentifier(identifier) Lifts it.
listBlocks() Active blocks with remaining time.

A ban refuses the player's next connection. It does not remove anyone
already on the server; use your admin resource for that.

Server state

Export Returns
getSlots() max, online, connecting, admitting, held, reserved, free, queued.
getMetrics() Counters, denials by gate, peaks, wait average and p95, admits/hour.
getGraceHolds() Players whose place or slot is being held after a disconnect.
getAdmitInterval() Average seconds between admissions, or nil.
getMaintenance() Current maintenance state.
setMaintenance(enabled, tiers?, seconds?, reason?) Turns it on or off.
storageBackend() 'json' or 'mysql'.
discordStatus() Cache size, rate-limit state, whether the token was rejected.

Use getSlots() when the server looks empty but nobody can get in.
connecting and held are slots that GetPlayers() cannot see.


Events

Every internal event is also fired as a normal server event, so you can
subscribe without depending on this resource:

AddEventHandler('rivo_queue:denied', function(data)
    print(('%s was refused by the %s gate'):format(data.name, data.gate))
end)
Event Payload
rivo_queue:queued name, identifier, position, total, points, tier
rivo_queue:admitted name, identifier, waited, position, points, tier, bypassed
rivo_queue:joined name, identifier, points, tier, waited
rivo_queue:left name, identifier, reason, waited, position
rivo_queue:denied name, identifier, gate, reason
rivo_queue:flagged name, identifier, matched, reason
rivo_queue:admin action, by, detail
rivo_queue:milestone kind (queueSize | longWait), value, player

Payloads are copies. Changing one does not affect queue state.


Writing a priority source

A source takes a player's identifiers and returns the priority rows that apply
to them.

There are two forms. When calling from another resource, name an export. A
plain function does not survive being passed across a resource boundary
intact, and a named export keeps working if either resource restarts.

-- from another resource: name your export
exports['rivo_queue']:registerPrioritySource('tebex', 200, 'my_store', 'queuePriorityFor')

-- and in my_store:
exports('queuePriorityFor', function(ctx)
    return { { points = 5000, label = 'Donator', category = 'tebex' } }
end)

The callback form is fine inside a single resource:

exports['rivo_queue']:registerPrioritySource('tebex', 200, function(ctx)
    local rows = {}

    for _, identifier in ipairs(ctx.all) do
        local pkg = MyStore.packageFor(identifier)
        if pkg then
            rows[#rows + 1] = {
                points    = pkg.points,
                label     = pkg.name,
                category  = 'tebex',
                tier      = pkg.tier,
                expiresAt = pkg.expiresAt,
            }
        end
    end

    return rows
end)

ctx contains:

Field
src The connecting player's temporary source.
name Their name.
identifiers Map: { license = 'license:…', discord = 'discord:…', … }
all Flat array of every identifier they presented.
primary The one the queue tracks them by.

Return an array of rows. points is required and must be above zero, or zero
when the row names a tier. Everything else is optional. Expired rows are dropped for you.

Things to know:

Match on ctx.all, not just ctx.primary. A row keyed on discord: should
still apply to a license-tracked player.

Your source may yield. HTTP calls and database queries are fine. It runs
inside the connecting player's own coroutine, so a slow source only delays that
player, not the queue.

Errors fail open. If your source errors, it is skipped with a console
warning and the player connects without those points. If a lookup times out,
return {} rather than guessing.

The second argument is the order sources are asked in. Lower runs first. It only
matters when Config.priorityMode = 'max' and two sources tie.

Built-in orders: config 10, store 20, discord 30, ace 40 (ACE and txAdmin).

prioritySources() lists what is registered.

View Rivo Queue