Skip to content

About

HTML/CSS/JS interfaces for Arma 3 with a two-way SQF channel, built on CT_WEBBROWSER

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

20 Commits

Folders and files

Repository files navigation

arma-webui

Build Arma 3 interfaces as HTML, CSS and JavaScript instead of hand-positioned dialog controls, with a two-way channel to SQF.

Arma 2.20 added CT_WEBBROWSER. This is the layer that makes it usable: a message protocol both directions, the game filesystem exposed to the page, native text entry, and the diagnostics needed to tell an engine limit from your own bug — because several things that look like engine limits are not.

[_ctrl] call webui_fnc_init;
[_ctrl, "playerInfo", { [name player, getPlayerUID player] }] call webui_fnc_on;
[_ctrl, "money", createHashMapFromArray [["cash", life_cash]]] call webui_fnc_push;
WEBUI.on("money", v => render(v.cash));
const info = await WEBUI.call("playerInfo");

Why

A dialog is idc numbers, .paa art for every panel and state, and a rebuild to move anything. A page is a stylesheet you can open in a browser tab.

Measured on a real screen: 33 app icons cost 726 KB as .paa against 12.7 KB as inline SVG; a phone chassis cost 2.8 MB as a texture against 2.2 KB of CSS; a full colour-scheme variant cost a second 710 KB icon bake against 227 bytes of custom properties.

.paa still wins for photographs, and it is the only option for world surfaces — a texture on an object, a sign, a screen in the world.

It is not the only option for drawing over gameplay: FINDINGS 8 measured a transparent page over the world (colorBackground[] = {0,0,0,0} on the control plus background: transparent in the page), which is how a HUD-style overlay is built. That is an RscTitles layer, not a dialog, so remember Esc does not close it and closeDialog does not either.

Requirements

Arma 3 2.20+ for CT_WEBBROWSER, 2.18+ for toJSON/fromJSON.

Install

  1. Copy functions/ and diagnostics/ into your mission, e.g. <mission>\webui\. Copy the pages — ui/webui.js, and ui/demo.html / ui/probe.html if you want them — to <mission>\ui\html\, which is both the path the whitelist in step 3 covers and the default webui.js location the two boot paths look in. If you put webui.js anywhere else, set WEBUI_jsPath before the first webui_fnc_init and change the path inside the step-6 stub to match — they are two separate copies of it:

    WEBUI_jsPath = "webui\ui\webui.js";   // wherever you actually put it
  2. #include config/CfgFunctions.hpp inside your CfgFunctions.

  3. #include config/CfgCommands.hpp in description.ext. Without the whitelist nothing works and it fails silently.

  4. #include config/prompt_overlay.hpp inside a dialog's class controls, after the browser control, if you want native text entry.

  5. Give a dialog a browser control and point it at a page:

class Page {
    idc = 4001;
    type = 106;                 // CT_WEBBROWSER
    style = 0;
    x = 0.2; y = 0.2; w = 0.6; h = 0.3;
    colorBackground[] = {0,0,0,0};
    url = "ui\html\mypage.html";   // must match the CfgCommands whitelist
};
// in the dialog's onLoad
[(_this select 0) displayCtrl 4001] call webui_fnc_init;
  1. Put the self-boot stub in the <head> of every page. Not optional — it is how the page gets webui.js, and a page without it populates noticeably later on every open:
<script>
window.WEBUIReady = new Promise(function (r) { window.__webuiReady = r; });
if (typeof A3API !== "undefined" && A3API.RequestFile) {
  window.__webuiBootPath = "stub";
  A3API.RequestFile("ui\\html\\webui.js").then(function (src) { (0, eval)(src); })
    .catch(function (e) { console.error("self-load failed", e); });
}
</script>

Then window.WEBUIReady.then(function (WEBUI) { ... }) — do not poll for window.WEBUI.

ui/demo.html is a working page that exercises the bridge; point a control at it — as ui\html\demo.html, so the step-3 whitelist covers it — to check an install. Two of the four directions (an SQF→JS call, and freeze/resume) start in SQF and cannot be triggered from the page; the demo prints the line to run instead. The player / screen / fps push channels are demonstration channels the library does not send: the panel populates from playerInfo and screenSize on its own, and feeding those three channels from SQF is what exercises the push direction.

API

See docs/API.md. Four directions: push, call, call-into-page, and a one-round-trip boolean. Plus textures and files from the game filesystem, a native prompt, and volume slaving.

Read this before debugging anything

docs/FINDINGS.md — measured engine behaviour, including several things that are not what they appear:

  • Animation only works when Arma is launched through Steam. Outside Steam the browser is clamped to ~1 fps and every transition looks broken. This cost an afternoon and three wrong conclusions.
  • Typed text does reach the DOM. Real <input> is fine. Codebases that avoid it are working around a bug that was never verified.
  • RequestTexture fails by resolving, not rejecting — a bad path returns a 23-character stub that looks like success.
  • The browser costs no measurable frame rate.
  • The CSS viewport is 30 px per UI_GRID unit, independent of screen resolution.
  • ExecJS carries at least 3.8 MB per call.
  • HTML on a 3D model works, at frame rate, for every viewer — cars, uniforms, backpacks; skins streamed from the server with no client download; ~0.3 ms per animated surface, a 30 fps floor past ~60, a capped pump. §12 has the laws and numbers; examples/ui2texture/ is the runnable set.

Page rules

  • One self-contained .html per screen, or one page with view swapping.
  • Inline all CSS and JS. No <script src> — the page has no resolvable base URL. Pull further scripts with WEBUI.file() and eval them. webui.js itself is the exception and must use A3API.RequestFile directly (install step 6): WEBUI.file() is a method on WEBUI, so it cannot be what loads WEBUI.
  • No external stylesheets, web fonts or CDNs. System font stack only.
  • fetch, XMLHttpRequest, localStorage and IndexedDB are blocked. State round-trips through SQF.
  • Images are data URIs or WEBUI.texture().
  • Esc closes the dialog and is handled by Arma. Do not add a page-level handler unless you intend to intercept it in SQF.

Diagnostics

Silent failure is this feature's defining trait, so the tools are part of the library rather than scaffolding:

function answers
webui_fnc_drawRate is the engine actually painting? (the launch-path check)
webui_fnc_bench what does the browser cost, and what viewport is the page really getting?
webui_fnc_paintProbe if it is not painting, which layer is throttling?
webui_fnc_forceProbe can any engine-side lever force repaints?
webui_fnc_focusProbe does window focus feed the clamp?
webui_fnc_latencyProbe what does a message cost, per leg, on both clocks?
webui_fnc_msgCapProbe find the exact page-to-SQF truncation cap (10240 chars)
webui_fnc_selfTest one green/red verdict for the whole install — run this before handing the library to anyone; procedure in docs/HANDOFF_TEST.md
webui_fnc_bootProbe why is this page slow to populate? which of the four readiness signals won, and how late — reports on screen, so it works when the affected client is not yours and you cannot read its RPT

The library also runs one check on its own: webui_fnc_clampCheck, once per session on the first ready page, detects the clamped-delivery world from FINDINGS §1 — the player who launched outside Steam and sees every web screen at ~1 fps. It forces a short 60 Hz style mutation (an idle sample cannot tell clamped from healthy, because idle is ~0.6 fps on a healthy client too), samples the draw rate, and files webui_clampVerdict / webui_clampFps in missionNamespace plus a [WEBUI-CLAMP] RPT line. It presents nothing: set webui_onClampVerdict (code, called with [verdict, fps]) to decide what the player sees, or set webui_clampCheckDisabled before init to opt out.

Pages also report uncaught errors and unhandled rejections to the RPT automatically. A JavaScript error kills a page silently and looks exactly like a broken feature; one missing var under "use strict" cost four debugging rounds before that was added.

Status

Extracted from a working mission, where it renders a full multi-screen interface. The measurements in FINDINGS come from one client; the diagnostics exist so you can confirm them on yours rather than trusting them.

Security

Handler names are looked up per control, so a page can only reach what was registered for it. That is a convenience boundary, not a trust boundary. Anyone who can run JavaScript in the page can call every registered name with arbitrary arguments, exactly as they could from the debug console. Validate anything that touches money, inventory or permissions server-side.

Licence

MIT — see LICENSE. Use it, change it, ship it closed, sell it. The only condition is that the copyright notice travels with substantial copies, so keep LICENSE alongside the files if you vendor them into a mission.

About

HTML/CSS/JS interfaces for Arma 3 with a two-way SQF channel, built on CT_WEBBROWSER

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages