OPX//77
opx_infinityelevators

elevators

Last updated on

Job-gated floor lists on Night City's existing lifts, with the server re-checking job, grade, duty, bucket and distance before it moves a cabin.

Takes over lifts that already exist in the game world and gives them a floor list from config/elevators.lua. Each floor can be public or locked to jobs, a minimum grade and being on duty. The client finds configured lifts near the player and reports them; the server adopts and locks each lift, then moves the cabin only after re-checking the player's job, bucket and distance itself. Players ride with the key (E by default), which opens the floor list drawn with the menu module. Use it to keep a precinct's upper floors for police, or a corporate tower's offices for its staff.

Sideboth
Requirescharacter (hard)
Optionalmenu (floor list), downed (no list while down), prompts (the key's hint row)
Contractelevators v1 — server, client
Configconfig/elevators.lua (shared)
Datanone
  • The key (KEY, opx.elevators.use) opens the floor list at the lift the player stands at. A strip row (prompts) names the key while within USE_RADIUS of a configured lift; without prompts the key still works. Away from any lift the key does nothing and says nothing (E is shared with other modules).
  • A lift nobody rides for 10 minutes is released on the host as well as forgotten, so it can be adopted again. After a restart, the client re-reports a lift that is still managed but not bound (every 30 s) and the server claims it back.
  • The server refuses a downed player (downed), not only the client panel.
  • A rider who leaves mid-trip no longer recalls the cabin: it finishes the trip it was given. An empty cabin stays where it stops.
  • With KEY.DEFAULT = false no key is declared and OpenPanel is the only way in. Adopted lifts are locked, so the game's own lift panel is refused either way.

The shipped elevators are samples

The four entries in config/elevators.lua have placeholder positions and guessed floor indexes. A lift is matched only within MATCH_RADIUS of its declared position, so a wrong position matches nothing and the lift is never adopted. Read real positions with the client console command resource.emit open77:elevators:nearby 100.

Commands

CommandAccessArgumentsEffect
/opx.elevators.whereACL command.opx.elevators.where[key]Prints config problems, then one line per elevator: label, position, floor counts, adopted id, phase, active floor, flags; then DENIED_FLOORS and MEMBERSHIP. Name set by COMMAND.

Configuration

config/elevators.lua sets OPX.Config.MODULES.elevators. Shared. Distances are measured across the ground (X and Y) against the declared position, so a lift can be called from any floor of its shaft.

KeyDefaultEffect
enabledtruefalse switches the module off.
DENIED_FLOORS'shown'shown greys a refused floor; hidden leaves it out of the list.
MEMBERSHIP'primary'primary reads the worked job only; any also counts every job membership for the grade (never for ON_DUTY).
JOB_MAX_AGE_MS60000Past this snapshot age every gated floor closes (job_stale); public floors stay open.
POLL_MS15000How often the client re-reads the character's job.
SCAN_MS500How often the client looks for lifts (and syncs the strip row). Was 2000 before PR #95: the row came up to 2 s late.
MATCH_RADIUS6.0Metres between a seen lift and a declared position for them to match.
USE_RADIUS4.0Metres from the declared position within which a player may ride. The server allows REACH_SLACK (1 m) more.
SCAN_RADIUS40.0Metres the client scans, and the server's limit on a sighting. Clamped to 1..300.
TRAVEL_MS8000Cabin travel time. Tunable ELEV_TRAVEL_MS.
REQUEST_WINDOW_MS10000Rate window. Tunable ELEV_REQUEST_WINDOW_MS.
REQUESTS_PER_WINDOW6Ride requests per player per window. Tunable ELEV_REQUESTS_PER_WINDOW.
KEY{ ID = 'opx.elevators.use', NAME = 'elevators.key.use', DEFAULT = 'E' }Rebindable key that opens the floor list. ID stores the player's rebind; NAME is the label's locale key. DEFAULT = false declares no key, leaving OpenPanel the only way in.
COMMAND'opx.elevators.where'The diagnostic command name; false registers none.
ELEVATORS4 samples: arasaka_tower, ncpd_watson, vik_clinic, afterlifeElevator key → definition. Below.
config/elevators.lua — one ELEVATORS entry
ncpd_watson = {
    LABEL = 'NCPD WATSON',
    X = -652.10, Y = 1394.55, Z = 12.40,
    BUCKET = 0,
    FLOOR_COUNT = 5,
    FLOORS = {
        { INDEX = 0, LABEL = 'Street' },
        { INDEX = 2, LABEL = 'Bullpen', JOBS = { ncpd = 0, maxtac = 0 }, REASON = 'NCPD only' },
        { INDEX = 3, LABEL = 'Holding', JOBS = { ncpd = 1, maxtac = 0 },
            ON_DUTY = true, REASON = 'NCPD Officer, on duty' },
    },
},
  • key: the durable name of the elevator (the host's id changes each restart).
  • LABEL: menu title. Never translated.
  • X, Y, Z: declared position.
  • BUCKET: routing bucket, default 0.
  • ENTITY: optional engine hash, when two shafts share a lobby.
  • FLOOR_COUNT: floors of the native lift. Overrides what the client reports.
  • FLOORS: list of { INDEX, LABEL, JOBS?, ON_DUTY?, REASON? }. INDEX is the native floor index (0-based), not the list position.

The job gate

A floor's JOBS, ON_DUTY and REASON follow the shared job-gate format; see OPX.JobGate.Evaluate.

  • JOBS: { [jobName] = minimumGradeLevel }, e.g. { ncpd = 1, maxtac = 0 }. Any one listed job at its grade passes. No JOBS = public.
  • ON_DUTY: true also requires that job to be the worked job and on duty.
  • REASON: text shown to a refused player instead of the generic sentence. Never translated.

Refusal codes

CodeMeaningLocale key
no_elevator_nearbyNo configured lift within USE_RADIUS.elevators.noElevatorNearby
no_such_elevator, no_such_floorNot in the config.elevators.noSuchElevator, elevators.noSuchFloor
no_characterNo character, or no job snapshot.elevators.noCharacter
job_staleSnapshot older than JOB_MAX_AGE_MS.elevators.jobStale
job_required, grade_too_low, off_dutyThe job gate refused.elevators.jobRequired, elevators.gradeTooLow, elevators.offDuty
not_adoptedThe server has not taken the lift yet.elevators.notAdopted
floor_out_of_rangeIndex past the lift's floor count.elevators.floorOutOfRange
no_position, wrong_bucket, too_farThe server's position check failed.elevators.noPosition, elevators.wrongBucket, elevators.tooFar
move_rejectedThe platform refused to move the cabin.elevators.moveRejected
rate_limitedToo many requests.elevators.rateLimited
downedThe player is down.elevators.downed
not_sentThe net event was not sent.elevators.notSent
player_down, menu_not_running, no_floors_availableOpenPanel only. From the key at a lift, toasted as elevators.downed (Not while you are down.), elevators.noPanel, elevators.noFloors. Away from a lift the key says nothing.—
internal_errorA client contract function raised; see the log.—

On this page