HUD Menus, Texts and Prompts

Menus, on-screen text and HUD elements drawn by Panorama through the CustomhudManager_s2t plugin — HudMenu, ICustomhudManager, the reference layouts and the addon that ships them.

The layout and click pages are the raw material: your own custom_hud_layout, your own ids, your own state. For the things nearly every plugin wants — a menu, a line of text on the screen, a "press E" prompt, a toast, a timer — the CustomhudManager_s2t plugin does that work, with two layouts it owns:

You callDrawn withLayout
ICustomhudManager::OpenMenu with a HudMenuone custom_hud_layout, per-player state, through the core's menu systems2t_menu
ICustomhudManager — ShowText, ShowPrompt, ShowToast, …one custom_hud_layout, per-player states2t_hud

Each entity is spawned on first use once the map's game rules exist, and goes with the map. The plugin resets a slot's state when its player disconnects and the core closes a plugin's open menus when that plugin unloads.

Nothing here works without the layout on the client: the CustomhudManager_s2t repository ships the reference layouts, and the addon is how they get to the players. A player without it sees nothing — the server cannot tell.

Getting the interface

ICustomhudManager is the plugin's own interface, not one of the core's: the header is public/ICustomhudManager.h in the plugin's repository (vendor it, a git submodule is the usual way, and add public/ to your include path), you declare the pointer, and you fetch it once every plugin is loaded, treating a null pointer as "no HUD" (the plugin is not installed, or it serves another revision than your header):

plugin.cpp
#include "ICustomhudManager.h"

ICustomhudManager* g_pCustomhudManager = nullptr;

void MyPlugin::OnAllSource2ModPluginsLoaded()
{
    int ret;
    GET_SOURCE2MOD_IFACE(g_pCustomhudManager, ICustomhudManager, CUSTOMHUD_MANAGER_INTERFACE_VERSION, ret);
    if (ret != SOURCE2MOD_IFACE_OK)
        SOURCE2MOD_LOG(this, "CustomhudManager_s2t is not loaded: nothing will be drawn on the HUD\n");
}

The plugin itself is one .smx in addons/source2mod/plugins/, from the CustomhudManager_s2t releases.

HUD menus

