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.
CS2 has an entity, custom_hud_layout, that makes every client draw a
Panorama layout on top of the game HUD. The SDK exposes it as
CCSCustomHudLayout: you create one for a layout, show or change parts of it
per player, and get a callback when a player clicks a button in it. Menus,
shops, vote prompts, status bars and toasts can all be built this way.
HUD menus, texts and prompts
Layouts
Click callbacks
Authoring
What the server can and cannot do
The layout itself lives on the client, as a compiled file. The server does not send panels, positions, colours or images. It can do exactly three things to a layout, all through networked fields of the entity:
- Toggle a CSS class on a panel, by the panel's
id. - Set a dialog variable string on a panel, which fills a
text="{s:name}"placeholder. - Enable input capture, so the player gets a mouse cursor and can click.
Everything else follows from those. A hidden row is a class. An accent colour is one of several classes the stylesheet defines. A progress bar is a ladder of classes. Plan the layout around that: anything the server should be able to change has to be baked into the stylesheet as a class.
Each of the three can be set globally (every player sees it) or per player (overrides the global value for one player only). That is how one entity shows a different menu page to each player.
What the client needs
The client resolves the layout from its own files:
panorama/layout/custom_game/<name>.vxml_c compiled layout
panorama/styles/custom_game/<name>.vcss_c compiled stylesheet it includes (conventional location)CCSCustomHudLayout::Create("my_panel") points the entity at
panorama/layout/custom_game/my_panel.vxml_c, so you pass only the name.
If a client does not have that file, it draws nothing.
Getting those files onto players' machines is a Workshop addon plus something on the server that hands it out; see Authoring before you build something players are meant to see.
The flow
plugin client
------ ------
CCSCustomHudLayout::Create("menu") --> loads panorama/layout/custom_game/menu.vxml_c
SetDialogVariableString(...) --> {s:title} shows the text
SetHasClass("Root", "show", true) --> the panel becomes visible
SetInputCaptureEnabled(true) --> mouse cursor, buttons clickable
<-- player clicks <Button id="buy_ak">
HookClick callback("buy_ak")
SetHasClass("Root", "show", false) --> hidden againThe click travels back as a user message (CCSUsrMsg_CustomHudClicked) that
the core already listens for, and is routed to the plugin that registered a
callback on that layout entity.
Headers
#include "source2mod/schema/entity/classes/CCSCustomHudLayout.h"
#include "source2mod/ISource2ModCustomHud.h" // only for the HOOK_CUSTOM_HUD_CLICK macrosReferences: CCSCustomHudLayout, ISource2ModCustomHud.