Skip to content

Add Panorama renderer for the EntWatch item HUD - #472

Open
tojba wants to merge 5 commits into
Source2ZE:mainfrom
tojba:codex/entwatch-panorama-hud
Open

tojba wants to merge 5 commits into
Source2ZE:mainfrom
tojba:codex/entwatch-panorama-hud

Conversation

@tojba

@tojba tojba commented Oct 18, 2026

Copy link
Copy Markdown

Add an optional Panorama (custom_hud_layout) renderer for the EntWatch item HUD

Summary

EntWatch's item HUD is currently drawn with a per-player point_worldtext
entity parented to the player's view. This PR adds a second renderer based on a
single shared custom_hud_layout (Panorama) entity, and moves the HUD code
behind a small renderer-independent interface so both can coexist.

World text stays the default. Panorama is opt-in per server
(entwatch_hud_style 1) or per player (!hudstyle new). Existing servers should
see no change unless they turn it on.

Why

The world-text HUD has limits that come from how it is built:

  • It is a text entity, so it has one font, one color per player, and no icons or
    layout.
  • Position and size are in world units relative to the camera. They are fiddly
    to tune and look different on different resolutions and FOVs.
  • Every player needs their own entity, parented and teleported around.

A Panorama layout is drawn by the client as normal UI. That allows a proper
per-item row (name, owner, ability cooldown, remaining uses / counter value),
and screen-percentage positioning that behaves the same on every resolution.
There is one shared entity; per-player differences are sent as per-slot CSS
classes and dialog variables.

What changed

Code structure

  • entwatch_hud.h / entwatch_hud.cpp: item rows are gathered once per update
    into a renderer-independent EntwatchHudRow list (name, owner, state,
    cooldown, ammo), then handed to whichever renderer each player uses.
  • entwatch_hud_worldtext.cpp: the existing world-text renderer, moved out of
    entwatch.cpp / playermanager.cpp with its behavior preserved. It does not
    depend on Panorama.
  • The Panorama renderer keeps per-player state and only sends what changed
    (classes for position/size/mode, dialog variables for changed rows).
    Up to 64 rows plus an overflow counter are supported.
  • If the compiled Panorama resources are missing, it warns, falls back to world
    text, and retries every 30 seconds.
  • EW_UpdateHud() is now also called on config unload, round start,
    !hud, and when preferences load, so the HUD hides or updates immediately
    instead of waiting for the next tick.

Player-facing

  • New !hudstyle [old|new|default]: choose the renderer. Stored per player.
  • !hudpos [x y|reset] and !hudsize [size|reset] now configure the player's
    active renderer. Each renderer has its own stored values:
    • world text: unchanged behavior and preference keys;
    • Panorama: position as whole screen percentages (0 to 95, default 2% / 15%),
      size as a percentage (50 to 200, default 100).
  • !hudpos / !hudsize now reject non-numeric or out-of-range input instead of
    silently using atof (which returned 0 for garbage).
  • New preference keys: entwatch_hud_style, entwatch_panorama_hudpos_x/y,
    entwatch_panorama_hudsize.
  • New cvars: entwatch_hud_style (server default, 0 = world text),
    cs2f_entwatch_panorama_debug (extra logging).

Panorama resources and tooling

  • Sources live in assets_source/panorama. hud.xml and hud.css are
    generated by scripts/generate-panorama.py from small templates, so the 64
    item rows and the per-position / per-size CSS classes are not edited by hand.
    Row count, position cap and size range are read from the C++ constants so the
    two sides cannot drift apart (--check fails if the generated files are stale).
  • scripts/compile-panorama.py runs the generator, then the CS2 Workshop Tools
    resourcecompiler, and copies the results to assets/panorama. It is plain
    Python (the same language as the rest of the build scripts) rather than a
    PowerShell script.
  • The two compiled files (hud.vxml_c, hud.vcss_c) are checked in, so
    normal and release builds don't need Workshop Tools. They only change when the
    HUD layout changes. PackageScript copies them into the package next to the
    existing sounds and particles.

Small robustness fixes touched along the way

  • Null/bounds checks in customhudlayout.cpp (click/disconnect callbacks, slot
    range) and a null handler check in GetHandlerStateText.
  • Preference setters clamp or validate loaded values.
  • EW_DestroyHud() on plugin unload.

Things reviewers should know

  • Clients need the resources too. As far as I can tell from the Source 2
    docs, Panorama files reach clients through a packed workshop addon or a
    workshop map (panorama/layout/custom_game and the styles folder are part of
    the publishable content), not through the server plugin. The compiled files
    have to be on the server and shipped to clients the same way as the existing
    CS2Fixes particles/sounds. I have not tested what a client without the files
    sees, so this is from documentation, not from observation. That is another
    reason Panorama is off by default. Input from people who already distribute
    custom content to players would be welcome.
  • Compiled binaries in the repo. assets/panorama/**/*_c are binary
    outputs from Valve's compiler. I kept them in the tree so releases do not
    depend on a Steam login in CI. Happy to change that if you prefer another
    approach.
  • Item list only grows downward. Reversing the order so the HUD can sit at
    the bottom of the screen was considered and skipped for now.

Testing

Honest scope: this has been tested in singleplayer, on a couple of maps,
with test data
(simulated EntWatch items), not on a populated server.

  • Both renderers show/hide correctly, switch live with !hudstyle, and update
    cooldown / uses / counter values.
  • !hudpos, !hudsize and reset work for both styles, and values survive
    reconnecting.
  • Fallback to world text works when the compiled resources are missing.
  • The generator reproduces the previous hand-made hud.xml / hud.css
    byte for byte (checked before the 95% position cap was applied).

Not yet tested: multiple real players at once, many simultaneous items,
real map configs on a live server, slot reuse after disconnect/reconnect with
several players, and different client resolutions / aspect ratios. Panorama
should be tried on more servers and maps before it becomes the default, which
is why the config default is world text.

Disclosure

The code was written by an AI assistant (Claude) and reviewed by me. I am not
very familiar with C++ or with CS2Fixes / CS2 server modding, so extra
scrutiny is welcome, especially of the custom_hud_layout usage, and the entity lifecycle (map change, round restart,
plugin unload).

tojba and others added 5 commits October 12, 2026 00:08
- Only start the 30s layout retry cooldown on failure
- Compare per-player Panorama state directly instead of building signature
  strings, and cache the last world-text message per player
- Gate !hudstyle on entwatch/entwatch_hud, remove stale world-text entities
  once a player renders with Panorama
- Don't write default HUD style/position/size back to preferences on load
- Cap Panorama HUD position at 95%
- Generate hud.xml/hud.css from templates (scripts/generate-panorama.py)
- Replace compile-panorama.ps1 with a cross-platform Python script
- Check in compiled Panorama resources and drop the CI Workshop Tools download
- Revert whitespace-only churn in unrelated files

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XagcvZZBfqEnBVSzXh88yb
Panorama still needs more testing on real servers and maps, and requires
the packaged panorama resources on server and clients, so keep the
existing world-text renderer as the default. Panorama stays opt-in via
entwatch_hud_style 1 or !hudstyle new.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XagcvZZBfqEnBVSzXh88yb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant