Skip to main content
There are two global ACE objects, and then a two-layer system per booth. Everything is decided on the server, on every request.

The two global ACE objects

Add them to server.cfg:
group.admin is just the conventional name — use whatever principal your server already has. If you run txAdmin, its administrators normally already sit in group.admin, so those two lines are usually all you need. To give the builder role to someone who is not a full admin:

Framework groups and jobs

Config.Permissions.adminGroups (default { 'admin', 'superadmin' }) is checked against your framework’s group system, and Config.Permissions.adminJobs (empty by default) against the player’s job. Satisfying either is equivalent to holding nexdev_djsystem.admin. Answers are cached for five seconds per player so framework lookups don’t run on every request, and cleared on disconnect. The server console always passes every check, so scheduled tasks and console commands are never blocked.

Layer 1 — the action tier

Each booth maps five actions to a tier: The five actions: This is how the shipped beach stage lets anyone add a track and hit the soundboard while keeping the transport controls for staff:

Layer 2 — the access mode

permissions.mode decides who counts as a DJ at that booth:
An unknown mode fails closed. A typo rejects the whole booth rather than downgrading it, so the booth disappears loudly instead of quietly publishing itself to everybody. Check the server console if a booth you just added didn’t show up.
A global admin passes both layers everywhere.
soundboard = false on a booth disables the soundboard there for everyone, including admins. It’s a capability of the booth, not a permission.

The per-booth ACE

Every booth carries its own ACE object in permissions.ace. A booth whose mode is ace — or any, with the ACE populated — admits exactly the holders of that object. The shipped nightclub is the worked example:
The other shipped booths declare nexdev_djsystem.club_vanilla, nexdev_djsystem.club_bahama and nexdev_djsystem.beach_festival. They’re only consulted when the booth’s mode is ace or any, so declaring them costs nothing until you use them.

The DJ claim

Being allowed to DJ and currently holding the booth are two different things. The first is a permission; the second is a lock. Only one player holds the deck at a booth at a time. The claim is taken on the first control action and released on:
  • Closing the panel
  • Disconnecting
  • Walking out of range
  • Dying
  • An admin running /djadmin kick
  • Config.DJIdleTimeout seconds without a control input (default 300)

What the panel is told

When a session opens, the server sends a resolved permission block and pushes an update whenever it changes. This is advisory only — the panel uses it to grey out buttons. Every request is re-checked on arrival, so a stale or forged permission block can only ever hide a button, never grant one.
This is why a player whose job changed while their panel was open sees buttons that no longer work. The panel refreshes on a 30-second loop; the server is already correct. Reopening the panel resyncs it immediately.

Presence

With Config.RequirePresence = true (the default), every state-changing request also passes a proximity check against the booth anchor and all of its sound points, computed on the server from the player’s actual position. You cannot control a booth from across the map, and you cannot control one you don’t have an open session at.