diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs index a1bc237..eb93e6c 100644 --- a/jRandomSkills - SRC Files/src/jRandomSkills.cs +++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs @@ -104,6 +104,9 @@ namespace src if (SkillUtils.TryClaimCurse(curser.Index, victimIndex)) return true; + if (!SkillUtils.AnyCurseCapacity(curser)) + return SkillUtils.TryClaimCurse(curser.Index, victimIndex, true); + var curserEvent = PlayerManager.GetPlayerFromEvent(curser); curserEvent?.PrintToChat($" {ChatColors.Red}{curserEvent.GetTranslation("curse_limit_info", victim.PlayerName)}"); return false; @@ -122,7 +125,7 @@ namespace src SkillsUsedThisMap.TryAdd(skill, 0); } - if (Enum.TryParse(skill, out var parsedSkill) && SkillUtils.IsCurseSkill(parsedSkill)) + if (SkillUtils.CurseLimitEnabled && SkillUtils.IsCurseSkill(skill)) { if (methodName == "DisableSkill" && param?.Length > 0 && param[0] is CCSPlayerController curser && curser.IsValid) SkillUtils.ReleaseCurse(curser.Index); diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs index 5a2bbd0..fe62a16 100644 --- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs +++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs @@ -478,6 +478,8 @@ namespace src.player foreach (var skill in SkillData.Skills) Instance.SkillAction(skill.Skill.ToString(), "PlayerDisconnect", [leavingIndex]); + SkillUtils.ClearCursesFor(leavingIndex); + PlayerManager.UnregisterPlayer(player.Index); EntityManager.DestroyPlayerEntities(player.Index); diff --git a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs index 046b2e7..3c90713 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Spectator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Spectator.cs @@ -13,6 +13,7 @@ namespace src.player.skills private const Skills skillName = Skills.Spectator; private const string cameraViewModel = "models/sprays/spray_plane.vmdl"; private static readonly ConcurrentDictionary cameras = []; + private static readonly ConcurrentDictionary lastUse = []; public static void LoadSkill() { @@ -26,6 +27,15 @@ namespace src.player.skills EntityManager.DestroyEntity(info.Value.Item2); cameras.Clear(); + lastUse.Clear(); + } + + public static void PlayerDisconnect(uint playerIndex) + { + if (cameras.TryRemove(playerIndex, out var cameraInfo) && cameraInfo.Item2 != 0) + EntityManager.DestroyEntity(cameraInfo.Item2); + + lastUse.TryRemove(playerIndex, out _); } public static void WeaponPickup(EventItemPickup @event) @@ -45,6 +55,11 @@ namespace src.player.skills { var playerPawn = player.PlayerPawn.Value; if (playerPawn?.CBodyComponent == null) return; + + float cooldown = SkillsInfo.GetValue(skillName, "useCooldown"); + if (lastUse.TryGetValue(player.Index, out var last) && (DateTime.Now - last).TotalSeconds < cooldown) return; + lastUse[player.Index] = DateTime.Now; + ChangeCamera(player); } @@ -54,6 +69,7 @@ namespace src.player.skills ChangeCamera(player, true); EntityManager.DestroyPlayerEntities(player.Index); cameras.TryRemove(player.Index, out _); + lastUse.TryRemove(player.Index, out _); } public static void OnTick() @@ -141,12 +157,19 @@ namespace src.player.skills p.PlayerPawn.Value.Health > 0).ToList(); if (enemies.Count == 0) + { + EntityManager.DestroyEntity(camera.Index); return 0; + } var enemy = enemies[Instance.Random.Next(enemies.Count)]; var pawn = enemy.PlayerPawn.Value; - if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null) return 0; + if (pawn == null || !pawn.IsValid || pawn.CameraServices == null || pawn.AbsOrigin == null) + { + EntityManager.DestroyEntity(camera.Index); + return 0; + } QAngle angle = new(0, pawn.EyeAngles.Y, 0); @@ -204,9 +227,10 @@ namespace src.player.skills } } - public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float distance = 100f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#42f5da", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float distance = 100f, float useCooldown = .5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public float Distance { get; set; } = distance; + public float UseCooldown { get; set; } = useCooldown; } } } \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/Thief.cs b/jRandomSkills - SRC Files/src/player/skills/Thief.cs index 536d276..246c3d4 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Thief.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Thief.cs @@ -25,21 +25,7 @@ namespace src.player.skills var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (playerInfo?.Skill != skillName) continue; - var enemies = PlayerManager.GetTickPlayers().Where(p => - p != null && - p.IsValid) - .Select(p => PlayerManager.GetPlayerEvent(p)) - .Where(p => - p != null && - p.IsValid && - p.Team != player.Team && - p.PlayerPawn?.Value != null && - p.PlayerPawn.Value.IsValid && - p.PlayerPawn.Value.Health > 0 && - !p.IsHLTV && - p.Team != CsTeam.Spectator - && p.Team != CsTeam.None - ).ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = []; foreach (var enemy in enemies) @@ -106,14 +92,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = PlayerManager.GetTickPlayers() - .Where(p => p != null - && p.IsValid - && p.Team != player.Team - && p.PlayerPawn?.Value?.Health > 0 - && p.Team != CsTeam.Spectator - && p.Team != CsTeam.None) - .ToArray(); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { diff --git a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs index 7878bda..0edd992 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs @@ -21,6 +21,8 @@ namespace src.player.skills private static readonly Color terroristWire = Color.FromArgb(255, 255, 64, 64); private static readonly Color counterTerroristWire = Color.FromArgb(255, 64, 128, 255); + private const double attemptCooldown = .5; + public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); @@ -125,6 +127,9 @@ namespace src.player.skills if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) || !skillInfo.CanUse) return; + if ((DateTime.Now - skillInfo.LastAttempt).TotalSeconds < attemptCooldown) return; + skillInfo.LastAttempt = DateTime.Now; + if (!TryPlaceWire(player)) { playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_no_wall_info")); @@ -306,6 +311,7 @@ namespace src.player.skills { public bool CanUse { get; set; } public DateTime Cooldown { get; set; } + public DateTime LastAttempt { get; set; } } public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff3b3b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Rare, float radarDuration = 5f, float triggerRadius = 24f, float wireHeight = 30f, float wireWidth = 1.5f, float maxWallDistance = 400f, float cooldown = 20f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs index 17df931..9ebf612 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs @@ -373,17 +373,35 @@ namespace src.utils Skills.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch, Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap, Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison, - Skills.PrimaryBan, Skills.WildThrow + Skills.PrimaryBan, Skills.Thief, Skills.WildThrow ]; + private static readonly HashSet curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal); + private static readonly Dictionary curseCounts = []; private static readonly Dictionary curserToVictim = []; private static readonly object curseLock = new(); + private static readonly Config.GameModes[] sharedSkillModes = + [Config.GameModes.TeamSkills, Config.GameModes.SameSkills, Config.GameModes.Debug]; + + public static bool CurseLimitEnabled + { + get + { + if (Config.LoadedConfig.CurseSkillPerPlayer is not int limit || limit <= 0) return false; + return Array.IndexOf(sharedSkillModes, (Config.GameModes)Config.LoadedConfig.GameMode) < 0; + } + } + public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill); + public static bool IsCurseSkill(string skill) => curseSkillNames.Contains(skill); + public static void ClearCurses() { + if (!CurseLimitEnabled) return; + lock (curseLock) { curseCounts.Clear(); @@ -393,23 +411,24 @@ namespace src.utils public static bool CanCurse(uint victimIndex) { - int? limit = Config.LoadedConfig.CurseSkillPerPlayer; - if (limit == null || limit <= 0) return true; + if (!CurseLimitEnabled) return true; + int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value; lock (curseLock) return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit; } - public static bool TryClaimCurse(uint curserIndex, uint victimIndex) + public static bool TryClaimCurse(uint curserIndex, uint victimIndex, bool force = false) { - int? limit = Config.LoadedConfig.CurseSkillPerPlayer; + if (!CurseLimitEnabled) return true; + int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value; lock (curseLock) { ReleaseCurseLocked(curserIndex); curseCounts.TryGetValue(victimIndex, out int used); - if (limit != null && limit > 0 && used >= limit) return false; + if (!force && used >= limit) return false; curseCounts[victimIndex] = used + 1; curserToVictim[curserIndex] = victimIndex; @@ -419,9 +438,25 @@ namespace src.utils public static void ReleaseCurse(uint curserIndex) { + if (!CurseLimitEnabled) return; + lock (curseLock) ReleaseCurseLocked(curserIndex); } + public static void ClearCursesFor(uint playerIndex) + { + if (!CurseLimitEnabled) return; + + lock (curseLock) + { + ReleaseCurseLocked(playerIndex); + curseCounts.Remove(playerIndex); + + foreach (var curser in curserToVictim.Where(kvp => kvp.Value == playerIndex).Select(kvp => kvp.Key).ToList()) + curserToVictim.Remove(curser); + } + } + private static void ReleaseCurseLocked(uint curserIndex) { if (!curserToVictim.Remove(curserIndex, out uint victimIndex)) return; @@ -435,13 +470,29 @@ namespace src.utils { if (player == null || !player.IsValid) return []; + var enemies = GetAliveEnemies(player); + if (!respectCurseLimit || !CurseLimitEnabled || enemies.Length == 0) return enemies; + + var withCapacity = enemies.Where(p => CanCurse(p.Index)).ToArray(); + return withCapacity.Length > 0 ? withCapacity : enemies; + } + + public static bool AnyCurseCapacity(CCSPlayerController player) + { + if (!CurseLimitEnabled) return true; + if (player == null || !player.IsValid) return true; + + return GetAliveEnemies(player).Any(p => CanCurse(p.Index)); + } + + private static CCSPlayerController[] GetAliveEnemies(CCSPlayerController player) + { return [.. PlayerManager.GetTickPlayers() .Where(p => p != null && p.IsValid) .Select(PlayerManager.GetPlayerEvent) .Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 - && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None - && (!respectCurseLimit || CanCurse(p.Index))) + && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None) .Cast()]; } diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll index c08b847..93c92cd 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json index 239e302..b0f1c45 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json @@ -206,6 +206,7 @@ }, { "Distance": 100.0, + "UseCooldown": 0.5, "NeedsTeammates": false, "DisableOnFreezeTime": false, "OnlyTeam": 0, @@ -1708,7 +1709,7 @@ "HudDuration": null, "DescriptionHudDuration": null, "RequiredPermission": "", - "MaxPerServer": -1, + "MaxPerServer": 1, "Rarity": "Uncommon" }, { diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll index ee43dd7..0245fd7 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