Writing your own bridge
A bridge adapter is one Lua file that tells tm_lib how to talk to a system it has no built in support for: your own framework, an inventory, a target script, a notification pack, a radial menu. Resources keep calling tm.fw, tm.inventory, tm.target, tm.notify, tm.showTextUI and tm.radial, and your file does the translating. The full function lists live in bridge.md. This page is the how-to.
Pick the kind
Every kind accepts the name custom. That name loads your file from a custom folder.
| You want to connect | Convar | Your file(s) | Runs on |
|---|---|---|---|
| A framework | setr tm:framework custom | bridge/framework/custom/server.lua and client.lua | both |
| An inventory | setr tm:inventory custom | bridge/inventory/custom/server.lua and client.lua | both |
| A target script | setr tm:target custom | bridge/target/custom/client.lua | client |
| Notifications | setr tm:notify custom | bridge/notify/custom/client.lua | client |
| Text prompts | setr tm:textui custom | bridge/textui/custom/client.lua | client |
| A radial (wheel) menu | setr tm:radial custom | bridge/radial/custom/client.lua | client |
Paths are inside the tm_lib folder. Create the custom folder yourself. Restart tm_lib after every change, because adapters load once at start.
Notifications, text UI and the radial menu have ready made templates in tm_lib/templates/bridge/, so start there (below). For the other three, copy a bundled adapter that is close to your system (for example bridge/target/ox_target/client.lua) and change it.
Files you add to tm_lib are never encrypted, and the manifest also lists every bridge/<kind>/custom folder in escrow_ignore, so a file placed there stays plain, readable Lua even if one is ever shipped in the download. Client adapters must match bridge/**/client.lua, which the manifest already lists, so no manifest edit is needed. Loading a file you added to an escrowed tm_lib has not been tested on a real escrowed download yet, so check the console after the first start.
Notifications, step by step
- Copy
templates/bridge/notify/client.luatobridge/notify/custom/client.lua. - Set
RESOURCEto the name of your notification resource. - In
show(item), replace the example export call with the one your resource documents.item.messageis the main text,item.captionan optional second line,item.toneis'success','error','warning'or'info', anditem.durationis milliseconds (0means "until dismissed",nilmeans "your default"). - Add
setr tm:notify customto yourserver.cfgand restarttm_lib. - Test (see below).
Leave dismiss and clear empty if your resource cannot remove a toast early. Keep isAvailable(): while your resource is stopped, tm_lib shows its own toasts instead of dropping the message.
Text prompts, step by step
Same steps with templates/bridge/textui/client.lua and setr tm:textui custom. One decision matters:
- Your system shows one prompt at a time (most do): keep
SINGLE = true.tm_libshows the newest prompt, and when it is hidden it brings the previous one back. Yourhide(id)is only called when nothing is left. - Your system can show several at once: set
SINGLE = false.showandhideare then called for every prompt id, and your resource has to keep them apart.
You never need to answer isTextUIOpen or skip repeated calls. tm_lib already does both.
Radial menu, step by step
Same steps with templates/bridge/radial/client.lua and setr tm:radial custom. tm_lib keeps the whole list of items and menus and runs the action of a picked item, so your file only draws and erases. Things to decide:
- Can your wheel open from code? Keep
openif it can. Delete it if it only opens from its own key, and callers getfalse, 'unsupported'. - Does it have menus of its own? If it does,
registerMenucreates one and an item withmenuset opens it. If it does not, keep the menu in a table and nest it into the item when you add it (theqb-radialmenuadapter does that).tm_libregisters every menu before the items that point at them, and registers a menu again whenever it changes. - Set
adapter.resourceto your resource name. When it stops,tm_libmoves every item into its own wheel. When it starts again, or restarts (which wipes its items),tm_libputs them back. Without it a restart is not noticed. - Picking an item: call
item.onSelect()with no arguments. It isnilfor an item that opens a menu. Never run the item'seventorcommandyourself,tm_libdoes that. canShow: call it when you build the wheel if you can. If you cannot, ignore it,tm_libchecks it again when the item is picked.
Systems that open from their own key should also leave tm_lib's key alone: it already does nothing while another system owns the wheel.
Rules every adapter follows
- Same arguments, same results on every system. Translate differences inside the adapter, never in the caller.
- Never throw on purpose. A call that fails is logged and the player simply misses that one message. For actions return
false, 'reason'(ornil, 'reason') instead of raising. A thing your system cannot do returnsnil, 'unsupported'. - Soft dependencies check the state. Ask
GetResourceState(name) == 'started'before calling another resource, and provideisAvailable()where the kind supports it (notify, text UI and the radial menu do). - Do not poll or allocate per frame. Adapters for framework, inventory and target run on hot paths. Cache handles and read from the system's own cached data.
- No config files. Anything a server owner should tune is a
tm_libsetting, not a table in your adapter. - Cross-resource calls use the resource's exports inside the adapter, the way the bundled adapters do.
- Put a comment at the top of the file saying what you checked it against and what the system cannot do. The bundled adapters all do, and it is the first thing the next person reads.
Checking your adapter
Console at boot.
tm_libchecks your file against the contract and logs each missing required function once (... adapter "custom" (client) does not implement: ...). A missing function is replaced by one that answersnil, 'unsupported', so fix the list, do not ignore it.File not found. For notifications, text UI and the radial menu, a missing
customfile is logged andtm_libshows its own toasts, prompts and wheel instead. For framework, inventory and target a missing file is logged and every call answersnil, 'bridge unavailable'.Try it. From any client script on the running server:
luatm.notify({ message = 'Test toast', caption = 'second line', type = 'success' }) tm.showTextUI('[E] Test', { id = 'test' }) Wait(3000) tm.hideTextUI('test')Then switch back with
setr tm:notify tmto compare against the built in toasts.Test every system you claim to support. A bridge change is verified when it starts clean and the smoke test passes, not when it works on one server.