Skip to the content
Docs

Writing a Custom Adapter

On this page9

Your system is not in the list. That is fine — every selector takes 'custom', and every bridge/*.lua file is open and stays open after escrow.

How to start

  1. Set the selector in config.lua to 'custom':

    lua
    Config.InventorySystem = 'custom'
  2. Open the matching file in bridge/.

  3. Find the branches marked -- setup custom here. Each one is a template with a comment saying exactly what to return.

  4. Fill in the ones your system actually needs. The ones you skip keep answering the neutral value.

  5. Restart and check the boot summary.

Which file for which selector

SelectorFileSide
Config.Frameworkbridge/framework_server.lua, bridge/framework_client.luaboth
Config.MulticharSystembridge/framework_server.luaserver
Config.InventorySystembridge/inventory.luaserver
Config.NotifySystembridge/notify.luaboth
Config.TextUISystembridge/textui.luaclient
Config.ProgressSystembridge/progress.luaclient
Config.TargetSystembridge/target.luaclient
Config.EmoteSystembridge/emote.luaclient
Config.VoiceSystembridge/voice_client.lua, bridge/voice_server.luaboth
Config.AppearanceSystembridge/appearance_client.lua, bridge/appearance_server.luaboth
Config.TattooSystembridge/appearance_server.luaserver
Config.DeathSystembridge/death_client.lua, bridge/death_server.luaboth
Config.PoliceSystembridge/police_server.luaserver
Config.MDTSystembridge/mdt.luaserver
Config.EvidenceSystembridge/evidence.luaserver
Config.ScreenshotSystembridge/screenshot_client.lua, bridge/screenshot_server.luaboth
Config.HudSystembridge/hud.luaclient
Config.NametagSystembridge/nametag_client.luaclient
Config.WeatherSyncSystembridge/studio_client.luaclient
ServerConfig.PhotoHost = 'custom'bridge/screenshot_server.luaserver

An empty custom branch is not an error

Leaving a branch as a template does not break anything: every call answers the neutral value — nothing is given, nothing is taken, nothing is counted — and the script logs one line naming the first function that was actually asked for. For example:

Code
Config.InventorySystem = 'custom' and the branches marked "setup custom here" in
bridge/inventory.lua are still empty, so every inventory call answers the neutral
value (nothing is given, taken or counted). Fill in the ones you use -
Inventory.GetItemCount was the first one asked for.

That message tells you which function to write next. Work down the list until it stops appearing.

Rules for a custom branch

Hooks — the other extension point

bridge/hooks.lua is a separate file of optional callbacks. They are not adapters: they are places to say yes, no or instead at a decision the core is about to make. Leave one empty and the core's own answer stands.

Gates — return false to refuse

HookCalled when
Hooks.CanStartScanSomeone tries to start a scan.
Hooks.CanShowPromptThe prompt is about to be drawn.
Hooks.CanUseDeviceA device is being switched on or used.
Hooks.CanIntroduceSomeone introduces themselves.
Hooks.CanStartImplantA face implant is being started.
Hooks.CanReadAsPoliceA camera report is being read as police.
Hooks.ShouldProcessDeathA death session is about to be opened.
Hooks.IsPlayerBusyThe core asks whether the player is mid-something.

Money and standalone

HookPurpose
Hooks.ChargeImplantTake the implant price your way.
Hooks.RefundImplantGive it back if the surgery fails.
Hooks.StandaloneChargeMoney on a server with no framework.
Hooks.GetCustomCharacterIdWhat a "character" is, with ServerConfig.Standalone.CharacterKey = 'custom'.
Hooks.GetCustomJobWhat job a player has, with no framework.

Presentation

HookPurpose
Hooks.FormatAutoLabelWrite the clothing words admins see in the panel and on Discord your own way. Players always see the memory line (where and when they met).
Hooks.OnHudVisibilityReact when our HUD shows or hides.
Hooks.FilterDiscordExportChange or drop a Discord export before it is sent.

Notifications — react, never block

HookFires on
Hooks.OnScanStateChangedA scan starts, pauses, resumes or ends.
Hooks.OnScanFinishedA scan produced a result.
Hooks.OnRecognizedA recognition succeeded.
Hooks.OnProofA proof was written.
Hooks.OnLabIdentifiedA Lab ID was granted.
Hooks.OnIdentityResetA face implant or identity reset completed.
Hooks.OnDeathSessionClosedA death session was decided.

Full signatures: Hooks.

Writing to us from your own resource

If your script needs to tell us something rather than answer a question, use the write exports:

ExportFor
ReportDown, ReportRevived, ReportRespawnedDeath scripts.
ReportDeathSignal (client export)The client side of the same, for death scripts that only run on the client.
GrantLabIdentificationEvidence scripts.
RecordIntroductionID cards, phones, business cards.
LogActionYour own cuff, rob, search, escort or custom action.
ResetIdentity, StartFaceImplantSurgeon shops and story events.
GiveItem, TriggerItemUseStandalone item handling.
OpenAdminPanelAdmin menus.
lua
ServerConfig.Api = {
    TrustedResources = { 'my_surgeon_shop', 'my_idcard' },
}

LogAction also has a per-minute limit for each calling resource (Config.Proof.ExportCallsPerMinute). A normal script never comes near it.

Read exports need nothing

IsRecognized, GetRecognitionLabel, GetRecognition, GetRecognizers, HasRecognized, GetReliability, IsFaceHidden, GetConcealment are open to any resource. No trust list, no permission.

See Nametags & Chat for the pattern, and Server Exports for the signatures.

Testing your adapter

  1. Boot summary — does your system show up where you expect?

    Code
    [5code-recognition] Integrations: ... inventory=custom ...
  2. Self-test — recognition:selftest in the server console. Every line PASS.

  3. recognition:status — running scans, detected scripts, voice trust.

  4. Config.Debug = true — client bridges then print their choices to F8, and the boot summary adds the job whitelists.

  5. The five-minute two-player test — First Test.

Keeping your adapter across updates

bridge/*.lua stays open, but it is also where new adapters land, so the shipped files change between versions.

  1. Take the new bridge file.
  2. Re-apply only your 'custom' branch into it — the branches are written to be self-contained for exactly this reason.
  3. Never keep an old bridge file wholesale: it may be missing functions the new core calls, and the resource will error at load.

See Updating.