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:
parent
20dafd2a7c
commit
793370a55f
37 changed files with 1076 additions and 415 deletions
67
jRandomSkills - SRC Files/src/utils/CooldownOverride.cs
Normal file
67
jRandomSkills - SRC Files/src/utils/CooldownOverride.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using CounterStrikeSharp.API.Core;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
|
||||
namespace src.utils
|
||||
{
|
||||
// Per-player cooldown bypass (css_setoverride). Skills keep their cooldown in their own static
|
||||
// per-player state, so instead of touching every skill this clears that state just before the
|
||||
// skill's UseSkill runs. Resetting only at use time (not every tick) matters: AimLock and Noclip
|
||||
// reuse their Cooldown timestamp as the start of the active effect.
|
||||
public static class CooldownOverride
|
||||
{
|
||||
private static readonly HashSet<ulong> _players = [];
|
||||
private static readonly ConcurrentDictionary<Type, FieldInfo[]> _stateFields = new();
|
||||
|
||||
// Toggles the override and returns the new state.
|
||||
public static bool Toggle(CCSPlayerController player)
|
||||
{
|
||||
if (_players.Remove(player.SteamID)) return false;
|
||||
_players.Add(player.SteamID);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Has(CCSPlayerController player) => _players.Contains(player.SteamID);
|
||||
|
||||
public static void ResetBeforeUse(Type skillType, CCSPlayerController player)
|
||||
{
|
||||
foreach (var field in _stateFields.GetOrAdd(skillType, FindStateFields))
|
||||
{
|
||||
if (field.GetValue(null) is not IDictionary state) continue;
|
||||
|
||||
// Spectator keeps a plain index -> last-use time map.
|
||||
if (field.FieldType.GetGenericArguments()[1] == typeof(DateTime))
|
||||
{
|
||||
state.Remove(player.Index);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entry = state[player.Index];
|
||||
if (entry == null) continue;
|
||||
|
||||
foreach (var prop in entry.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (!prop.CanWrite) continue;
|
||||
|
||||
if (prop.Name == "Cooldown" && prop.PropertyType == typeof(DateTime))
|
||||
prop.SetValue(entry, DateTime.MinValue);
|
||||
else if (prop.Name == "CanUse" && prop.PropertyType == typeof(bool))
|
||||
prop.SetValue(entry, true);
|
||||
else if (prop.Name is "NextUse" or "NextCamera" && prop.PropertyType == typeof(float))
|
||||
prop.SetValue(entry, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static ConcurrentDictionary<uint, T> fields keyed by player index - the per-player skill
|
||||
// state every cooldown skill uses. DateTime-valued ones only when they are last-use maps.
|
||||
private static FieldInfo[] FindStateFields(Type skillType)
|
||||
=> [.. skillType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(f => f.FieldType.IsGenericType
|
||||
&& f.FieldType.GetGenericTypeDefinition() == typeof(ConcurrentDictionary<,>)
|
||||
&& f.FieldType.GetGenericArguments()[0] == typeof(uint)
|
||||
&& (f.FieldType.GetGenericArguments()[1] != typeof(DateTime)
|
||||
|| f.Name.Contains("use", StringComparison.OrdinalIgnoreCase)))];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue