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 call | Drawn with | Layout |
|---|---|---|
ICustomhudManager::OpenMenu with a HudMenu | one custom_hud_layout, per-player state, through the core's menu system | s2t_menu |
ICustomhudManager — ShowText, ShowPrompt, ShowToast, … | one custom_hud_layout, per-player state | s2t_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):
#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–6pick the options of the page,7previous page,8next page,9close — 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
DisabledEvaluatorflipped, the menu was rebuilt in place from a handler, the player died).
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:
| Field | Default | |
|---|---|---|
CaptureInput | true | A 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. |
CaptureWhenDead | true | A 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. |
Position | Left | HudMenuPosition::Left, Center or Right; a class (pos-left, pos-center, pos-right) on menu_root the stylesheet places. |
DimBackground | true | Darkens 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);HudSlot | Where (reference stylesheet) |
|---|---|
Top | top centre — announcements |
TopLeft, TopRight | the corners |
Left, Right | beside the crosshair |
Center | over the crosshair — countdowns, "START" |
Bottom | above the weapon HUD |
Panel | a 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:
| Call | Draws |
|---|---|
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 publishThe 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 API | ICustomhudManager — HudMenu, OpenMenu and every element; the Menus guide for everything a menu does |
| The plugin | CustomhudManager_s2t on GitHub: the .smx, the reference layouts, the build |
| Your own layouts | Layouts, Click callbacks, Authoring |
| Sample plugin | section 11, sample_hudmenu, sample_hudtext, sample_prompt |
Panorama HUD
Server-driven Panorama layouts with custom_hud_layout — what the server controls, what the client needs, and how a click gets back to your plugin.
ICustomhudManager
On-screen text, prompts, toasts, announcements, countdowns, status chips, progress bars, hit feedback, an event feed and overlays on the Panorama HUD.