Integration
Paste this into your own Panorama script. Edit the UMM_ constants and the body
of applySetting; the rest is boilerplate you never touch.
(function () { var UMM_CHANNEL = "ClientUI_FireOutput"; var UMM_PROTOCOL = 1; var UMM_ID = "my_mod"; // stable, unique; namespaces your saved settings var UMM_NAME = "My Mod"; // optional tab title; defaults to the id var UMM_SETTINGS = [ { id: "enabled", type: "toggle", label: "Enabled", default: true }, { id: "opacity", type: "slider", label: "Opacity", min: 20, max: 100, step: 5, default: 90, unit: "%" } ];
// Your one code path. Runs for every setting at boot and on every change. function applySetting(key, value) { if (key === "enabled") myPanel.visible = value; if (key === "opacity") myPanel.style.opacity = (value / 100).toFixed(2); }
function ummAnnounce() { try { $.DispatchEvent(UMM_CHANNEL, JSON.stringify({ umm: UMM_PROTOCOL, t: "register", id: UMM_ID, name: UMM_NAME, settings: UMM_SETTINGS })); } catch (e) {} }
function onUmmBus(payload) { if (typeof payload !== "string" || payload.indexOf('"umm"') === -1) return; var msg; try { msg = JSON.parse(payload); } catch (e) { return; } if (!msg || msg.umm !== UMM_PROTOCOL) return; if (msg.t === "hello") ummAnnounce(); else if (msg.t === "set" && msg.id === UMM_ID) applySetting(msg.key, msg.value); }
function registerSettings() { try { $.RegisterForUnhandledEvent(UMM_CHANNEL, onUmmBus); } catch (e) {} // Apply declared defaults now, so the mod works even without UMM installed. for (var i = 0; i < UMM_SETTINGS.length; i++) { var s = UMM_SETTINGS[i]; if (s.type === "group") continue; // a header, no value if (s.type === "checks") { // one independent boolean per option var opts = s.options || []; for (var k = 0; k < opts.length; k++) applySetting(opts[k].id, opts[k]["default"]); continue; } applySetting(s.id, s["default"]); } ummAnnounce(); }
registerSettings();})();That is the whole integration. There is nothing to bundle, nothing to require and no version to track.
Everything is UMM_-prefixed on purpose. You are pasting into a file that
already has code in it, and the prefix keeps the snippet from colliding with
what is already there and makes it greppable later.
The worked example walks through buffs-log’s copy of
it line by line, using the same identifiers.
Key points
Section titled “Key points”UMM_IDis required, unique, and permanent. It namespaces your saved settings, so changing it orphans every value your users have saved.UMM_NAMEis optional and defaults to the id. It is the tab title.applySetting(key, value)is your only code path. It fires once per setting at boot, again if UMM has a saved value for it, and on every change afterwards. Keep it idempotent: the boot sequence can call it twice for one setting, first with the default, then with the saved value.- The default loop skips
groupand descends intochecks. Neither carries a value of its own, so a loop without those guards breaks the moment you add a divider. See the settings reference. - Persistence is UMM’s job. A change applies immediately and holds for the
session; the player clicks Save Settings in the window to keep it across
restarts. Restored values reach you as ordinary
setmessages. - Reset needs no code. The reset button sends your mod a
setmessage carrying the default value, identical to a change made by hand.
Why it looks like this
Section titled “Why it looks like this”Mods live in separate VPKs and separate Panorama panel contexts, and exactly one
channel crosses contexts: ClientUI_FireOutput. It is engine-declared and
carries a string payload. Arbitrary event names are not dispatchable, which is
why every community framework piggybacks on this one event.
Two consequences shape the snippet:
- Namespacing lives inside the payload (
{"umm":1,...}), because the channel itself is shared with everyone. - Handlers must not throw on foreign traffic. Hence the cheap
indexOf('"umm"')check beforeJSON.parse, and thetry/catcharound it. Other mods are broadcasting on this channel and your handler sees all of it.
Load order between UMM and your mod is undefined, so both directions are covered:
a mod that boots later announces itself, and UMM broadcasts hello on boot so
mods that booted earlier announce themselves again.
Message shapes
Section titled “Message shapes”Three messages cross the boundary between UMM and your mod. Each is a JSON
string on ClientUI_FireOutput carrying umm: 1 as its version marker.
{umm:1, t:"hello"} // UMM -> mods, "re-announce"{umm:1, t:"register", id, name, settings, values} // mod -> UMM, full manifest{umm:1, t:"set", id, key, value} // UMM -> mod, value changedvalues is the one field the snippet leaves out. Send it alongside your manifest
if your mod already holds values it wants UMM to adopt; see
value precedence for how it ranks
against stored values and your declared defaults.
Listing UMM as a requirement
Section titled “Listing UMM as a requirement”Deadlock Mod Manager shows a mod’s requirements on its install page, and it takes them from the GameBanana submission - not from anything in your VPK. If you want players to see UMM listed there, add it to your mod page’s requirements yourself.
- Open your mod on GameBanana and click Edit, then the Technical tab.
- Scroll down to Requirements and add an entry.
- Name it
Universal Mod Managerand set the URL to https://gamebanana.com/mods/693642. - Click the gear next to the entry and pick Required or Recommended, then set the one below it to Enabled.
- Save the submission. Deadlock Mod Manager picks the change up the next time it refreshes your mod’s page.
Use the GameBanana page URL, not the docs site or a direct download link. That URL is what the manager resolves back to a mod it can install.
- Settings reference for every control type.
- Client library if you prefer an
onChangeAPI. - Worked example for a real integration.