using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Utils; using src.utils; using System.Collections.Concurrent; namespace src.player.skills { public class TrueArmor : ISkill { private const Skills skillName = Skills.TrueArmor; private static readonly ConcurrentDictionary holders = []; private static readonly ConcurrentDictionary pending = []; public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); } public static void NewRound() { pending.Clear(); } public static void EnableSkill(CCSPlayerController player) { if (player == null || !player.IsValid) return; holders[player.Index] = 0; } public static void DisableSkill(CCSPlayerController player) { if (player == null) return; holders.TryRemove(player.Index, out _); pending.TryRemove(player.Index, out _); } public static void PlayerDisconnect(uint playerIndex) { holders.TryRemove(playerIndex, out _); pending.TryRemove(playerIndex, out _); } public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo) { if (holders.IsEmpty) return; if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out var info)) return; if (!holders.ContainsKey(victim!.Index)) return; int armor = victimPawn!.ArmorValue; if (armor <= 0) return; float damage = info!.Damage; if (damage <= 0) return; float ratio = SkillsInfo.GetValue(skillName, "armorShare"); float absorbed = MathF.Min(damage * ratio, armor); if (absorbed <= 0) return; info.Damage = damage - absorbed; int restoredArmor = (int)MathF.Round(armor - absorbed); bool hadHelmet = victimPawn.ItemServices?.As()?.HasHelmet ?? false; pending[victim.Index] = new PendingArmor(restoredArmor, hadHelmet); victimPawn.ArmorValue = 0; Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); Debug.WriteToDebug($"[TrueArmor] {victim.PlayerName}: raw={damage:0.#} absorbed={absorbed:0.#} " + $"passed={info.Damage:0.#} armor={armor}->{restoredArmor} helmet={hadHelmet} hp={victimPawn.Health}", DebugCategory.Damage); } public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult) { if (pending.IsEmpty) return; if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out _)) return; if (!pending.TryRemove(victim!.Index, out var restored)) return; victimPawn!.ArmorValue = restored.Armor; Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); if (!restored.HasHelmet) return; var itemServices = victimPawn.ItemServices?.As(); if (itemServices == null || itemServices.HasHelmet) return; itemServices.HasHelmet = true; Utilities.SetStateChanged(victim, "CCSPlayerController", "m_bPawnHasHelmet"); } private readonly record struct PendingArmor(int Armor, bool HasHelmet); private static bool TryResolveVictim(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info) { victim = null; pawn = null; info = null; if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return false; var victimPawn = damagedEntity.As(); if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return false; var controller = victimPawn.Controller.Value; if (controller == null || !controller.IsValid) return false; var resolved = controller.As(); if (resolved == null || !resolved.IsValid) return false; var routed = PlayerManager.GetPlayerEvent(resolved); victim = routed != null && routed.IsValid ? routed : resolved; pawn = victimPawn; info = damageInfo; return true; } public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9ad3f5", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Uncommon, float armorShare = .33f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public float ArmorShare { get; set; } = armorShare; } } }