HUD
Custom on-screen UI for CS2. Two layers:
- Screen effects —
Fade,Shake,HintTextfrom@s2script/cs2. Usermessages; no workshop addon. - Custom Panorama —
uifrom@s2script/cs2. Preferui.components()(pooled modals, badges, toasts). Fall back toui.hud()only when you ship your own workshop layout.
There is no @s2script/sdk/hud. Import ui from @s2script/cs2 and drive panels through it. @s2script/cs2/ui is types-only — require("@s2script/cs2/ui") is undefined. ui is load-window only — it throws after settle.
Operator setup
Custom Panorama needs:
- CS2 dedicated build ≥ 24934554 (
AllowCustomGameUIis on; older clients crash on addon layouts). - MultiAddonManager on the server.
mm_extra_addons "3790153369"ingame/csgo/cfg/multiaddonmanager/multiaddonmanager.cfg(decimal id, quoted, noworkshop/prefix). That is the shipped s2script HUD addon.
Clients must actually mount that addon. +host_workshop_map delivers a map, not a content addon. The server cannot detect a missing client mount — drive calls succeed and the player simply sees nothing.
Screen effects (Fade / Shake / HintText) skip this checklist.
First HUD
Register each layout at load (hud() / components()). The layout entity spawns when the first client becomes SIGNON_ACTIVE — not a console-only createLayout() workaround. Paint and drive never create the entity as a side-effect; OnMapStart only resets it. Draw from player-join, a game event, a command, or any other callback after a client is active.
import type { Client } from '@s2script/sdk';
import { ui } from '@s2script/cs2';
let panels: ReturnType<typeof ui.components>;
export function OnPluginStart(): void {
panels = ui.components();
}
export function OnClientActive(client: Client): void {
const slot = client.slot;
panels.toast(slot, { title: 'Hello', message: 'from s2script', variant: 'good' });
const badge = panels.badge({ corner: 'tr', accent: 'accent' });
badge?.show(slot, { title: 'STATUS', text: 'online' });
const modal = panels.modal({
title: 'Pick one',
rows: [
{ a: 'Heal', b: '100 HP' },
{ a: 'Nope', disabled: true },
],
onPick: (s, index) => {
panels.toast(s, { message: `picked ${index}`, holdSeconds: 3 });
},
buttons: [{ text: 'Close', variant: 'ghost', onClick: (s) => modal?.close(s) }],
});
modal?.open(slot);
} createLayout() / components().ensure() force a spawn of a specific descriptor. They are optional once hud() or components() has already run and a client is active.
Toast, badge, and modal props live on Default components. A kill feed or MOTD of your own is Mounting your own.
Screen effects
No addon. Each call returns false if the usermessage / fields do not resolve:
import { command } from '@s2script/sdk';
import { Fade, Shake, HintText } from '@s2script/cs2';
export function OnPluginStart(): void {
command('flash', (cmd) => {
const slot = cmd.callerSlot;
if (slot < 0) return;
Fade.blind(slot, 2);
Shake.to(slot, { amplitude: 10, frequency: 40, duration: 1 });
HintText.to(slot, 'eyes open');
});
} Fade.to takes packed RGBA color plus duration / holdTime / flags. Prefer Fade.blind for a full-screen flash.
Hazards
- Wait for an active client —
OnMapStartresets the layout; it does not spawn. Paint/drive never create the entity. Player-join, game events, and commands are all fine. - Intern budget — three × 1024 vectors shared by every plugin on the HUD entity. Prefer the component pool over private layouts.
- No client-mount detection — missing addon → silent blank screen.
- Do not
require("@s2script/cs2/ui")— types-only subpath; runtime isuifrom@s2script/cs2. onCustomHudClickedis observe-only — preferHud.onClick.disabledis cosmetic unless you also callsetDisabled(primitive) or accept thatonPickstill fires (components).
See also
- Default components — toast, badge, modal
- Mounting your own —
LayoutDescriptor+hud() - Creating a layout — XML / CSS for
custom_hud_layout - HUD module — generated from
ui.d.ts - CS2 module —
Fade/Shake/HintTextand the rest of the game package - Menus — chat / center HTML menus (not Panorama)
- Usermessages