feat: Panorama skill HUD and menu, CSS 1.0.375

- Skill card and W/S/E target menu drawn as CCSCustomHudLayout panels via PanoramaManager
  (panorama/ sources), replacing PrintToCenterHtml and WASDMenuAPI; WASDMenuAPI.dll dropped
- Custom status lines (cooldowns, charges) overwrite the description on the card; Medic keeps its
  description with the charge count below
- css_setoverride toggles a per-player cooldown bypass
- YourSkillChatInfo also gates the round-start skill description in chat
- Language: "[css_useSkill]" becomes the configured button name, "Press ... key" wording,
  welcome message trimmed to its first line
- Build against CounterStrikeSharp.API 1.0.375
This commit is contained in:
Astra 2026-09-24 21:28:37 +01:00
parent 20dafd2a7c
commit 793370a55f
37 changed files with 1076 additions and 415 deletions

View file

@ -3,13 +3,13 @@ using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using PanoramaManager;
using src.command;
using src.player;
using src.utils;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text.Json;
using WASDSharedAPI;
using static CounterStrikeSharp.API.Core.Listeners;
namespace src
@ -22,8 +22,21 @@ namespace src
public IEnumerable<jSkill_PlayerInfo> SkillPlayer => PlayerManager.GetAllPlayers();
public Random Random => Random.Shared;
public CCSGameRules? GameRules { get; set; }
// The entity backing GameRules above. SetStateChanged needs the entity itself,
// not the GameRules subobject, so this is kept alongside it.
public CCSGameRulesProxy? GameRulesProxy { get; set; }
private ConcurrentBag<string> ManifestResources { get; set; } = ["models/sprays/spray_plane.vmdl"];
public IWasdMenuManager? MenuManager;
// Stage 1 of the Panorama HUD replacement (see PlayerEvents.cs's UpdateSkillHUD and
// CLAUDE.md's "Panorama HUD" section) - drives skill_hud.xml for skills that don't set a
// custom PrintHTML override. Own LayoutContract, not LayoutContract.Default: PanoramaManager
// warns two layouts sharing a root id silently share dialog variables across plugins.
public static readonly LayoutContract SkillHudContract = new()
{
RootPanelId = "SkillHudRoot",
RowCount = 0,
CaptureInput = false,
};
public PanelHandle? SkillHud;
// Skills that were enabled at least once this round; used to reset only those on round change (not all 124).
public static readonly ConcurrentDictionary<string, byte> ActiveSkillsThisRound = new();
public static readonly ConcurrentDictionary<string, byte> SkillsUsedThisMap = new();
@ -44,10 +57,13 @@ namespace src
PlayerOnTick.Load();
Event.Load();
Command.Load();
WASDMenuAPI.WASDMenuAPI.LoadPlugin(Instance, hotReload);
LoadAllSkills();
PlayerManager.SyncWithPlugin(Instance);
Panorama.Init(this);
SkillHud = Panorama.Spawn("panorama/layout/custom_game/skill_hud.vxml_c", SkillHudContract);
PanoramaMenu.Load(this);
Instance.RegisterListener<OnServerPrecacheResources>(LoadManifest);
Task.Run(async () =>
@ -65,6 +81,10 @@ namespace src
Event.Unload();
Debug.Unload();
SkillHud?.Dispose();
PanoramaMenu.Unload();
Panorama.Shutdown();
base.Unload(hotReload);
}
@ -160,6 +180,10 @@ namespace src
if (method == null) return null;
if (methodName == "UseSkill" && param?.Length > 0 && param[0] is CCSPlayerController user
&& user.IsValid && CooldownOverride.Has(user))
CooldownOverride.ResetBeforeUse(method.DeclaringType!, user);
if (!PerfLog.Enabled)
return method.Invoke(null, param);
@ -277,7 +301,6 @@ namespace src
Console.WriteLine($"\nDependences:");
var files = new Dictionary<string, string> {
{ "Newtonsoft Json", "./Newtonsoft.Json.dll" },
{ "WASDMenuAPI", "./WASDMenuAPI.dll" },
{ "MaxMind", "./MaxMind.Db.dll" },
{ "GeoLite2", "./packages/GeoLite2-Country.mmdb" },
{ "RayTraceApi", "./../../shared/RayTraceApi/RayTraceApi.dll" },
@ -385,6 +408,23 @@ namespace src
public string? Content;
}
// Change-detection so SetVariableFor/SetClassFor are only called when something actually
// changed, same principle as HudCacheEntry above (its cache.Content re-send is the same idea,
// just at a coarser granularity since PrintToCenterHtml takes one blob, not per-field writes).
// TouchedThisTick is reset and checked once per HUD frame in PlayerOnTick.cs - it's what closes
// the panel for a player who stopped being updated (warmup started, menu opened, HUD
// suppressed, ...) instead of it silently showing stale content forever, which a Panorama panel
// would otherwise do - PrintToCenterHtml expires on its own after a few seconds; this doesn't.
public sealed class PanoramaHudCache
{
public bool Open;
public bool TouchedThisTick;
public string? Skill;
public string? Extra;
public string? RarityClass;
public bool? IsInfo;
}
public class jSkill_SkillInfo(Skills skill, string color, bool display)
{
public Skills Skill { get; } = skill;