diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs index 0349dab..a1bc237 100644 --- a/jRandomSkills - SRC Files/src/jRandomSkills.cs +++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs @@ -92,6 +92,23 @@ namespace src Debug.WriteToDebug($"Loaded: {skill.Skill}"); } + private static bool TryClaimCurseTarget(object[]? param) + { + if (param == null || param.Length < 2) return true; + if (param[0] is not CCSPlayerController curser || !curser.IsValid) return true; + if (param[1] is not string[] commands || commands.Length < 1) return true; + if (!uint.TryParse(commands[0], out uint victimIndex)) return true; + + var victim = Utilities.GetPlayerFromIndex((int)victimIndex); + if (victim == null || !victim.IsValid) return true; + + if (SkillUtils.TryClaimCurse(curser.Index, victimIndex)) return true; + + var curserEvent = PlayerManager.GetPlayerFromEvent(curser); + curserEvent?.PrintToChat($" {ChatColors.Red}{curserEvent.GetTranslation("curse_limit_info", victim.PlayerName)}"); + return false; + } + private static readonly ConcurrentDictionary<(string Skill, string Method), MethodInfo?> _skillMethodCache = new(); internal object? SkillAction(string skill, string methodName, object[]? param = null) @@ -105,6 +122,15 @@ namespace src SkillsUsedThisMap.TryAdd(skill, 0); } + if (Enum.TryParse(skill, out var parsedSkill) && SkillUtils.IsCurseSkill(parsedSkill)) + { + if (methodName == "DisableSkill" && param?.Length > 0 && param[0] is CCSPlayerController curser && curser.IsValid) + SkillUtils.ReleaseCurse(curser.Index); + + if (methodName == "TypeSkill" && !TryClaimCurseTarget(param)) + return null; + } + var method = _skillMethodCache.GetOrAdd((skill, methodName), key => { string className = $"src.player.skills.{key.Skill}"; diff --git a/jRandomSkills - SRC Files/src/lang/de.json b/jRandomSkills - SRC Files/src/lang/de.json index 0d0d15f..bebaa5d 100644 --- a/jRandomSkills - SRC Files/src/lang/de.json +++ b/jRandomSkills - SRC Files/src/lang/de.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.", "selectplayerskill_command": "Gib /t ein", "selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden", "player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S – Scrollen", "menu_controlls_padding": " | ", "menu_controlls_select": "E – Auswählen" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/en.json b/jRandomSkills - SRC Files/src/lang/en.json index 8663de9..172540e 100644 --- a/jRandomSkills - SRC Files/src/lang/en.json +++ b/jRandomSkills - SRC Files/src/lang/en.json @@ -487,7 +487,6 @@ "tripwire": "Tripwire", "tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar", - "tripwire_limit_info": "You can only have {0} tripwires at a time.", "tripwire_no_wall_info": "No walls close enough on both sides.", "tripwire_placed_info": "Tripwire placed.", "tripwire_triggered_info": "'{0}' tripped your wire.", @@ -533,6 +532,7 @@ "duplicate_player": "More than one player found with the same name.", "selectplayerskill_command": "Type /t", "selectplayerskill_incorrect_enemy_index": "No players were found to select.", + "curse_limit_info": "The curse limit for {0} has been reached", "skill_not_found_setskill": "No such CHATCOLORS.REDskill found", "player_not_found_setskill": "No such CHATCOLORS.REDplayer found", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Scroll", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Select" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/fr.json b/jRandomSkills - SRC Files/src/lang/fr.json index 64fa588..19a008a 100644 --- a/jRandomSkills - SRC Files/src/lang/fr.json +++ b/jRandomSkills - SRC Files/src/lang/fr.json @@ -486,7 +486,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -532,6 +531,7 @@ "duplicate_player": "Plus d’un joueur trouvé avec le même nom.", "selectplayerskill_command": "Tapez /t", "selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée", "player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé", diff --git a/jRandomSkills - SRC Files/src/lang/pl.json b/jRandomSkills - SRC Files/src/lang/pl.json index 9dd510d..c8958da 100644 --- a/jRandomSkills - SRC Files/src/lang/pl.json +++ b/jRandomSkills - SRC Files/src/lang/pl.json @@ -486,7 +486,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -532,6 +531,7 @@ "duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.", "selectplayerskill_command": "Wpisz /t", "selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy", "player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza", diff --git a/jRandomSkills - SRC Files/src/lang/pt-br.json b/jRandomSkills - SRC Files/src/lang/pt-br.json index e3b4a3b..907e797 100644 --- a/jRandomSkills - SRC Files/src/lang/pt-br.json +++ b/jRandomSkills - SRC Files/src/lang/pt-br.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Mais de um jogador encontrado com o mesmo nome.", "selectplayerskill_command": "Digite /t", "selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada", "player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Rolar", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Selecionar" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/ru.json b/jRandomSkills - SRC Files/src/lang/ru.json index 57f0118..f77d7cc 100644 --- a/jRandomSkills - SRC Files/src/lang/ru.json +++ b/jRandomSkills - SRC Files/src/lang/ru.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Найдено несколько игроков с таким именем.", "selectplayerskill_command": "Введите /t", "selectplayerskill_incorrect_enemy_index": "Игроки для выбора не найдены.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Навык не найден", "player_not_found_setskill": "Игрок не найден", diff --git a/jRandomSkills - SRC Files/src/lang/tr.json b/jRandomSkills - SRC Files/src/lang/tr.json index 6ccf3a3..cc64097 100644 --- a/jRandomSkills - SRC Files/src/lang/tr.json +++ b/jRandomSkills - SRC Files/src/lang/tr.json @@ -487,7 +487,6 @@ "tripwire": "Tel Tuzağı", "tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür", - "tripwire_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.", "tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.", "tripwire_placed_info": "Tel gerildi.", "tripwire_triggered_info": "'{0}' teline takıldı.", @@ -533,6 +532,7 @@ "duplicate_player": "Aynı isimde birden fazla oyuncu bulundu.", "selectplayerskill_command": "Sohbete /t yazın", "selectplayerskill_incorrect_enemy_index": "Seçilecek oyuncu yok.", + "curse_limit_info": "'{0}' üzerindeki lanet sınırı doldu, başka bir oyuncu seç", "skill_not_found_setskill": "Böyle bir yetenek yok", "player_not_found_setskill": "Böyle bir oyuncu yok", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Kaydır", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Seç" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/lang/zh.json b/jRandomSkills - SRC Files/src/lang/zh.json index bb09467..7564a60 100644 --- a/jRandomSkills - SRC Files/src/lang/zh.json +++ b/jRandomSkills - SRC Files/src/lang/zh.json @@ -484,7 +484,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -530,6 +529,7 @@ "duplicate_player": "找到多个同名玩家。", "selectplayerskill_command": "输入 /t", "selectplayerskill_incorrect_enemy_index": "未找到可供选择的玩家。", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "未找到 CHATCOLORS.RED 技能", "player_not_found_setskill": "未找到 CHATCOLORS.RED 玩家", @@ -569,4 +569,4 @@ "menu_controlls_scroll": "W/S - 滚动", "menu_controlls_padding": " | ", "menu_controlls_select": "E - 选择" -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/EntityEvents.cs b/jRandomSkills - SRC Files/src/player/EntityEvents.cs new file mode 100644 index 0000000..51c36bc --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/EntityEvents.cs @@ -0,0 +1,249 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Events; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.UserMessages; +using CounterStrikeSharp.API.Modules.Utils; +using RayTraceAPI; +using src.player.skills; +using src.utils; +using System.Collections.Concurrent; +using static CounterStrikeSharp.API.Core.Listeners; +using static src.jRandomSkills; +using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; + +namespace src.player +{ + public static partial class Event + { + private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombBeginplant", @event); + return HookResult.Continue; + } + } + + private static HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombAbortplant", @event); + return HookResult.Continue; + } + } + + private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombPlanted", @event); + return HookResult.Continue; + } + } + + private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("BombBegindefuse", @event); + return HookResult.Continue; + } + } + + private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("DecoyStarted", @event); + return HookResult.Continue; + } + } + + private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("DecoyDetonate", @event); + return HookResult.Continue; + } + } + + private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("SmokegrenadeDetonate", @event); + return HookResult.Continue; + } + } + + private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info) + { + lock (setLock) + { + DispatchToActiveSkills("SmokegrenadeExpired", @event); + return HookResult.Continue; + } + } + + private static HookResult OnTakeDamage(DynamicHook h) + { + lock (setLock) + { + DispatchOnTakeDamage(h); + + if (Fortnite.skillInThisRound == true && + !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) + Instance.SkillAction("Fortnite", "OnTakeDamage", [h]); + + return HookResult.Continue; + } + } + + private static HookResult OnTriggerEnter(DynamicHook hook) + { + lock (setLock) + { + CBaseTrigger trigger = hook.GetParam(0); + CBaseEntity entity = hook.GetParam(1); + + DispatchToActiveSkills("OnTriggerEnter", trigger, entity); + return HookResult.Continue; + } + } + + private static HookResult OnTriggerExit(DynamicHook hook) + { + lock (setLock) + { + CBaseTrigger trigger = hook.GetParam(0); + CBaseEntity entity = hook.GetParam(1); + + DispatchToActiveSkills("OnTriggerExit", trigger, entity); + return HookResult.Continue; + } + } + + private static HookResult OnWeaponCanAcquire(DynamicHook hook) + { + lock (setLock) + { + CCSPlayer_ItemServices itemServices = hook.GetParam(0); + if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue; + + CEconItemView econItem = hook.GetParam(1); + if (econItem == null) return HookResult.Continue; + + CBasePlayerPawn pawn = itemServices.Pawn.Value; + if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue; + + CCSPlayerController player = pawn.Controller.Value.As(); + if (player == null || !player.IsValid) return HookResult.Continue; + + var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); + if (playerInfo == null) return HookResult.Continue; + + CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString()); + if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue; + + var activeSkills = Instance.SkillPlayer + .Where(p => !p.IsDrawing) + .Select(p => p.Skill.ToString()) + .Distinct(); + + bool block = false; + foreach (string skillName in activeSkills) + { + bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]); + if (result == true) + { + block = true; + break; + } + } + + return block ? HookResult.Handled : HookResult.Continue; + } + } + + private static HookResult WeaponDrop(DynamicHook hook) + { + lock (setLock) + { + CCSPlayerController player = hook.GetParam(0); + if (player == null || !player.IsValid) + return HookResult.Continue; + + var activeSkills = Instance.SkillPlayer + .Where(p => !p.IsDrawing) + .Select(p => p.Skill.ToString()) + .Distinct(); + + bool block = false; + foreach (string skillName in activeSkills) + { + bool? result = (bool?)Instance.SkillAction(skillName, "WeaponDrop", [hook, player]); + if (result == true) + { + block = true; + break; + } + } + return block ? HookResult.Handled : HookResult.Continue; + } + } + + private static void EntitySpawned(CEntityInstance entity) + { + lock (setLock) + { + DispatchToActiveSkills("OnEntitySpawned", entity); + } + } + + public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList) + { + long perfStart = PerfLog.Start(); + lock (setLock) + { + try + { + // Keep dying entities out of snapshots until the engine processes the kill. + var dying = EntityManager.GetRecentlyDestroyedSnapshot(); + if (dying.Count > 0) + { + foreach (var (info, player) in infoList) + { + if (player == null || !player.IsValid) continue; + foreach (var entityIndex in dying) + if (info.TransmitEntities.Contains(entityIndex)) + info.TransmitEntities.Remove(entityIndex); + } + } + } + catch (Exception ex) + { + Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}"); + } + + DispatchToActiveSkills("CheckTransmit", infoList); + } + PerfLog.Sample("CheckTransmit", perfStart); + } + + public static void EnableTransmit() + { + if (!isTransmitRegistered) + { + Instance?.RegisterListener(CheckTransmit); + isTransmitRegistered = true; + } + } + } +} diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs index e5e4ca0..5a2bbd0 100644 --- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs +++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs @@ -107,55 +107,6 @@ namespace src.player catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] unhook failed: {ex.Message}"); } } - private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary assignmentCounts, Config.GameModes gameMode) - { - if (candidates == null || candidates.Count == 0) return noneSkill; - - bool ignoreMax = gameMode == Config.GameModes.SameSkills || gameMode == Config.GameModes.TeamSkills; - - const int attempts = 6; - for (int attempt = 0; attempt < attempts; attempt++) - { - var (roll, rolled) = RarityManager.RollRarity(); - - var filtered = candidates.Where(s => - { - if (s == null) return false; - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); - if (def == null) return false; - - if (!string.Equals(def.Rarity ?? string.Empty, rolled.ToString(), StringComparison.OrdinalIgnoreCase)) - return false; - - if (!ignoreMax && def.MaxPerServer >= 0) - { - int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; - if (current >= def.MaxPerServer) return false; - } - - return true; - }).ToList(); - - if (filtered.Count > 0) - return filtered[Random.Shared.Next(filtered.Count)]; - } - - var fallback = candidates.Where(s => - { - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); - if (def == null) return true; - if (ignoreMax) return true; - if (def.MaxPerServer < 0) return true; - int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; - return current < def.MaxPerServer; - }).ToList(); - - if (fallback.Count > 0) - return fallback[Random.Shared.Next(fallback.Count)]; - - return candidates[Random.Shared.Next(candidates.Count)]; - } - private static readonly Skills[] lateDamageSkills = [Skills.SecondLife, Skills.Phoenix, Skills.ReZombie]; private static readonly HashSet tickFailuresLogged = []; @@ -301,78 +252,6 @@ namespace src.player } } - private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombBeginplant", @event); - return HookResult.Continue; - } - } - - private static HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombAbortplant", @event); - return HookResult.Continue; - } - } - - private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombPlanted", @event); - return HookResult.Continue; - } - } - - private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("BombBegindefuse", @event); - return HookResult.Continue; - } - } - - private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("DecoyStarted", @event); - return HookResult.Continue; - } - } - - private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("DecoyDetonate", @event); - return HookResult.Continue; - } - } - - private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("SmokegrenadeDetonate", @event); - return HookResult.Continue; - } - } - - private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info) - { - lock (setLock) - { - DispatchToActiveSkills("SmokegrenadeExpired", @event); - return HookResult.Continue; - } - } - private static HookResult PlayerHurtPre(EventPlayerHurt @event, GameEventInfo info) { lock (setLock) @@ -466,113 +345,6 @@ namespace src.player } } - private static HookResult OnTakeDamage(DynamicHook h) - { - lock (setLock) - { - DispatchOnTakeDamage(h); - - if (Fortnite.skillInThisRound == true && - !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) - Instance.SkillAction("Fortnite", "OnTakeDamage", [h]); - - return HookResult.Continue; - } - } - - private static HookResult OnTriggerEnter(DynamicHook hook) - { - lock (setLock) - { - CBaseTrigger trigger = hook.GetParam(0); - CBaseEntity entity = hook.GetParam(1); - - DispatchToActiveSkills("OnTriggerEnter", trigger, entity); - return HookResult.Continue; - } - } - - private static HookResult OnTriggerExit(DynamicHook hook) - { - lock (setLock) - { - CBaseTrigger trigger = hook.GetParam(0); - CBaseEntity entity = hook.GetParam(1); - - DispatchToActiveSkills("OnTriggerExit", trigger, entity); - return HookResult.Continue; - } - } - - private static HookResult OnWeaponCanAcquire(DynamicHook hook) - { - lock (setLock) - { - CCSPlayer_ItemServices itemServices = hook.GetParam(0); - if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue; - - CEconItemView econItem = hook.GetParam(1); - if (econItem == null) return HookResult.Continue; - - CBasePlayerPawn pawn = itemServices.Pawn.Value; - if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue; - - CCSPlayerController player = pawn.Controller.Value.As(); - if (player == null || !player.IsValid) return HookResult.Continue; - - var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); - if (playerInfo == null) return HookResult.Continue; - - CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString()); - if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue; - - var activeSkills = Instance.SkillPlayer - .Where(p => !p.IsDrawing) - .Select(p => p.Skill.ToString()) - .Distinct(); - - bool block = false; - foreach (string skillName in activeSkills) - { - bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]); - if (result == true) - { - block = true; - break; - } - } - - return block ? HookResult.Handled : HookResult.Continue; - } - } - - private static HookResult WeaponDrop(DynamicHook hook) - { - lock (setLock) - { - CCSPlayerController player = hook.GetParam(0); - if (player == null || !player.IsValid) - return HookResult.Continue; - - var activeSkills = Instance.SkillPlayer - .Where(p => !p.IsDrawing) - .Select(p => p.Skill.ToString()) - .Distinct(); - - bool block = false; - foreach (string skillName in activeSkills) - { - bool? result = (bool?)Instance.SkillAction(skillName, "WeaponDrop", [hook, player]); - if (result == true) - { - block = true; - break; - } - } - return block ? HookResult.Handled : HookResult.Continue; - } - } - private static readonly Dictionary _skillNames = Enum.GetValues().ToDictionary(s => s, s => s.ToString()); private static readonly HashSet _activeSkillsSet = []; @@ -759,94 +531,6 @@ namespace src.player } } - private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info) - { - lock (setLock) - { - bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true; - isTransmitRegistered = false; - SkillUtils.ClearKillCredits(); - Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist)) - { - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) continue; - skillPlayer.IsDrawing = !isWarmup; - skillPlayer.PrintHTML = null; - } - - Instance.RemoveListener(CheckTransmit); - int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue() ?? 0; - freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); - - setSkillTimer?.Kill(); - - if (isWarmup) - { - setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return HookResult.Continue; - } - - float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f; - setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return HookResult.Continue; - } - } - - private static void DisableAll() - { - long perfStart = PerfLog.Start(); - DisableAllCore(); - PerfLog.End("DisableAll total", perfStart, 2.0); - } - - private static void DisableAllCore() - { - lock (setLock) - { - // Re-register CheckTransmit so the dying-entity filter covers the kills below. - EnableTransmit(); - - Fortnite.skillInThisRound = false; - EntityManager.DestroyAllTracked(); - - foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid)) - { - if (player == null || !player.IsValid) continue; - - var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - if (playerInfo == null) continue; - - ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0); - SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0); - if (playerInfo.SpecialSkill != noneSkill.Skill) - { - ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0); - SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0); - } - - Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]); - - playerInfo.Skill = noneSkill.Skill; - playerInfo.SpecialSkill = noneSkill.Skill; - playerInfo.PrintHTML = null; - playerInfo.SkillChance = 1; - playerInfo.SkillUsed = false; - - RestorePlayer(player); - } - - // Reset every skill used so far on this map, not only the ones held this round: a skill - // nobody drew now would otherwise never clear state left over from an earlier round. - // Skills that never ran cannot hold state, so they stay out of the sweep. - foreach (var skillName in SkillsUsedThisMap.Keys) - Instance.SkillAction(skillName, "NewRound"); - ActiveSkillsThisRound.Clear(); - tickFailuresLogged.Clear(); - } - } - public static void RestorePlayer(CCSPlayerController? player) { if (player == null || !player.IsValid) return; @@ -863,90 +547,6 @@ namespace src.player Utilities.SetStateChanged(player, "CBasePlayerController", "m_iDesiredFOV"); } - public static void OnMapChange() - { - lock (setLock) - { - isTransmitRegistered = false; - Instance.RemoveListener(CheckTransmit); - - Fortnite.skillInThisRound = false; - - EntityManager.SuppressKills = true; - EntityManager.DestroyAllTracked(); - foreach (var skill in SkillData.Skills) - Instance.SkillAction(skill.Skill.ToString(), "NewRound"); - EntityManager.SuppressKills = false; - - ActiveSkillsThisRound.Clear(); - SkillsUsedThisMap.Clear(); - nextRoundPicks.Clear(); - - playersSkills.Clear(); - staticSkills.Clear(); - - ctSkill = noneSkill; - tSkill = noneSkill; - allSkill = noneSkill; - - PlayerManager.Clear(); - - ConVar.Find("sv_legacy_jump")?.SetValue("1"); - } - } - - private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info) - { - Illiterate.Disable(); - DispatchToActiveSkills("RoundEnd"); - - lock (setLock) - { - Instance.AddTimer(.5f, () => - { - if (!Config.LoadedConfig.SummaryAfterTheRound) return; - - var _players = Utilities.GetPlayers().Where(p => p.IsValid && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).OrderBy(p => p.Team).ToList(); - - foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid)) - { - string skillsText = ""; - foreach (var _player in _players) - { - var _playerSkill = PlayerManager.GetPlayerByIndex(_player.Index); - if (_playerSkill == null) continue; - - var skillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.Skill); - var specialSkillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.SpecialSkill); - if (skillInfo == null) continue; - - skillsText += $" {ChatColors.DarkRed}\u202A{_player.PlayerName}\u202C{ChatColors.Lime}: {(_playerSkill.SpecialSkill == Skills.None || specialSkillInfo == null ? player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance) : $"{player.GetSkillName(specialSkillInfo.Skill)} -> {player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance)}")}\n"; - } - - if (string.IsNullOrEmpty(skillsText)) continue; - - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("summary"), border: "t"); - foreach (string text in skillsText.Split("\n")) - if (!string.IsNullOrEmpty(text)) - SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); - SkillUtils.PrintToChat(player, string.Empty, border: "b"); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - // Before the optional disable below, so the "don't repeat the current skill" - // exclusion still sees this round's skills. - Instance.AddTimer(.6f, PrecomputeNextRoundSkills, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - if (Config.LoadedConfig.DisableSkillsOnRoundEnd) - { - isTransmitRegistered = false; - Instance.AddTimer(1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - Instance.RemoveListener(CheckTransmit); - } - return HookResult.Continue; - } - } - private static HookResult PlayerDeathPre(EventPlayerDeath @event, GameEventInfo info) { try @@ -1077,14 +677,6 @@ namespace src.player } } - private static void EntitySpawned(CEntityInstance entity) - { - lock (setLock) - { - DispatchToActiveSkills("OnEntitySpawned", entity); - } - } - private static HookResult BulletImpact(EventBulletImpact @event, GameEventInfo info) { lock (setLock) @@ -1094,483 +686,8 @@ namespace src.player } } - private static void SetSkill() - { - long perfStart = PerfLog.Start(); - SetSkillCore(); - PerfLog.End("SetSkill total", perfStart, 2.0); - } - private static readonly Dictionary nextRoundPicks = []; - private sealed class PickContext - { - public required List BaseList { get; init; } - public required Dictionary RequiredPermissions { get; init; } - public required HashSet NeedsTeammates { get; init; } - public required HashSet CtOnly { get; init; } - public required HashSet TOnly { get; init; } - public required int TerroristCount { get; init; } - public required int CounterTerroristCount { get; init; } - } - - private static PickContext BuildPickContext(List validPlayers) - { - Dictionary perms = []; - foreach (var s in SkillData.Skills) - { - if (s == null || s.Skill == Skills.None) continue; - string perm = SkillsInfo.GetValue(s.Skill, "requiredPermission"); - if (!string.IsNullOrEmpty(perm)) perms[s.Skill] = perm; - } - - return new PickContext - { - BaseList = [.. SkillData.Skills.Where(s => s != null && s.Skill != Skills.None)], - RequiredPermissions = perms, - NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)), - CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)), - TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)), - TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist), - CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist), - }; - } - - private static HashSet ToSkillSet(IEnumerable names) - { - HashSet set = []; - foreach (var name in names) - if (Enum.TryParse(name, out var skill)) set.Add(skill); - return set; - } - - private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary assignmentCounts, Config.GameModes gameMode) - { - List skillList = [.. ctx.BaseList]; - - if (!player.IsBot && ctx.RequiredPermissions.Count != 0) - skillList.RemoveAll(s => ctx.RequiredPermissions.TryGetValue(s.Skill, out var perm) && !AdminManager.PlayerHasPermissions(player, perm)); - - if (gameMode != Config.GameModes.FullRandom) - skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); - - int teamCount = player.Team == CsTeam.Terrorist ? ctx.TerroristCount : ctx.CounterTerroristCount; - if (teamCount == 1) - skillList.RemoveAll(s => ctx.NeedsTeammates.Contains(s.Skill)); - - if (player.Team == CsTeam.Terrorist) - skillList.RemoveAll(s => ctx.CtOnly.Contains(s.Skill)); - else - skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill)); - - if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) - { - skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); - if (skillList.Count == 0) skills.Clear(); - } - - var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); - - if (gameMode == Config.GameModes.NoRepeat) - { - if (playersSkills.TryGetValue(player.Index, out ConcurrentBag? value)) - value.Add(randomSkill); - else - playersSkills.TryAdd(player.Index, [randomSkill]); - } - - return randomSkill; - } - - private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List validPlayers, Dictionary assignmentCounts) - { - if (pick.Skill == Skills.None) return true; - if (!SkillData.Skills.Any(s => s.Skill == pick.Skill)) return false; - - string name = pick.Skill.ToString(); - if (player.Team == CsTeam.Terrorist && counterterroristSkills.Any(s => s.Name == name)) return false; - if (player.Team == CsTeam.CounterTerrorist && terroristSkills.Any(s => s.Name == name)) return false; - - var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name); - if (def == null) return false; - if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false; - if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false; - - return true; - } - - // Runs at round end so the expensive skill selection is off the round-start hot path; - // SetSkillCore then only applies the picks. - private static void PrecomputeNextRoundSkills() - { - long perfStart = PerfLog.Start(); - lock (setLock) - { - nextRoundPicks.Clear(); - - var gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (gameMode is not (Config.GameModes.Normal or Config.GameModes.FullRandom or Config.GameModes.NoRepeat)) return; - if (Instance?.GameRules == null || Instance.GameRules.WarmupPeriod == true) return; - - var validPlayers = Utilities.GetPlayers() - .Where(p => p != null && p.IsValid && !p.IsHLTV) - .Where(p => { try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } catch { return false; } }).ToList(); - - var ctx = BuildPickContext(validPlayers); - - Dictionary assignmentCounts = []; - foreach (var player in validPlayers) - { - var skillPlayer = PlayerManager.GetPlayerByIndex(player.Index); - if (skillPlayer == null) continue; - - var pick = PickSkillForPlayer(player, skillPlayer, ctx, assignmentCounts, gameMode); - nextRoundPicks[player.Index] = pick; - - if (pick.Skill != Skills.None) - assignmentCounts[pick.Skill] = assignmentCounts.TryGetValue(pick.Skill, out var c) ? c + 1 : 1; - } - } - PerfLog.End("PrecomputeSkills total", perfStart, 2.0); - } - - public static void UpdateSkillHudExpired(jSkill_PlayerInfo skillPlayer, Skills skill) - { - float globalHudExpired = Config.LoadedConfig.SkillHudDuration; - float? skillHudExpired = SkillsInfo.GetValue(skill, "hudDuration"); - - skillPlayer.SkillHudExpired = - !skillHudExpired.HasValue ? - (globalHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalHudExpired)) - : skillHudExpired.Value == -1 ? DateTime.MaxValue - : DateTime.Now.AddSeconds(skillHudExpired.Value); - - float globalDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration; - float? skillDescriptionHudExpired = SkillsInfo.GetValue(skill, "descriptionHudDuration"); - - skillPlayer.SkillDescriptionHudExpired = - !skillDescriptionHudExpired.HasValue ? - (globalDescriptionHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalDescriptionHudExpired)) - : skillDescriptionHudExpired.Value == -1 ? DateTime.MaxValue - : DateTime.Now.AddSeconds(skillDescriptionHudExpired.Value); - } - - private static void SetSkillCore() - { - setSkillTimer = null; - lock (setLock) - { - if (Instance == null) return; - - // GameRules null = not ready; keep polling so skills land right after warmup ends. - if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true) - { - setSkillTimer?.Kill(); - setSkillTimer = Instance.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - return; - } - - var validPlayers = Utilities.GetPlayers() - .Where(p => p != null && p.IsValid && !p.IsHLTV) - .Where(p => - { - try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } - catch { return false; } - }).ToList(); - - if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) - { - List tSkills = [.. SkillData.Skills]; - tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - tSkill = tSkills.Count == 0 ? noneSkill : tSkills[Instance.Random.Next(tSkills.Count)]; - - List ctSkills = [.. SkillData.Skills]; - ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[Instance.Random.Next(ctSkills.Count)]; - } - else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.SameSkills) - { - List allSkills = [.. SkillData.Skills]; - allSkills.RemoveAll(s => s.Skill == allSkill.Skill || s.Skill == Skills.None || !allTeamsSkills.Any(s2 => s2.Name == s.Skill.ToString())); - allSkill = allSkills.Count == 0 ? noneSkill : allSkills[Instance.Random.Next(allSkills.Count)]; - } - else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.Debug && debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - - Dictionary assignmentCounts = new(); - foreach (var sp in Instance.SkillPlayer) - { - if (sp == null) continue; - if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; - else assignmentCounts[sp.Skill] = 1; - } - - PickContext? pickContext = null; - - foreach (var player in validPlayers) - { - if (player == null) continue; - var teammates = validPlayers.Where(p => p != null && p.IsValid && p.Team == player.Team && p != player).ToList(); - string teammateSkills = ""; - - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) continue; - - skillPlayer.IsDrawing = false; - skillPlayer.HudOnDeathBlocked = null; - if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) - { - skillPlayer.Skill = Skills.None; - continue; - } - - jSkill_SkillInfo randomSkill = noneSkill; - - Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) - { - // Prefer the pick made at the end of the previous round; re-pick only when - // it no longer fits (team change, missing player, max reached). - if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts)) - randomSkill = pre; - else - { - pickContext ??= BuildPickContext(validPlayers); - randomSkill = PickSkillForPlayer(player, skillPlayer, pickContext, assignmentCounts, gameMode); - } - } - else if (gameMode == Config.GameModes.TeamSkills) - randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; - else if (gameMode == Config.GameModes.SameSkills) - randomSkill = allSkill; - else if (gameMode == Config.GameModes.Debug) - { - if (debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - randomSkill = debugSkills[0]; - debugSkills.RemoveAt(0); - player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); - } - - Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); - skillPlayer.Skill = randomSkill.Skill; - skillPlayer.SpecialSkill = Skills.None; - - if (randomSkill.Skill != Skills.None) - { - if (assignmentCounts.TryGetValue(randomSkill.Skill, out var cnt)) assignmentCounts[randomSkill.Skill] = cnt + 1; - else assignmentCounts[randomSkill.Skill] = 1; - } - - if (randomSkill.Skill == Skills.Illiterate) - Illiterate.Enable(); - - var playerIndex = player.Index; - Instance?.AddTimer(.2f, () => - { - var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); - if (playerTarget == null || !playerTarget.IsValid) return; - - if (randomSkill.Display) - SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}", - border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t"); - - if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) - Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => - { - var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); - if (playerTarget == null || !playerTarget.IsValid) return; - - if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; - Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill); - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - else - { - if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; - Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill); - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); - UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); - - if (Config.LoadedConfig.TeamMateSkillChatInfo) - { - Instance?.AddTimer(.6f, () => - { - if (player == null || !player.IsValid) return; - - foreach (var teammate in teammates) - { - var teammateInfo = PlayerManager.GetPlayerByIndex(teammate.Index); - if (teammateInfo != null && teammateInfo?.Skill != null) - { - var skillInfo = SkillData.Skills.FirstOrDefault(p => p.Skill == teammateInfo.Skill); - teammateSkills += $" {ChatColors.DarkRed}\u202A{teammate.PlayerName}\u202C{ChatColors.Lime}: {(skillInfo == null ? player.GetSkillName(Skills.None) : player.GetSkillName(skillInfo.Skill, teammateInfo.SkillChance))}\n"; - } - } - - if (!string.IsNullOrEmpty(teammateSkills)) - { - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "t"); - foreach (string text in teammateSkills.Split("\n")) - if (!string.IsNullOrEmpty(text)) - SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); - SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "b"); - } - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - } - } - - nextRoundPicks.Clear(); - } - } - - public static void SetRandomSkill(CCSPlayerController player) - { - lock (setLock) - { - var validPlayers = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList(); - - if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) - { - List tSkills = [.. SkillData.Skills]; - tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - tSkill = tSkills.Count == 0 ? noneSkill : tSkills[0]; - - List ctSkills = [.. SkillData.Skills]; - ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[0]; - } - - if (player == null) return; - var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); - if (skillPlayer == null) return; - - skillPlayer.IsDrawing = false; - if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) - { - skillPlayer.Skill = Skills.None; - return; - } - - jSkill_SkillInfo randomSkill = noneSkill; - if (Instance?.GameRules != null && Instance?.GameRules.WarmupPeriod == false) - { - Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; - if (staticSkills.TryGetValue(player.Index, out var staticSkill)) - randomSkill = staticSkill; - else if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) - { - List skillList = [.. SkillData.Skills]; - skillList.RemoveAll(s => s?.Skill == Skills.None); - if (!player.IsBot) - skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue(s.Skill, "requiredPermission"))); - - if (gameMode != Config.GameModes.FullRandom) - skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); - - if (validPlayers.Count(p => p.Team == player.Team) == 1) - { - SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)]; - skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString())); - } - - if (player.Team == CsTeam.Terrorist) - skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - else - skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); - - if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) - { - skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); - if (skillList.Count == 0) skills.Clear(); - } - - var assignmentCounts = new Dictionary(); - foreach (var sp in Instance.SkillPlayer) - { - if (sp == null) continue; - if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; - else assignmentCounts[sp.Skill] = 1; - } - - randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); - } - else if (gameMode == Config.GameModes.TeamSkills) - randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; - else if (gameMode == Config.GameModes.SameSkills) - randomSkill = allSkill; - else if (gameMode == Config.GameModes.Debug) - { - if (debugSkills.Count == 0) - debugSkills = [.. SkillData.Skills]; - randomSkill = debugSkills[0]; - debugSkills.RemoveAt(0); - player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); - } - } - - Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); - skillPlayer.Skill = randomSkill.Skill; - skillPlayer.SpecialSkill = Skills.None; - - if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo) - SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}", - border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t"); - - if (randomSkill.Skill == Skills.Illiterate) - Illiterate.Enable(); - - Instance?.AddTimer(.2f, () => - { - if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) - Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => - { - if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return; - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - else - Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); - }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); - - Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); - UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); - } - } - - public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList) - { - long perfStart = PerfLog.Start(); - lock (setLock) - { - try - { - // Keep dying entities out of snapshots until the engine processes the kill. - var dying = EntityManager.GetRecentlyDestroyedSnapshot(); - if (dying.Count > 0) - { - foreach (var (info, player) in infoList) - { - if (player == null || !player.IsValid) continue; - foreach (var entityIndex in dying) - if (info.TransmitEntities.Contains(entityIndex)) - info.TransmitEntities.Remove(entityIndex); - } - } - } - catch (Exception ex) - { - Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}"); - } - - DispatchToActiveSkills("CheckTransmit", infoList); - } - PerfLog.Sample("CheckTransmit", perfStart); - } - public static void UpdateSkillHUD(CCSPlayerController? player, string? headerLine, string? centerLine, string? extraLine, bool isDescription) { lock (setLock) @@ -1604,15 +721,5 @@ namespace src.player } } - public static void EnableTransmit() - { - if (!isTransmitRegistered) - { - Instance?.RegisterListener(CheckTransmit); - isTransmitRegistered = true; - } - } - - public static DateTime GetFreezeTimeEnd() => freezeTimeEnd; } -} \ No newline at end of file +} diff --git a/jRandomSkills - SRC Files/src/player/RoundEvents.cs b/jRandomSkills - SRC Files/src/player/RoundEvents.cs new file mode 100644 index 0000000..71d2d4b --- /dev/null +++ b/jRandomSkills - SRC Files/src/player/RoundEvents.cs @@ -0,0 +1,692 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Events; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.UserMessages; +using CounterStrikeSharp.API.Modules.Utils; +using RayTraceAPI; +using src.player.skills; +using src.utils; +using System.Collections.Concurrent; +using static CounterStrikeSharp.API.Core.Listeners; +using static src.jRandomSkills; +using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; + +namespace src.player +{ + public static partial class Event + { + private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary assignmentCounts, Config.GameModes gameMode) + { + if (candidates == null || candidates.Count == 0) return noneSkill; + + bool ignoreMax = gameMode == Config.GameModes.SameSkills || gameMode == Config.GameModes.TeamSkills; + + const int attempts = 6; + for (int attempt = 0; attempt < attempts; attempt++) + { + var (roll, rolled) = RarityManager.RollRarity(); + + var filtered = candidates.Where(s => + { + if (s == null) return false; + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); + if (def == null) return false; + + if (!string.Equals(def.Rarity ?? string.Empty, rolled.ToString(), StringComparison.OrdinalIgnoreCase)) + return false; + + if (!ignoreMax && def.MaxPerServer >= 0) + { + int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; + if (current >= def.MaxPerServer) return false; + } + + return true; + }).ToList(); + + if (filtered.Count > 0) + return filtered[Random.Shared.Next(filtered.Count)]; + } + + var fallback = candidates.Where(s => + { + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == s.Skill.ToString()); + if (def == null) return true; + if (ignoreMax) return true; + if (def.MaxPerServer < 0) return true; + int current = assignmentCounts.TryGetValue(s.Skill, out var c) ? c : 0; + return current < def.MaxPerServer; + }).ToList(); + + if (fallback.Count > 0) + return fallback[Random.Shared.Next(fallback.Count)]; + + return candidates[Random.Shared.Next(candidates.Count)]; + } + + private static HookResult RoundStart(EventRoundStart @event, GameEventInfo info) + { + lock (setLock) + { + bool isWarmup = Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true; + isTransmitRegistered = false; + SkillUtils.ClearKillCredits(); + SkillUtils.ClearCurses(); + Instance.AddTimer(.1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist)) + { + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) continue; + skillPlayer.IsDrawing = !isWarmup; + skillPlayer.PrintHTML = null; + } + + Instance.RemoveListener(CheckTransmit); + int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue() ?? 0; + freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); + + setSkillTimer?.Kill(); + + if (isWarmup) + { + setSkillTimer = Instance?.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return HookResult.Continue; + } + + float timeToDraw = (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0) + Math.Max(freezetime - Config.LoadedConfig.SkillTimeBeforeStart, 0) + .3f; + setSkillTimer = Instance?.AddTimer(timeToDraw, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return HookResult.Continue; + } + } + + private static void DisableAll() + { + long perfStart = PerfLog.Start(); + DisableAllCore(); + PerfLog.End("DisableAll total", perfStart, 2.0); + } + + private static void DisableAllCore() + { + lock (setLock) + { + // Re-register CheckTransmit so the dying-entity filter covers the kills below. + EnableTransmit(); + + Fortnite.skillInThisRound = false; + EntityManager.DestroyAllTracked(); + + foreach (var player in Utilities.GetPlayers().Where(p => p != null && p.IsValid)) + { + if (player == null || !player.IsValid) continue; + + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) continue; + + ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0); + SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0); + if (playerInfo.SpecialSkill != noneSkill.Skill) + { + ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0); + SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0); + } + + Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]); + + playerInfo.Skill = noneSkill.Skill; + playerInfo.SpecialSkill = noneSkill.Skill; + playerInfo.PrintHTML = null; + playerInfo.SkillChance = 1; + playerInfo.SkillUsed = false; + + RestorePlayer(player); + } + + // Reset every skill used so far on this map, not only the ones held this round: a skill + // nobody drew now would otherwise never clear state left over from an earlier round. + // Skills that never ran cannot hold state, so they stay out of the sweep. + foreach (var skillName in SkillsUsedThisMap.Keys) + Instance.SkillAction(skillName, "NewRound"); + ActiveSkillsThisRound.Clear(); + tickFailuresLogged.Clear(); + } + } + + public static void OnMapChange() + { + lock (setLock) + { + isTransmitRegistered = false; + Instance.RemoveListener(CheckTransmit); + + Fortnite.skillInThisRound = false; + + EntityManager.SuppressKills = true; + EntityManager.DestroyAllTracked(); + foreach (var skill in SkillData.Skills) + Instance.SkillAction(skill.Skill.ToString(), "NewRound"); + EntityManager.SuppressKills = false; + + ActiveSkillsThisRound.Clear(); + SkillsUsedThisMap.Clear(); + nextRoundPicks.Clear(); + + playersSkills.Clear(); + staticSkills.Clear(); + + ctSkill = noneSkill; + tSkill = noneSkill; + allSkill = noneSkill; + + PlayerManager.Clear(); + + ConVar.Find("sv_legacy_jump")?.SetValue("1"); + } + } + + private static HookResult RoundEnd(EventRoundEnd @event, GameEventInfo info) + { + Illiterate.Disable(); + DispatchToActiveSkills("RoundEnd"); + + lock (setLock) + { + Instance.AddTimer(.5f, () => + { + if (!Config.LoadedConfig.SummaryAfterTheRound) return; + + var _players = Utilities.GetPlayers().Where(p => p.IsValid && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).OrderBy(p => p.Team).ToList(); + + foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid)) + { + string skillsText = ""; + foreach (var _player in _players) + { + var _playerSkill = PlayerManager.GetPlayerByIndex(_player.Index); + if (_playerSkill == null) continue; + + var skillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.Skill); + var specialSkillInfo = SkillData.Skills.FirstOrDefault(s => s.Skill == _playerSkill.SpecialSkill); + if (skillInfo == null) continue; + + skillsText += $" {ChatColors.DarkRed}\u202A{_player.PlayerName}\u202C{ChatColors.Lime}: {(_playerSkill.SpecialSkill == Skills.None || specialSkillInfo == null ? player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance) : $"{player.GetSkillName(specialSkillInfo.Skill)} -> {player.GetSkillName(skillInfo.Skill, _playerSkill.SkillChance)}")}\n"; + } + + if (string.IsNullOrEmpty(skillsText)) continue; + + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("summary"), border: "t"); + foreach (string text in skillsText.Split("\n")) + if (!string.IsNullOrEmpty(text)) + SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); + SkillUtils.PrintToChat(player, string.Empty, border: "b"); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + // Before the optional disable below, so the "don't repeat the current skill" + // exclusion still sees this round's skills. + Instance.AddTimer(.6f, PrecomputeNextRoundSkills, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + if (Config.LoadedConfig.DisableSkillsOnRoundEnd) + { + isTransmitRegistered = false; + Instance.AddTimer(1f, () => DisableAll(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + Instance.RemoveListener(CheckTransmit); + } + return HookResult.Continue; + } + } + + private static void SetSkill() + { + long perfStart = PerfLog.Start(); + SetSkillCore(); + PerfLog.End("SetSkill total", perfStart, 2.0); + } + + private sealed class PickContext + { + public required List BaseList { get; init; } + public required Dictionary RequiredPermissions { get; init; } + public required HashSet NeedsTeammates { get; init; } + public required HashSet CtOnly { get; init; } + public required HashSet TOnly { get; init; } + public required int TerroristCount { get; init; } + public required int CounterTerroristCount { get; init; } + } + + private static PickContext BuildPickContext(List validPlayers) + { + Dictionary perms = []; + foreach (var s in SkillData.Skills) + { + if (s == null || s.Skill == Skills.None) continue; + string perm = SkillsInfo.GetValue(s.Skill, "requiredPermission"); + if (!string.IsNullOrEmpty(perm)) perms[s.Skill] = perm; + } + + return new PickContext + { + BaseList = [.. SkillData.Skills.Where(s => s != null && s.Skill != Skills.None)], + RequiredPermissions = perms, + NeedsTeammates = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates).Select(s => s.Name)), + CtOnly = ToSkillSet(counterterroristSkills.Select(s => s.Name)), + TOnly = ToSkillSet(terroristSkills.Select(s => s.Name)), + TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist), + CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist), + }; + } + + private static HashSet ToSkillSet(IEnumerable names) + { + HashSet set = []; + foreach (var name in names) + if (Enum.TryParse(name, out var skill)) set.Add(skill); + return set; + } + + private static jSkill_SkillInfo PickSkillForPlayer(CCSPlayerController player, jSkill_PlayerInfo skillPlayer, PickContext ctx, Dictionary assignmentCounts, Config.GameModes gameMode) + { + List skillList = [.. ctx.BaseList]; + + if (!player.IsBot && ctx.RequiredPermissions.Count != 0) + skillList.RemoveAll(s => ctx.RequiredPermissions.TryGetValue(s.Skill, out var perm) && !AdminManager.PlayerHasPermissions(player, perm)); + + if (gameMode != Config.GameModes.FullRandom) + skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); + + int teamCount = player.Team == CsTeam.Terrorist ? ctx.TerroristCount : ctx.CounterTerroristCount; + if (teamCount == 1) + skillList.RemoveAll(s => ctx.NeedsTeammates.Contains(s.Skill)); + + if (player.Team == CsTeam.Terrorist) + skillList.RemoveAll(s => ctx.CtOnly.Contains(s.Skill)); + else + skillList.RemoveAll(s => ctx.TOnly.Contains(s.Skill)); + + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) + { + skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); + if (skillList.Count == 0) skills.Clear(); + } + + var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); + + if (gameMode == Config.GameModes.NoRepeat) + { + if (playersSkills.TryGetValue(player.Index, out ConcurrentBag? value)) + value.Add(randomSkill); + else + playersSkills.TryAdd(player.Index, [randomSkill]); + } + + return randomSkill; + } + + private static bool IsPickStillValid(jSkill_SkillInfo pick, CCSPlayerController player, List validPlayers, Dictionary assignmentCounts) + { + if (pick.Skill == Skills.None) return true; + if (!SkillData.Skills.Any(s => s.Skill == pick.Skill)) return false; + + string name = pick.Skill.ToString(); + if (player.Team == CsTeam.Terrorist && counterterroristSkills.Any(s => s.Name == name)) return false; + if (player.Team == CsTeam.CounterTerrorist && terroristSkills.Any(s => s.Name == name)) return false; + + var def = SkillsInfo.LoadedConfig.FirstOrDefault(d => d.Name == name); + if (def == null) return false; + if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false; + if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false; + + return true; + } + + // Runs at round end so the expensive skill selection is off the round-start hot path; + // SetSkillCore then only applies the picks. + private static void PrecomputeNextRoundSkills() + { + long perfStart = PerfLog.Start(); + lock (setLock) + { + nextRoundPicks.Clear(); + + var gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (gameMode is not (Config.GameModes.Normal or Config.GameModes.FullRandom or Config.GameModes.NoRepeat)) return; + if (Instance?.GameRules == null || Instance.GameRules.WarmupPeriod == true) return; + + var validPlayers = Utilities.GetPlayers() + .Where(p => p != null && p.IsValid && !p.IsHLTV) + .Where(p => { try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } catch { return false; } }).ToList(); + + var ctx = BuildPickContext(validPlayers); + + Dictionary assignmentCounts = []; + foreach (var player in validPlayers) + { + var skillPlayer = PlayerManager.GetPlayerByIndex(player.Index); + if (skillPlayer == null) continue; + + var pick = PickSkillForPlayer(player, skillPlayer, ctx, assignmentCounts, gameMode); + nextRoundPicks[player.Index] = pick; + + if (pick.Skill != Skills.None) + assignmentCounts[pick.Skill] = assignmentCounts.TryGetValue(pick.Skill, out var c) ? c + 1 : 1; + } + } + PerfLog.End("PrecomputeSkills total", perfStart, 2.0); + } + + public static void UpdateSkillHudExpired(jSkill_PlayerInfo skillPlayer, Skills skill) + { + float globalHudExpired = Config.LoadedConfig.SkillHudDuration; + float? skillHudExpired = SkillsInfo.GetValue(skill, "hudDuration"); + + skillPlayer.SkillHudExpired = + !skillHudExpired.HasValue ? + (globalHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalHudExpired)) + : skillHudExpired.Value == -1 ? DateTime.MaxValue + : DateTime.Now.AddSeconds(skillHudExpired.Value); + + float globalDescriptionHudExpired = Config.LoadedConfig.SkillDescriptionDuration; + float? skillDescriptionHudExpired = SkillsInfo.GetValue(skill, "descriptionHudDuration"); + + skillPlayer.SkillDescriptionHudExpired = + !skillDescriptionHudExpired.HasValue ? + (globalDescriptionHudExpired == -1 ? DateTime.MaxValue : DateTime.Now.AddSeconds(globalDescriptionHudExpired)) + : skillDescriptionHudExpired.Value == -1 ? DateTime.MaxValue + : DateTime.Now.AddSeconds(skillDescriptionHudExpired.Value); + } + + private static void SetSkillCore() + { + setSkillTimer = null; + lock (setLock) + { + if (Instance == null) return; + + // GameRules null = not ready; keep polling so skills land right after warmup ends. + if (Instance.GameRules == null || Instance.GameRules.WarmupPeriod == true) + { + setSkillTimer?.Kill(); + setSkillTimer = Instance.AddTimer(1f, SetSkill, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + return; + } + + var validPlayers = Utilities.GetPlayers() + .Where(p => p != null && p.IsValid && !p.IsHLTV) + .Where(p => + { + try { return p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist; } + catch { return false; } + }).ToList(); + + if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) + { + List tSkills = [.. SkillData.Skills]; + tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + tSkill = tSkills.Count == 0 ? noneSkill : tSkills[Instance.Random.Next(tSkills.Count)]; + + List ctSkills = [.. SkillData.Skills]; + ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[Instance.Random.Next(ctSkills.Count)]; + } + else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.SameSkills) + { + List allSkills = [.. SkillData.Skills]; + allSkills.RemoveAll(s => s.Skill == allSkill.Skill || s.Skill == Skills.None || !allTeamsSkills.Any(s2 => s2.Name == s.Skill.ToString())); + allSkill = allSkills.Count == 0 ? noneSkill : allSkills[Instance.Random.Next(allSkills.Count)]; + } + else if (Config.LoadedConfig.GameMode == (int)Config.GameModes.Debug && debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + + Dictionary assignmentCounts = new(); + foreach (var sp in Instance.SkillPlayer) + { + if (sp == null) continue; + if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; + else assignmentCounts[sp.Skill] = 1; + } + + PickContext? pickContext = null; + + foreach (var player in validPlayers) + { + if (player == null) continue; + var teammates = validPlayers.Where(p => p != null && p.IsValid && p.Team == player.Team && p != player).ToList(); + string teammateSkills = ""; + + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) continue; + + skillPlayer.IsDrawing = false; + skillPlayer.HudOnDeathBlocked = null; + if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) + { + skillPlayer.Skill = Skills.None; + continue; + } + + jSkill_SkillInfo randomSkill = noneSkill; + + Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) + { + // Prefer the pick made at the end of the previous round; re-pick only when + // it no longer fits (team change, missing player, max reached). + if (nextRoundPicks.TryGetValue(player.Index, out var pre) && IsPickStillValid(pre, player, validPlayers, assignmentCounts)) + randomSkill = pre; + else + { + pickContext ??= BuildPickContext(validPlayers); + randomSkill = PickSkillForPlayer(player, skillPlayer, pickContext, assignmentCounts, gameMode); + } + } + else if (gameMode == Config.GameModes.TeamSkills) + randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; + else if (gameMode == Config.GameModes.SameSkills) + randomSkill = allSkill; + else if (gameMode == Config.GameModes.Debug) + { + if (debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + randomSkill = debugSkills[0]; + debugSkills.RemoveAt(0); + player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); + } + + Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); + skillPlayer.Skill = randomSkill.Skill; + skillPlayer.SpecialSkill = Skills.None; + + if (randomSkill.Skill != Skills.None) + { + if (assignmentCounts.TryGetValue(randomSkill.Skill, out var cnt)) assignmentCounts[randomSkill.Skill] = cnt + 1; + else assignmentCounts[randomSkill.Skill] = 1; + } + + if (randomSkill.Skill == Skills.Illiterate) + Illiterate.Enable(); + + var playerIndex = player.Index; + Instance?.AddTimer(.2f, () => + { + var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); + if (playerTarget == null || !playerTarget.IsValid) return; + + if (randomSkill.Display) + SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}", + border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t"); + + if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) + Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => + { + var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex); + if (playerTarget == null || !playerTarget.IsValid) return; + + if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; + Debug.WriteToDebug("Enabling skill after freeze time: " + randomSkill.Skill); + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + else + { + if (PlayerManager.GetPlayerByIndex(playerTarget!.Index)?.Skill != randomSkill.Skill) return; + Debug.WriteToDebug("Enabling skill: " + randomSkill.Skill); + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [playerTarget]); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); + UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); + + if (Config.LoadedConfig.TeamMateSkillChatInfo) + { + Instance?.AddTimer(.6f, () => + { + if (player == null || !player.IsValid) return; + + foreach (var teammate in teammates) + { + var teammateInfo = PlayerManager.GetPlayerByIndex(teammate.Index); + if (teammateInfo != null && teammateInfo?.Skill != null) + { + var skillInfo = SkillData.Skills.FirstOrDefault(p => p.Skill == teammateInfo.Skill); + teammateSkills += $" {ChatColors.DarkRed}\u202A{teammate.PlayerName}\u202C{ChatColors.Lime}: {(skillInfo == null ? player.GetSkillName(Skills.None) : player.GetSkillName(skillInfo.Skill, teammateInfo.SkillChance))}\n"; + } + } + + if (!string.IsNullOrEmpty(teammateSkills)) + { + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "t"); + foreach (string text in teammateSkills.Split("\n")) + if (!string.IsNullOrEmpty(text)) + SkillUtils.PrintToChat(player, text, title: player.GetTranslation("teammate_skills"), border: ""); + SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslation("teammate_skills"), border: "b"); + } + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + } + } + + nextRoundPicks.Clear(); + } + } + + public static void SetRandomSkill(CCSPlayerController player) + { + lock (setLock) + { + var validPlayers = Utilities.GetPlayers().Where(p => p != null && p.IsValid && !p.IsHLTV && p.Team is CsTeam.CounterTerrorist or CsTeam.Terrorist).ToList(); + + if (Config.LoadedConfig.GameMode == (int)Config.GameModes.TeamSkills) + { + List tSkills = [.. SkillData.Skills]; + tSkills.RemoveAll(s => s.Skill == tSkill.Skill || s.Skill == Skills.None || counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + tSkill = tSkills.Count == 0 ? noneSkill : tSkills[0]; + + List ctSkills = [.. SkillData.Skills]; + ctSkills.RemoveAll(s => s.Skill == ctSkill.Skill || s.Skill == Skills.None || terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + ctSkill = ctSkills.Count == 0 ? noneSkill : ctSkills[0]; + } + + if (player == null) return; + var skillPlayer = PlayerManager.GetPlayerByIndex(player!.Index); + if (skillPlayer == null) return; + + skillPlayer.IsDrawing = false; + if (player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) + { + skillPlayer.Skill = Skills.None; + return; + } + + jSkill_SkillInfo randomSkill = noneSkill; + if (Instance?.GameRules != null && Instance?.GameRules.WarmupPeriod == false) + { + Config.GameModes gameMode = (Config.GameModes)Config.LoadedConfig.GameMode; + if (staticSkills.TryGetValue(player.Index, out var staticSkill)) + randomSkill = staticSkill; + else if (gameMode == Config.GameModes.Normal || gameMode == Config.GameModes.FullRandom || gameMode == Config.GameModes.NoRepeat) + { + List skillList = [.. SkillData.Skills]; + skillList.RemoveAll(s => s?.Skill == Skills.None); + if (!player.IsBot) + skillList.RemoveAll(s => !string.IsNullOrEmpty(SkillsInfo.GetValue(s.Skill, "requiredPermission")) && !AdminManager.PlayerHasPermissions(player, SkillsInfo.GetValue(s.Skill, "requiredPermission"))); + + if (gameMode != Config.GameModes.FullRandom) + skillList.RemoveAll(s => s?.Skill == skillPlayer?.Skill || s?.Skill == skillPlayer?.SpecialSkill); + + if (validPlayers.Count(p => p.Team == player.Team) == 1) + { + SkillsInfo.DefaultSkillInfo[] skillsNeedsTeammates = [.. SkillsInfo.LoadedConfig.Where(s => s.NeedsTeammates)]; + skillList.RemoveAll(s => skillsNeedsTeammates.Any(s2 => s2.Name == s.Skill.ToString())); + } + + if (player.Team == CsTeam.Terrorist) + skillList.RemoveAll(s => counterterroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + else + skillList.RemoveAll(s => terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out ConcurrentBag? skills)) + { + skillList.RemoveAll(s => skills.Any(s2 => s2.Skill == s.Skill)); + if (skillList.Count == 0) skills.Clear(); + } + + var assignmentCounts = new Dictionary(); + foreach (var sp in Instance.SkillPlayer) + { + if (sp == null) continue; + if (assignmentCounts.TryGetValue(sp.Skill, out var cnt)) assignmentCounts[sp.Skill] = cnt + 1; + else assignmentCounts[sp.Skill] = 1; + } + + randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); + } + else if (gameMode == Config.GameModes.TeamSkills) + randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; + else if (gameMode == Config.GameModes.SameSkills) + randomSkill = allSkill; + else if (gameMode == Config.GameModes.Debug) + { + if (debugSkills.Count == 0) + debugSkills = [.. SkillData.Skills]; + randomSkill = debugSkills[0]; + debugSkills.RemoveAt(0); + player.PrintToChat($"{SkillData.Skills.Count - debugSkills.Count}/{SkillData.Skills.Count}"); + } + } + + Instance?.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]); + skillPlayer.Skill = randomSkill.Skill; + skillPlayer.SpecialSkill = Skills.None; + + if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo) + SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}", + border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t"); + + if (randomSkill.Skill == Skills.Illiterate) + Illiterate.Enable(); + + Instance?.AddTimer(.2f, () => + { + if (SkillsInfo.GetValue(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime()) + Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () => + { + if (PlayerManager.GetPlayerByIndex(player!.Index)?.Skill != randomSkill.Skill) return; + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + else + Instance?.SkillAction(randomSkill.Skill.ToString(), "EnableSkill", [player]); + }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); + + Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{player.GetSkillName(randomSkill.Skill)}\"."); + UpdateSkillHudExpired(skillPlayer, randomSkill.Skill); + } + } + + public static DateTime GetFreezeTimeEnd() => freezeTimeEnd; + } +} diff --git a/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs b/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs index aeffe90..8f8ac6c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Bankrupt.cs @@ -36,7 +36,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 e in enemies) diff --git a/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs b/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs index a89fe09..867d3a3 100644 --- a/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs +++ b/jRandomSkills - SRC Files/src/player/skills/CarefulBullets.cs @@ -51,7 +51,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -110,7 +110,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; diff --git a/jRandomSkills - SRC Files/src/player/skills/Darkness.cs b/jRandomSkills - SRC Files/src/player/skills/Darkness.cs index 84aa377..66216c0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Darkness.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Darkness.cs @@ -59,7 +59,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -130,7 +130,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; diff --git a/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs b/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs index 66e04a3..869f9f6 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deactivator.cs @@ -32,7 +32,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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) @@ -85,7 +85,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = []; diff --git a/jRandomSkills - SRC Files/src/player/skills/Deaf.cs b/jRandomSkills - SRC Files/src/player/skills/Deaf.cs index d0a9d4c..9576610 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Deaf.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Deaf.cs @@ -64,7 +64,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -122,7 +122,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/Earthquake.cs b/jRandomSkills - SRC Files/src/player/skills/Earthquake.cs deleted file mode 100644 index 89a8b5c..0000000 --- a/jRandomSkills - SRC Files/src/player/skills/Earthquake.cs +++ /dev/null @@ -1,141 +0,0 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; -using static src.jRandomSkills; -using System.Collections.Concurrent; -using src.utils; - -namespace src.player.skills -{ - public class Earthquake : ISkill - { - //private const Skills skillName = Skills.Earthquake; - //private static readonly ConcurrentDictionary SkillPlayerInfo = []; - //private static readonly object setLock = new(); - - //public static void LoadSkill() - //{ - // SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - //} - - //public static void NewRound() - //{ - // lock (setLock) - // SkillPlayerInfo.Clear(); - //} - - //public static void EnableSkill(CCSPlayerController player) - //{ - // SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo - // { - // SteamID = player.Index, - // CanUse = true, - // Cooldown = DateTime.MinValue, - // }); - //} - - //public static void DisableSkill(CCSPlayerController player) - //{ - // SkillPlayerInfo.TryRemove(player.Index, out _); - // SkillUtils.ResetPrintHTML(player); - //} - - //public static void PlayerDeath(EventPlayerDeath @event) - //{ - // var player = PlayerManager.GetPlayerEvent(@event.Userid); - // if (player == null || !player.IsValid) return; - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo?.Skill == skillName) - // SkillPlayerInfo.TryRemove(player.Index, out _); - //} - - //public static void OnTick() - //{ - // foreach (var player in PlayerManager.GetTickPlayers()) - // { - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo?.Skill == skillName) - // if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) - // UpdateHUD(player, skillInfo); - // } - //} - - //private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo) - //{ - // float cooldown = 0; - // if (skillInfo != null) - // { - // float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue(skillName, "cooldown")) - DateTime.Now).TotalSeconds); - // cooldown = Math.Max(time, 0); - - // if (cooldown == 0 && skillInfo?.CanUse == false) - // skillInfo.CanUse = true; - // } - - // if (cooldown == 0) - // return; - - // var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - // if (playerInfo == null) return; - - // string remainingLine = $"{player.GetTranslation("hud_info", $"{cooldown}")}"; - //} - - //public static void UseSkill(CCSPlayerController player) - //{ - // var playerPawn = player.PlayerPawn.Value; - // if (playerPawn?.CBodyComponent == null) return; - - // if (SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) - // { - // if (!player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; - // if (skillInfo.CanUse) - // { - // skillInfo.CanUse = false; - // skillInfo.Cooldown = DateTime.Now; - // MakeShake(player); - // } - // } - //} - - //private static void MakeShake(CCSPlayerController player) - //{ - // foreach (var enemy in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.PawnIsAlive)) - // CreateShake(player); - //} - - //private static void CreateShake(CCSPlayerController player) - //{ - // var pawn = player.PlayerPawn.Value; - // if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return; - - // var shake = EntityManager.CreateTrackedEnvShake(player.Index); - // if (shake == null || !shake.IsValid) return; - - // shake.Amplitude = SkillsInfo.GetValue(skillName, "amplitude"); - // shake.Frequency = SkillsInfo.GetValue(skillName, "frequency"); - // shake.Duration = SkillsInfo.GetValue(skillName, "duration"); - // shake.Radius = SkillsInfo.GetValue(skillName, "radius"); - - // shake.Teleport(new Vector(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z)); - // shake.AcceptInput("SetParent", pawn, pawn, "!activator"); - // shake.AcceptInput("StartShake"); - //} - - //public class PlayerSkillInfo - //{ - // public ulong SteamID { get; set; } - // public bool CanUse { get; set; } - // public DateTime Cooldown { get; set; } - //} - - //public class SkillConfig(Skills skill = skillName, bool active = false, string color = "#42f59b", 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 cooldown = 16f, float amplitude = 15f, float frequency = 500f, float duration = 8f, float radius = 50f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) - //{ - // public float Cooldown { get; set; } = cooldown; - // public float Amplitude { get; set; } = amplitude; - // public float Frequency { get; set; } = frequency; - // public float Duration { get; set; } = duration; - // public float Radius { get; set; } = radius; - //} - } -} \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs b/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs index 5571b14..232aecb 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ExpensiveAmmo.cs @@ -41,6 +41,7 @@ namespace src.player.skills var player = PlayerManager.GetPlayerEvent(@event.Userid); if (player == null || !player.IsValid) return; if (!cursedPlayers.ContainsKey(player.Index)) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; var moneyServices = player.InGameMoneyServices; if (moneyServices == null) return; @@ -61,7 +62,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) continue; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -119,7 +120,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; @@ -154,15 +155,6 @@ namespace src.player.skills SkillUtils.CloseMenu(player); } - private static CCSPlayerController[] GetSelectableEnemies(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) - .Cast()]; - } - public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#e0c341", 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, int moneyPerShot = 50) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public int MoneyPerShot { get; set; } = moneyPerShot; diff --git a/jRandomSkills - SRC Files/src/player/skills/Giant.cs b/jRandomSkills - SRC Files/src/player/skills/Giant.cs index f58ee6d..ac6eb3c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Giant.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Giant.cs @@ -175,14 +175,8 @@ namespace src.player.skills private static CCSPlayerController[] GetSelectableEnemies(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.IsHLTV - && p.Team != CsTeam.Spectator && p.Team != CsTeam.None - && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 - && PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken) - .Cast()]; + return [.. SkillUtils.GetSelectableEnemies(player, true) + .Where(p => PlayerManager.GetPlayerByIndex(p.Index)?.Skill != Skills.Chicken)]; } public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8ad3ff", 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 minScale = 1.1f, float maxScale = 1.4f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) diff --git a/jRandomSkills - SRC Files/src/player/skills/Glitch.cs b/jRandomSkills - SRC Files/src/player/skills/Glitch.cs index becc4e4..c8fe078 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Glitch.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Glitch.cs @@ -56,7 +56,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -129,7 +129,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/HealingChicken.cs b/jRandomSkills - SRC Files/src/player/skills/HealingChicken.cs deleted file mode 100644 index 6eb9e53..0000000 --- a/jRandomSkills - SRC Files/src/player/skills/HealingChicken.cs +++ /dev/null @@ -1,82 +0,0 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Utils; -using src.utils; -using System.Collections.Concurrent; -using System.Drawing; -using static src.jRandomSkills; -using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; - -namespace src.player.skills -{ - public class HealingChicken : ISkill - { - //private const Skills skillName = Skills.HealingChicken; - //private readonly static ConcurrentDictionary> playerSmokes = []; - //private static readonly object setLock = new(); - - //public static void LoadSkill() - //{ - // SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - //} - - //public static void NewRound() - //{ - - //} - - //public static void EnableSkill(CCSPlayerController player) - //{ - // SpawnChicken(player); - //} - - //public static void DisableSkill(CCSPlayerController player) - //{ - - //} - - //private static void SpawnChicken(CCSPlayerController player) - //{ - // if (player == null || !player.IsValid) return; - - // var pawn = player.PlayerPawn.Value; - // if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return; - - // int amount = SkillsInfo.GetValue(skillName, "amount"); - // for (int i = 0; i < 1; i++) - // { - // CChicken? chicken = EntityManager.CreateTrackedChicken(player.Index); - // if (chicken == null || !chicken.IsValid) continue; - - // chicken.Render = Color.Green; - // Vector offset = new ( - // (float)(100 * Math.Cos(2 * Math.PI * i / amount)), - // (float)(100 * Math.Sin(2 * Math.PI * i / amount)), - // 0 - // ); - - // chicken.Teleport(pawn.AbsOrigin + offset); - - // // Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_leader", player.PlayerPawn.Raw); - // Vector? spawn = SkillUtils.GetSpawnPointVector(player); - // Schema.SetSchemaValue(chicken.Handle, "CChicken", "m_vecPathGoal", spawn); - - // Instance.AddTickTimer(1, () => - // { - // if (chicken == null || !chicken.IsValid) return; - - // Server.PrintToChatAll($"{chicken.PathGoal}, {chicken.UpdateTimer.Duration}, {chicken.UpdateTimer.Timescale}"); - // }, TimerFlags.REPEAT | TimerFlags.STOP_ON_MAPCHANGE); - // } - //} - - //public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b5ab8f", 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, int amount = 3, int heal = 2, int tickCooldown = 16) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) - //{ - // public int Amount { get; set; } = amount; - // public int Heal { get; set; } = heal; - // public int TickCooldown { get; set; } = tickCooldown; - //} - } -} \ No newline at end of file diff --git a/jRandomSkills - SRC Files/src/player/skills/Jammer.cs b/jRandomSkills - SRC Files/src/player/skills/Jammer.cs index 458f878..3e2e511 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Jammer.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Jammer.cs @@ -64,7 +64,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -151,7 +151,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs b/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs index c485922..bffa306 100644 --- a/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/JumpBan.cs @@ -69,7 +69,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -127,7 +127,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs b/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs index e3de2ca..74e8310 100644 --- a/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs +++ b/jRandomSkills - SRC Files/src/player/skills/JumpCurse.cs @@ -76,7 +76,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) continue; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -134,7 +134,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; @@ -169,15 +169,6 @@ namespace src.player.skills SkillUtils.CloseMenu(player); } - private static CCSPlayerController[] GetSelectableEnemies(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) - .Cast()]; - } - public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7ad1c4", 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 jumpVelocity = 301f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public float JumpVelocity { get; set; } = jumpVelocity; diff --git a/jRandomSkills - SRC Files/src/player/skills/Knockback.cs b/jRandomSkills - SRC Files/src/player/skills/Knockback.cs index 2e53c29..4fb7081 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Knockback.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Knockback.cs @@ -20,6 +20,7 @@ namespace src.player.skills var playerInfo = PlayerManager.GetPlayerByIndex(player.Index); if (playerInfo?.Skill != skillName) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; var pawn = player.PlayerPawn?.Value; if (pawn == null || !pawn.IsValid || pawn.Health <= 0) return; diff --git a/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs b/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs index d7a104e..29cc08f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/LifeSwap.cs @@ -31,7 +31,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -86,7 +86,7 @@ namespace src.player.skills if (playerInfo == null) return; playerInfo.SkillUsed = false; - 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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e.PawnHealth} HP", e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs index 0f772cc..78eafd5 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs @@ -128,7 +128,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs b/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs index 8583415..a26deed 100644 --- a/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/MoneySwap.cs @@ -31,7 +31,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); @@ -85,7 +85,7 @@ namespace src.player.skills if (playerInfo == null) return; playerInfo.SkillUsed = false; - 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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {(e.InGameMoneyServices == null ? 0 : e.InGameMoneyServices.Account + e.InGameMoneyServices.CashSpentThisRound)}$", e.Index.ToString()))]; diff --git a/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs b/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs index 11c0b2d..c650155 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Nightmare.cs @@ -57,7 +57,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) continue; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; SkillUtils.UpdateMenu(player, menuItems); } @@ -72,7 +72,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - var enemies = GetSelectableEnemies(player); + var enemies = SkillUtils.GetSelectableEnemies(player, true); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = [.. enemies.Select(e => (e.PlayerName, e.Index.ToString()))]; @@ -225,17 +225,6 @@ namespace src.player.skills EntityManager.DestroyEntity(volumeIndex); } - private static CCSPlayerController[] GetSelectableEnemies(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) - .Select(p => p!)]; - } - public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5b2c6f", 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, string postProcessing = "lighting/postprocessing/effects/death_cam_phase1_low_violence.vpost", float fadeTime = .25f, float minExposure = .5f, float maxExposure = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) { public string PostProcessing { get; set; } = postProcessing; diff --git a/jRandomSkills - SRC Files/src/player/skills/Poison.cs b/jRandomSkills - SRC Files/src/player/skills/Poison.cs index 40ff6b6..c7b8e8c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Poison.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Poison.cs @@ -80,7 +80,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -139,7 +139,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs b/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs index 6f6a74f..d123d4c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs +++ b/jRandomSkills - SRC Files/src/player/skills/PrimaryBan.cs @@ -75,7 +75,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) continue; if (!SkillUtils.HasMenu(player)) 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 = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); SkillUtils.UpdateMenu(player, menuItems); @@ -134,7 +134,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs index 98ebac3..7878bda 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Tripwire.cs @@ -14,10 +14,13 @@ namespace src.player.skills private const Skills skillName = Skills.Tripwire; private static readonly ConcurrentDictionary wires = []; - private static readonly ConcurrentDictionary wireCount = []; + private static readonly ConcurrentDictionary SkillPlayerInfo = []; private static readonly ConcurrentDictionary<(uint Owner, uint Target), (int Slot, int ExpiryTick)> revealed = []; private static readonly object setLock = new(); + private static readonly Color terroristWire = Color.FromArgb(255, 255, 64, 64); + private static readonly Color counterTerroristWire = Color.FromArgb(255, 64, 128, 255); + public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); @@ -31,11 +34,22 @@ namespace src.player.skills DestroyWire(wire.BeamIndex); wires.Clear(); - wireCount.Clear(); + SkillPlayerInfo.Clear(); revealed.Clear(); } } + public static void EnableSkill(CCSPlayerController player) + { + if (player == null || !player.IsValid) return; + + SkillPlayerInfo[player.Index] = new PlayerSkillInfo + { + CanUse = true, + Cooldown = DateTime.MinValue, + }; + } + public static void PlayerDisconnect(uint playerIndex) { RemovePlayerWires(playerIndex); @@ -43,15 +57,32 @@ namespace src.player.skills foreach (var key in revealed.Keys) if (key.Owner == playerIndex || key.Target == playerIndex) revealed.TryRemove(key, out _); + + SkillPlayerInfo.TryRemove(playerIndex, out _); } public static void DisableSkill(CCSPlayerController player) { if (player == null) return; - RemovePlayerWires(player.Index); + ClearOwner(player.Index); + SkillUtils.ResetPrintHTML(player); + } + + public static void PlayerDeath(EventPlayerDeath @event) + { + var player = @event.Userid; + if (player == null || !player.IsValid) return; + + ClearOwner(player.Index); + SkillUtils.ResetPrintHTML(player); + } + + private static void ClearOwner(uint ownerIndex) + { + RemovePlayerWires(ownerIndex); foreach (var key in revealed.Keys) - if (key.Owner == player.Index) + if (key.Owner == ownerIndex) revealed.TryRemove(key, out _); } @@ -65,7 +96,7 @@ namespace src.player.skills wires.TryRemove(kvp.Key, out _); } - wireCount.TryRemove(ownerIndex, out _); + SkillPlayerInfo.TryRemove(ownerIndex, out _); } private static void DestroyWire(uint beamIndex) @@ -92,12 +123,7 @@ namespace src.player.skills var playerEvent = PlayerManager.GetPlayerFromEvent(player); if (playerEvent == null || !playerEvent.IsValid) return; - int maxWires = SkillsInfo.GetValue(skillName, "maxWires"); - if (wireCount.TryGetValue(player.Index, out int placed) && placed >= maxWires) - { - playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("tripwire_limit_info", maxWires)); - return; - } + if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo) || !skillInfo.CanUse) return; if (!TryPlaceWire(player)) { @@ -105,10 +131,29 @@ namespace src.player.skills return; } - wireCount.AddOrUpdate(player.Index, 1, (_, v) => v + 1); + skillInfo.CanUse = false; + skillInfo.Cooldown = DateTime.Now; + playerEvent.PrintToChat($" {ChatColors.Green}" + playerEvent.GetTranslation("tripwire_placed_info")); } + private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo) + { + float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue(skillName, "cooldown")) - DateTime.Now).TotalSeconds); + float cooldown = Math.Max(time, 0); + + if (cooldown == 0 && !skillInfo.CanUse) + skillInfo.CanUse = true; + + var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); + if (playerInfo == null) return; + + if (cooldown == 0) + playerInfo.PrintHTML = null; + else + playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"{cooldown}")}"; + } + private static bool TryPlaceWire(CCSPlayerController player) { var pawn = player.PlayerPawn?.Value; @@ -138,7 +183,9 @@ namespace src.player.skills if (EntityManager.OverBudget()) return false; - var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, Color.FromArgb(255, 255, 40, 40)); + Color wireColor = player.Team == CsTeam.Terrorist ? terroristWire : counterTerroristWire; + + var beam = EntityManager.CreateTrackedBeam(player.Index, start, end, wireColor); if (beam == null || !beam.IsValid) return false; beam.Width = SkillsInfo.GetValue(skillName, "wireWidth"); @@ -156,6 +203,18 @@ namespace src.player.skills public static void OnTick() { + if (!SkillPlayerInfo.IsEmpty && Server.TickCount % 8 == 0) + { + foreach (var player in PlayerManager.GetTickPlayers()) + { + if (player == null || !player.IsValid) continue; + if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) continue; + + if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill == skillName) + UpdateHUD(player, skillInfo); + } + } + if (revealed.IsEmpty && wires.IsEmpty) return; int tick = Server.TickCount; @@ -243,14 +302,20 @@ namespace src.player.skills public required Vector End { 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, int maxWires = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) + public class PlayerSkillInfo + { + public bool CanUse { get; set; } + public DateTime Cooldown { 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) { public float RadarDuration { get; set; } = radarDuration; public float TriggerRadius { get; set; } = triggerRadius; public float WireHeight { get; set; } = wireHeight; public float WireWidth { get; set; } = wireWidth; public float MaxWallDistance { get; set; } = maxWallDistance; - public int MaxWires { get; set; } = maxWires; + public float Cooldown { get; set; } = cooldown; } } } diff --git a/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs b/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs index 1ffd167..a2aa998 100644 --- a/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs +++ b/jRandomSkills - SRC Files/src/player/skills/WildThrow.cs @@ -42,7 +42,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).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); if (enemies.Length > 0) { ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); diff --git a/jRandomSkills - SRC Files/src/utils/Config.cs b/jRandomSkills - SRC Files/src/utils/Config.cs index 9d1a873..6f7acc4 100644 --- a/jRandomSkills - SRC Files/src/utils/Config.cs +++ b/jRandomSkills - SRC Files/src/utils/Config.cs @@ -90,6 +90,7 @@ namespace src.utils public bool TraceRayBeam { get; set; } public string DisableHUDOnDeathPermission { get; set; } public bool DisableSkillsOnRoundEnd { get; set; } + public int? CurseSkillPerPlayer { get; set; } public LanguageSystem LanguageSystem { get; set; } public HtmlHudCustomisation HtmlHudCustomisation { get; set; } public ChatMessage ChatMessage { get; set; } @@ -119,6 +120,7 @@ namespace src.utils DisableSpectateHUD = false; DisableHUDOnDeathPermission = "@jRandomSkills/death"; DisableSkillsOnRoundEnd = false; + CurseSkillPerPlayer = null; LanguageSystem = new LanguageSystem { diff --git a/jRandomSkills - SRC Files/src/utils/EntityManager.cs b/jRandomSkills - SRC Files/src/utils/EntityManager.cs index 6f775e7..5675e6a 100644 --- a/jRandomSkills - SRC Files/src/utils/EntityManager.cs +++ b/jRandomSkills - SRC Files/src/utils/EntityManager.cs @@ -123,44 +123,6 @@ namespace src.utils return CreateTrackedDynamicProp(playerIndex, "prop_dynamic_override"); } - public static CEnvShake? CreateTrackedEnvShake(uint playerIndex) - { - try - { - if (OverBudget()) return null; - var shake = Utilities.CreateEntityByName("env_shake"); - if (shake == null || !shake.IsValid) return null; - - shake.DispatchSpawn(); - RegisterEntity(shake.Index, playerIndex, "env_shake"); - return shake; - } - catch (Exception ex) - { - Server.PrintToConsole($"[EntityManager] CreateTrackedEnvShake: {ex.Message}"); - return null; - } - } - - public static CChicken? CreateTrackedChicken(uint playerIndex) - { - try - { - if (OverBudget()) return null; - var chicken = Utilities.CreateEntityByName("chicken"); - if (chicken == null || !chicken.IsValid) return null; - - chicken.DispatchSpawn(); - RegisterEntity(chicken.Index, playerIndex, "chicken"); - return chicken; - } - catch (Exception ex) - { - Server.PrintToConsole($"[EntityManager] CreateTrackedChicken: {ex.Message}"); - return null; - } - } - public static CPhysicsPropMultiplayer? CreateTrackedPhysicsProp(uint playerIndex) { try diff --git a/jRandomSkills - SRC Files/src/utils/PlayerManager.cs b/jRandomSkills - SRC Files/src/utils/PlayerManager.cs index 97e661d..35ef3a0 100644 --- a/jRandomSkills - SRC Files/src/utils/PlayerManager.cs +++ b/jRandomSkills - SRC Files/src/utils/PlayerManager.cs @@ -103,17 +103,6 @@ namespace src.utils return playersByIndex.Values.Count(p => p.Skill == skills); } - public static bool UpdatePlayerSkill(uint playerIndex, Skills skill, Skills specialSkill = Skills.None) - { - if (playersByIndex.TryGetValue(playerIndex, out var playerInfo)) - { - playerInfo.Skill = skill; - playerInfo.SpecialSkill = specialSkill; - return true; - } - return false; - } - public static void Clear() { playersByIndex.Clear(); diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs index 25ac717..17df931 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs @@ -346,6 +346,105 @@ namespace src.utils pendingKillCredits.Clear(); } + private static readonly HashSet bulletWeapons = new(StringComparer.Ordinal) + { + "deagle", "revolver", "glock", "usp_silencer", "cz75a", + "fiveseven", "p250", "tec9", "elite", "hkp2000", + "mp9", "mac10", "bizon", "mp7", "ump45", "p90", "mp5sd", + "famas", "galilar", "m4a1", "m4a1_silencer", "ak47", "aug", "sg553", + "ssg08", "awp", "scar20", "g3sg1", + "nova", "xm1014", "mag7", "sawedoff", + "m249", "negev" + }; + + public static bool FiresBullets(string? weapon) + { + if (string.IsNullOrEmpty(weapon)) return false; + + if (weapon.StartsWith("weapon_", StringComparison.Ordinal)) + weapon = weapon["weapon_".Length..]; + + return bulletWeapons.Contains(weapon); + } + + private static readonly HashSet curseSkills = + [ + Skills.Bankrupt, Skills.CarefulBullets, Skills.Darkness, Skills.Deactivator, + 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 + ]; + + private static readonly Dictionary curseCounts = []; + private static readonly Dictionary curserToVictim = []; + private static readonly object curseLock = new(); + + public static bool IsCurseSkill(Skills skill) => curseSkills.Contains(skill); + + public static void ClearCurses() + { + lock (curseLock) + { + curseCounts.Clear(); + curserToVictim.Clear(); + } + } + + public static bool CanCurse(uint victimIndex) + { + int? limit = Config.LoadedConfig.CurseSkillPerPlayer; + if (limit == null || limit <= 0) return true; + + lock (curseLock) + return !curseCounts.TryGetValue(victimIndex, out int used) || used < limit; + } + + public static bool TryClaimCurse(uint curserIndex, uint victimIndex) + { + int? limit = Config.LoadedConfig.CurseSkillPerPlayer; + + lock (curseLock) + { + ReleaseCurseLocked(curserIndex); + + curseCounts.TryGetValue(victimIndex, out int used); + if (limit != null && limit > 0 && used >= limit) return false; + + curseCounts[victimIndex] = used + 1; + curserToVictim[curserIndex] = victimIndex; + return true; + } + } + + public static void ReleaseCurse(uint curserIndex) + { + lock (curseLock) ReleaseCurseLocked(curserIndex); + } + + private static void ReleaseCurseLocked(uint curserIndex) + { + if (!curserToVictim.Remove(curserIndex, out uint victimIndex)) return; + if (!curseCounts.TryGetValue(victimIndex, out int used)) return; + + if (used <= 1) curseCounts.Remove(victimIndex); + else curseCounts[victimIndex] = used - 1; + } + + public static CCSPlayerController[] GetSelectableEnemies(CCSPlayerController player, bool respectCurseLimit = false) + { + if (player == null || !player.IsValid) return []; + + 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))) + .Cast()]; + } + public static bool TakeHealth(CCSPlayerPawn? pawn, int damage, CCSPlayerController? damageAttacker = null, string? damageWeapon = null) { if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll index ed3424a..c08b847 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/config.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json index 03dbe24..22fa6b8 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json @@ -20,6 +20,7 @@ "TraceRayBeam": false, "DisableHUDOnDeathPermission": "@jRandomSkills/death", "DisableSkillsOnRoundEnd": false, + "CurseSkillPerPlayer": null, "LanguageSystem": { "DefaultLangCode": "en", "DisableGeoLite": false, @@ -208,4 +209,4 @@ "Permissions": "@jRandomSkills/owner" } } -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json index 5617b60..239e302 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json @@ -87,7 +87,7 @@ "WireHeight": 30.0, "WireWidth": 1.5, "MaxWallDistance": 400.0, - "MaxWires": 2, + "Cooldown": 20.0, "NeedsTeammates": false, "DisableOnFreezeTime": false, "OnlyTeam": 0, diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll index e6e3986..ee43dd7 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json index 0d0d15f..bebaa5d 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/de.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Mehr als ein Spieler mit demselben Namen gefunden.", "selectplayerskill_command": "Gib /t ein", "selectplayerskill_incorrect_enemy_index": "Es wurden keine Spieler gefunden, die ausgewählt werden können.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Keine solche CHATCOLORS.REDskill gefunden", "player_not_found_setskill": "Kein solcher CHATCOLORS.REDplayer gefunden", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S – Scrollen", "menu_controlls_padding": " | ", "menu_controlls_select": "E – Auswählen" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json index 0866036..172540e 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/en.json @@ -486,8 +486,7 @@ "toxicsmoke_desc": "Your smoke grenades deal damage", "tripwire": "Tripwire", - "tripwire_desc": "Click [css_useSkill] to string a tripwire between two walls", - "tripwire_limit_info": "You can only have {0} tripwires at a time.", + "tripwire_desc": "Click [css_useSkill] to string a wire between two walls. Enemies touching it show on your radar", "tripwire_no_wall_info": "No walls close enough on both sides.", "tripwire_placed_info": "Tripwire placed.", "tripwire_triggered_info": "'{0}' tripped your wire.", @@ -533,6 +532,7 @@ "duplicate_player": "More than one player found with the same name.", "selectplayerskill_command": "Type /t", "selectplayerskill_incorrect_enemy_index": "No players were found to select.", + "curse_limit_info": "The curse limit for {0} has been reached", "skill_not_found_setskill": "No such CHATCOLORS.REDskill found", "player_not_found_setskill": "No such CHATCOLORS.REDplayer found", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Scroll", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Select" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json index 64fa588..19a008a 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/fr.json @@ -486,7 +486,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -532,6 +531,7 @@ "duplicate_player": "Plus d’un joueur trouvé avec le même nom.", "selectplayerskill_command": "Tapez /t", "selectplayerskill_incorrect_enemy_index": "Aucun joueur trouvé à sélectionner.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Aucune CHATCOLORS.REDcompétence trouvée", "player_not_found_setskill": "Aucun CHATCOLORS.REDjoueur trouvé", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json index 9dd510d..c8958da 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pl.json @@ -486,7 +486,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -532,6 +531,7 @@ "duplicate_player": "Znaleziono więcej niż jednego gracza o tej samej nazwie.", "selectplayerskill_command": "Wpisz /t", "selectplayerskill_incorrect_enemy_index": "Nie znaleziono graczy do wyboru.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nie znaleziono takiej CHATCOLORS.REDsupermocy", "player_not_found_setskill": "Nie znaleziono takiego CHATCOLORS.REDgracza", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json index e3b4a3b..907e797 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/pt-br.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Mais de um jogador encontrado com o mesmo nome.", "selectplayerskill_command": "Digite /t", "selectplayerskill_incorrect_enemy_index": "Não foram encontrados jogadores para selecionar.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Nenhuma habilidade CHATCOLORS.RED encontrada", "player_not_found_setskill": "Nenhum jogador CHATCOLORS.RED encontrado", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Rolar", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Selecionar" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json index 57f0118..f77d7cc 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/ru.json @@ -487,7 +487,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -533,6 +532,7 @@ "duplicate_player": "Найдено несколько игроков с таким именем.", "selectplayerskill_command": "Введите /t", "selectplayerskill_incorrect_enemy_index": "Игроки для выбора не найдены.", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "Навык не найден", "player_not_found_setskill": "Игрок не найден", diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json index 0b6fc08..cc64097 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json @@ -486,8 +486,7 @@ "toxicsmoke_desc": "Sis bombaların hasar verir", "tripwire": "Tel Tuzağı", - "tripwire_desc": "[css_useSkill] ile iki duvar arasına tel gerersin", - "tripwire_limit_info": "Aynı anda en fazla {0} tel gerebilirsin.", + "tripwire_desc": "[css_useSkill] ile iki duvar arasına tel ger. Tele değen düşman radarında görünür", "tripwire_no_wall_info": "İki yanında da yeterince yakın duvar yok.", "tripwire_placed_info": "Tel gerildi.", "tripwire_triggered_info": "'{0}' teline takıldı.", @@ -533,6 +532,7 @@ "duplicate_player": "Aynı isimde birden fazla oyuncu bulundu.", "selectplayerskill_command": "Sohbete /t yazın", "selectplayerskill_incorrect_enemy_index": "Seçilecek oyuncu yok.", + "curse_limit_info": "'{0}' üzerindeki lanet sınırı doldu, başka bir oyuncu seç", "skill_not_found_setskill": "Böyle bir yetenek yok", "player_not_found_setskill": "Böyle bir oyuncu yok", @@ -571,4 +571,4 @@ "menu_controlls_scroll": "W/S - Kaydır", "menu_controlls_padding": " | ", "menu_controlls_select": "E - Seç" -} \ No newline at end of file +} diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json index bb09467..7564a60 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/zh.json @@ -484,7 +484,6 @@ "tripwire": "tripwire", "tripwire_desc": "tripwire_desc", - "tripwire_limit_info": "tripwire_limit_info: {0}", "tripwire_no_wall_info": "tripwire_no_wall_info", "tripwire_placed_info": "tripwire_placed_info", "tripwire_triggered_info": "tripwire_triggered_info: {0}", @@ -530,6 +529,7 @@ "duplicate_player": "找到多个同名玩家。", "selectplayerskill_command": "输入 /t", "selectplayerskill_incorrect_enemy_index": "未找到可供选择的玩家。", + "curse_limit_info": "curse_limit_info", "skill_not_found_setskill": "未找到 CHATCOLORS.RED 技能", "player_not_found_setskill": "未找到 CHATCOLORS.RED 玩家", @@ -569,4 +569,4 @@ "menu_controlls_scroll": "W/S - 滚动", "menu_controlls_padding": " | ", "menu_controlls_select": "E - 选择" -} \ No newline at end of file +}