A HudMenu is a CenterHtmlMenu drawn on the HUD instead of in the center panel. Same IBaseMenu, same options and handlers, same PostSelectAction, same cooldown hooks; the differences:

  • the rows are clickable — the plugin enables input capture while the menu is open and turns a click on a row into the key the row stands for;
  • 1–6 pick the options of the page, 7 previous page, 8 next page, 9 close — the chat triggers (!3) and console keys keep working;
  • the navigation texts (PrevText, NextText, CloseText) are the plugin's, so they can be in the player's language;
  • it is not redrawn every frame: it is redrawn when what would be drawn changed (an option's DisabledEvaluator flipped, the menu was rebuilt in place from a handler, the player died).
plugin.cpp
static HudMenu g_ShopMenu("Shop");

void OpenShop(CCSPlayerController* player)
{
    g_ShopMenu.ClearOptions();
    g_ShopMenu.AddMenuOption("AWP  -  4750", [](CCSPlayerController* p, ChatMenuOption&) { Buy(p, "weapon_awp"); });
    g_ShopMenu.AddMenuOption("AK-47 - 2700", [](CCSPlayerController* p, ChatMenuOption&) { Buy(p, "weapon_ak47"); });

    // In the player's language.
    g_ShopMenu.PrevText = Translate(player, "menu.prev");
    g_ShopMenu.NextText = Translate(player, "menu.next");
    g_ShopMenu.CloseText = Translate(player, "menu.close");

    g_pCustomhudManager->OpenMenu(g_PluginID, player, &g_ShopMenu);
}

Four fields shape it per open, so a plugin can make them the player's choice:

FieldDefault
CaptureInputtrueA cursor and clickable rows — and no moving or aiming while the menu is open. false is display-only: the number keys pick, the player keeps playing. With the cursor on, the number keys are ignored.
CaptureWhenDeadtrueA dead player cannot use the slot binds (they do nothing without a pawn), so a menu without CaptureInput takes the mouse while the player is dead and lets go on respawn. The keys and chat triggers keep working meanwhile.
PositionLeftHudMenuPosition::Left, Center or Right; a class (pos-left, pos-center, pos-right) on menu_root the stylesheet places.
DimBackgroundtrueDarkens the screen behind the window.

The rules on ownership and lifetime are the same as for a center HTML menu.

A player without the addon sees no menu — and, with input capture on, has a cursor. Where you cannot assume the addon, open a CenterHtmlMenu instead; the two share IBaseMenu, so a menu object can be either.

Texts

ICustomhudManager puts a text into one of eight slots — positions the stylesheet defines — with a colour and a size that are classes of the stylesheet too. A plugin never sends a colour, only a name; and where it wants a text somewhere else than the slot's place, it says so in steps, not pixels.

#include "ICustomhudManager.h"

// Five seconds, top of the screen, yellow and large.
g_pCustomhudManager->ShowText(player, HudSlot::Top, "Round starts in 5", 5.0f, { HudColor::Yellow, HudSize::Large });

// A boxed multi-line panel, until it is hidden.
g_pCustomhudManager->ShowText(player, HudSlot::Panel, "Quest: 3 / 10 kills\nReward: 500 credits", 0.0f);
g_pCustomhudManager->HideText(player, HudSlot::Panel);
HudSlotWhere (reference stylesheet)
Toptop centre — announcements
TopLeft, TopRightthe corners
Left, Rightbeside the crosshair
Centerover the crosshair — countdowns, "START"
Bottomabove the weapon HUD
Panela boxed panel on the right — quests, info

Moving a slot

HudTextStyle carries offsetX and offsetY (ICustomhudManager003): steps of 40 px on the 1080p reference the stylesheet is written for, -10 .. 10, positive is right and down. Panorama scales the whole layout with the resolution, so a step is the same share of the screen on 1080p, 1440p and 4K. The core turns the two numbers into the classes xm10 .. xp10 and ym10 .. yp10 on the slot panel; the reference stylesheet generates them, and a stylesheet of your own can give them any meaning. Everything else about the slot (its anchor, colour, size) stays.

HudTextStyle style{ HudColor::Blue, HudSize::Normal };
style.offsetY = 2;   // 80 px below the top slot's place
g_pCustomhudManager->ShowText(player, HudSlot::Top, "Round starts in 5", 4.0f, style);

The text is plain: \n starts a line, there is no markup. A text with seconds above zero goes away by itself; HideText for the rest, and HideAll for a player whose texts no longer apply (death, team change). What this replaces: point_worldtext parented to the pawn and moved every tick, and PrintToCenterHtml printed every tick to keep a panel up.

Toasts, announcements, countdown, chips, progress, hits, feed, overlays

The rest of what the hudkit gallery has, all on the same layout and all per player:

CallDraws
ShowToast(player, style, title, text, seconds)a card in the stack at the top right, newest first; four are kept. Styles Info, Success, Warning, Danger, Neutral.
ShowAnnounce(player, title, subtitle, seconds, color)the big upper-centre callout, with an entrance each time
ShowCountdown(player, "3", seconds, color)the giant centre number or word; each call pops it again, so a countdown is one call a second
ShowStatus(player, chip, label, value, color)one of four chips under the round timer, until HideStatus
ShowProgress(player, label, value, 0..1, color)a labelled bar under the crosshair, until HideProgress
ShowHit(player, damage, headshot, kill)the crosshair flashes and the number drifts up — gold on a headshot, red on a kill; gone by itself
AddFeed(player, style, time, text, seconds)a row in the event feed at the top left, newest first; five are kept
ShowOverlay(player, overlay, text, seconds)a full-screen tint: Poison, Burn, Freeze, Heal, Blind, Black, with an optional centred label
ShowTimer(player, tag, time, sub, color)the big timer: a tag pill, a large time in a halo and a line under it; each call pulses the number, so a countdown is one call a second. Until HideTimer.
ShowCard(player, tag, title, sub, color)the corner card at the top left — the day of a jail, a role, the mode. Until HideCard.
g_pCustomhudManager->ShowToast(player, HudToastStyle::Success, "Quest done", "+500 credits", 5.0f);
g_pCustomhudManager->ShowAnnounce(player, "Free day", "No orders until the next round", 5.0f, HudColor::Green);
g_pCustomhudManager->ShowStatus(player, 0, "Day", "Freeday", HudColor::Yellow);
g_pCustomhudManager->ShowHit(attacker, damage, hitgroup == 1, health <= 0);

HideAll(player) clears every one of them together with the texts and the prompt. The sample plugin's sample_hud <what> shows each.

Interaction prompts

A key cap, what the key does, and — for a hold-to-use action — a progress bar:

// While the player looks at the thing.
g_pCustomhudManager->ShowPrompt(player, "E", "Steal the weapon");

// While holding: 0..1 fills the bar.
g_pCustomhudManager->ShowPrompt(player, "E", "Stealing...", held / 2.0f);

// Looked away, or done.
g_pCustomhudManager->HidePrompt(player);

progress below zero hides the bar. The bar is a clip, not a width, so it does not restart when the texts around it change.

The addon

The plugin addresses the layouts by panel id; the reference layouts in the CustomhudManager_s2t repository (also in every release archive) are the contract, the stylesheets are the look:

panorama/layout/custom_game/s2t_menu.xml    menu — keep the ids
panorama/layout/custom_game/s2t_hud.xml     texts and the prompt — keep the ids
panorama/styles/custom_game/s2t_menu.css    yours to change
panorama/styles/custom_game/s2t_hud.css
panorama/README.md                          build and publish

The ids are listed at the top of each XML and on the ICustomhudManager page; the menu's are menu_dim, menu_root, menu_title, menu_row_0 … menu_row_5 (each with _key and _text labels), menu_prev, menu_next, menu_close (with _text labels) and menu_footer. The classes the plugin toggles: show, disabled on rows, and pos-left / pos-center / pos-right on menu_root. The layout names are fixed (s2t_menu, s2t_hud); the ids inside stay.

Getting them to players is a Workshop addon plus something on the server that hands it out — see Shipping to clients. The short version: create an addon in the CS2 Workshop Tools, copy panorama/ into its content folder, compile, publish, and install MultiAddonManager on the server with mm_extra_addons <id>.

What is where

HUD APIICustomhudManager — HudMenu, OpenMenu and every element; the Menus guide for everything a menu does
The pluginCustomhudManager_s2t on GitHub: the .smx, the reference layouts, the build
Your own layoutsLayouts, Click callbacks, Authoring
Sample pluginsection 11, sample_hudmenu, sample_hudtext, sample_prompt

On this page