using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Commands; using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Utils; using src.command; using src.player; using src.utils; using System.Collections.Concurrent; using System.Reflection; using System.Text.Json; using WASDSharedAPI; using static CounterStrikeSharp.API.Core.Listeners; namespace src { public partial class jRandomSkills : BasePlugin { #pragma warning disable CS8618 public static jRandomSkills Instance { get; private set; } #pragma warning restore CS8618 public IEnumerable SkillPlayer => PlayerManager.GetAllPlayers(); public Random Random => Random.Shared; public CCSGameRules? GameRules { get; set; } private ConcurrentBag ManifestResources { get; set; } = ["models/sprays/spray_plane.vmdl"]; public IWasdMenuManager? MenuManager; // Skills that were enabled at least once this round; used to reset only those on round change (not all 124). public static readonly ConcurrentDictionary ActiveSkillsThisRound = new(); public static readonly ConcurrentDictionary SkillsUsedThisMap = new(); public override string ModuleName => "[CS2] [ jRandomSkills ]"; public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus."; public override string ModuleVersion => "1.2.4.b2"; public override void Load(bool hotReload) { Instance = this; Config.LoadConfig(); SkillsInfo.LoadSkillsInfo(); Localization.Load(); Debug.Load(); PlayerOnTick.Load(); Event.Load(); Command.Load(); WASDMenuAPI.WASDMenuAPI.LoadPlugin(Instance, hotReload); LoadAllSkills(); PlayerManager.SyncWithPlugin(Instance); Instance.RegisterListener(LoadManifest); Task.Run(async () => { await Task.Delay(3500); PrintInfoToConsole(); }); } public override void Unload(bool hotReload) { src.player.PerfLog.Info("===== PLUGIN UNLOAD (clean shutdown/reload) ====="); Debug.WriteToDebug("===== PLUGIN UNLOAD (clean shutdown/reload) ====="); Event.Unload(); Debug.Unload(); base.Unload(hotReload); } internal void AddToManifest(string prop) { if (!ManifestResources.Contains(prop)) ManifestResources.Add(prop); } internal void LoadManifest(ResourceManifest manifest) { foreach (var prop in ManifestResources) manifest.AddResource(prop); } internal void LoadAllSkills() { foreach (var skill in Enum.GetValues(typeof(Skills))) if (SkillsInfo.GetValue(skill, "active")) SkillAction(skill.ToString()!, "LoadSkill"); Debug.WriteToDebug($"jRandomSkills v{Instance.ModuleVersion} ({SkillData.Skills.Count - 1}/{SkillsInfo.LoadedConfig.Count - 1} Skills) loaded!"); Debug.WriteToDebug($"GameModes: {(Config.GameModes)Config.LoadedConfig.GameMode}, Lang: {Config.LoadedConfig.LanguageSystem.DefaultLangCode}, Debug: {DebugCategories.Describe(Config.DebugFlags)}"); foreach (var skill in SkillData.Skills) Debug.WriteToDebug($"Loaded: {skill.Skill}", DebugCategory.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; if (!SkillUtils.AnyCurseCapacity(curser)) return SkillUtils.TryClaimCurse(curser.Index, victimIndex, true); var curserEvent = PlayerManager.GetPlayerFromEvent(curser); curserEvent?.PrintToChat($" {ChatColors.Red}{curserEvent.GetTranslation("curse_limit_info", victim.PlayerName)}"); return false; } private static readonly ConcurrentDictionary<(string Skill, string Method), MethodInfo?> _skillMethodCache = new(); internal object? SkillAction(string skill, string methodName, object[]? param = null) { if (string.IsNullOrEmpty(skill)) return null; if (methodName == "EnableSkill") { ActiveSkillsThisRound.TryAdd(skill, 0); SkillsUsedThisMap.TryAdd(skill, 0); } if (SkillUtils.IsCurseSkill(skill)) { 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}"; Type? type = Type.GetType(className) ?? Assembly.GetExecutingAssembly().GetType(className) ?? AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where(t => t != null)!; } catch { return []; } }) .FirstOrDefault(t => t != null && string.Equals(t.FullName, className, StringComparison.Ordinal)); if (type == null || !typeof(ISkill).IsAssignableFrom(type)) { Server.PrintToConsole($"Could not find or load {className}"); return null; } return type.GetMethod(key.Method, BindingFlags.Static | BindingFlags.Public); }); if (method == null) return null; if (!PerfLog.Enabled) return method.Invoke(null, param); long perfStart = PerfLog.Start(); var result = method.Invoke(null, param); PerfLog.Sample($"SkillAction {skill}.{methodName}", perfStart, 30.0, 1.0); return result; } internal new void AddCommand(string name, string description, CommandInfo.CommandCallback handler) { var definition = new CommandDefinition(name, description, handler); CommandDefinitions.Add(definition); CommandManager.RegisterCommand(definition); } internal bool IsPlayerValid([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] CCSPlayerController? player) { return player != null && player.IsValid && player.PlayerPawn?.Value != null && player.PlayerPawn.Value.IsValid && player.PlayerPawn.Value.LifeState == (byte)LifeState_t.LIFE_ALIVE; } public uint[] footstepSoundEvents = [3109879199, 70939233, 1342713723, 2722081556, 1909915699, 3193435079, 2300993891, 3847761506, 4084367249, 1342713723, 3847761506, 2026488395, 2745524735, 2684452812, 2265091453, 1269567645, 520432428, 3266483468, 1346129716, 2061955732, 2240518199, 2829617974, 1194677450, 1803111098, 3749333696, 29217150, 1692050905, 2207486967, 2633527058, 3342414459, 988265811, 540697918, 1763490157, 3755338324, 3161194970, 3753692454, 3166948458, 3997353267, 3161194970, 3753692454, 3166948458, 3997353267, 809738584, 3368720745, 3295206520, 3184465677, 123085364, 3123711576, 737696412, 1403457606, 1770765328, 892882552, 3023174225, 4163677892, 3952104171, 4082928848, 1019414932, 1485322532, 1161855519, 1557420499, 1163426340, 809738584, 3368720745, 2708661994, 2479376962, 3295206520, 1404198078, 1194093029, 1253503839, 2189706910, 1218015996, 96240187, 1116700262, 84876002, 1598540856, 2231399653]; public uint[] silentSoundEvents = [2551626319, 765706800, 765706800, 2860219006, 2162652424, 2551626319, 2162652424, 117596568, 117596568, 740474905, 1661204257, 3009312615, 1506215040, 115843229, 3299941720, 1016523349, 2684452812, 2067683805, 2067683805, 1016523349, 4160462271, 1543118744, 585390608, 3802757032, 2302139631, 2546391140, 144629619, 4152012084, 4113422219, 1627020521, 2899365092, 819435812, 3218103073, 961838155, 1535891875, 1826799645, 3460445620, 1818046345, 3666896632, 3099536373, 1440734007, 1409986305, 1939055066, 782454593, 4074593561, 1540837791, 3257325156]; private static async void PrintInfoToConsole() { string? versionFromGithub = await GetLatestVersion(); var diffrentConfig = JsonSerializer.Serialize(Config.LoadedConfig) != JsonSerializer.Serialize(new Config.SettingsModel()); var diffrentSkillsInfo = JsonSerializer.Serialize(SkillsInfo.LoadedConfig) != JsonSerializer.Serialize(new SkillsInfo.SkillsInfoModel()); // Top border Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Magenta; Console.WriteLine($"\n************************************************************************************************************\n"); // ASCII tag Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.WriteLine(" || '||''|. '|| .|'''.| '|| || '|| '|| \r\n ... || || .... .. ... .. || ... .. .. .. ||.. ' || .. ... || || .... \r\n || ||''|' '' .|| || || .' '|| .| '|. || || || ''|||. || .' || || || ||. ' \r\n || || |. .|' || || || |. || || || || || || . '|| ||'|. || || || . '|.. \r\n || .||. '|' '|..'|' .||. ||. '|..'||. '|..|' .|| || ||. |'....|' .||. ||. .||. .||. .||. |'..|' \r\n .. |' \r\n '' "); // Version info Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write($"\njRandomSkills "); if (versionFromGithub == null) { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Yellow; Console.Write($"v{Instance.ModuleVersion} (failed to get version from github)"); } else if (versionFromGithub == Instance.ModuleVersion) { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Green; Console.Write($"v{Instance.ModuleVersion} (latest version)"); } else { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Red; Console.Write($"v{Instance.ModuleVersion} (new version {versionFromGithub} detected)"); } Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.WriteLine($" ({SkillData.Skills.Count - 1}/{SkillsInfo.LoadedConfig.Count - 1} Skills) loaded!"); if (versionFromGithub != null && versionFromGithub != Instance.ModuleVersion) { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Red; Console.WriteLine($"\n#########################################################"); Console.WriteLine($"# Download the new version from: #"); Console.WriteLine($"# https://github.com/Juzlus/jRandomSkills/releases #"); Console.WriteLine($"#########################################################"); } // Config preset Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write("\nConfig preset: "); Console.ForegroundColor = Config.LoadedConfig.ConfigName == "Default" && diffrentConfig ? (ConsoleColor)CS2ConsoleColors.Yellow : (ConsoleColor)CS2ConsoleColors.LightBlue; Console.Write($"{(Config.LoadedConfig.ConfigName == "Default" && diffrentConfig ? "(CUSTOM)" : Config.LoadedConfig.ConfigName)}"); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write("\nSkillsInfo preset: "); Console.ForegroundColor = SkillsInfo.LoadedConfig.Name == "Default" && diffrentSkillsInfo ? (ConsoleColor)CS2ConsoleColors.Yellow : (ConsoleColor)CS2ConsoleColors.LightBlue; Console.WriteLine($"{(SkillsInfo.LoadedConfig.Name == "Default" && diffrentSkillsInfo ? "(CUSTOM)" : SkillsInfo.LoadedConfig.Name)}"); // Main config info Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write("\nGameMode: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.Write($"{(Config.GameModes)Config.LoadedConfig.GameMode} ({Config.LoadedConfig.GameMode})"); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write(", Lang: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.Write(Config.LoadedConfig.LanguageSystem.DefaultLangCode); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write(", DebugMode: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.WriteLine(Config.LoadedConfig.DebugMode); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write("SkillHudDuration: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.Write(Config.LoadedConfig.SkillHudDuration == -1 ? "infinity" : Config.LoadedConfig.SkillHudDuration); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write(", SkillButton: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.Write(Config.LoadedConfig.AlternativeSkillButton ?? "(NULL)"); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.Write(", HtmlHudFix: "); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightBlue; Console.WriteLine(Config.LoadedConfig.EnableFlashingHtmlHudFix); // Dependences Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.WriteLine($"\nDependences:"); var files = new Dictionary { { "Newtonsoft Json", "./Newtonsoft.Json.dll" }, { "WASDMenuAPI", "./WASDMenuAPI.dll" }, { "MaxMind", "./MaxMind.Db.dll" }, { "GeoLite2", "./packages/GeoLite2-Country.mmdb" }, { "RayTraceApi", "./../../shared/RayTraceApi/RayTraceApi.dll" }, { "RayTraceImpl", "./../../plugins/RayTraceImpl/RayTraceImpl.dll" }, { "RayTrace MetaMod", "./../../../metamod/RayTrace.vdf" }, { "jRandomSkills gamedata", "./../../gamedata/jRandomSkills.gamedata.json" } }; foreach (var fileInfo in files) { string fullPath = Path.GetFullPath(Path.Combine(Instance.ModuleDirectory, fileInfo.Value)); if (File.Exists(fullPath)) { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Green3; Console.WriteLine($"- {fileInfo.Key} [OK]"); } else { Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.LightRed; Console.WriteLine($"- {fileInfo.Key} (Missing: {fileInfo.Value})"); } } // Skills info List enabled = []; List disabled = []; foreach (Skills skill in Enum.GetValues(typeof(Skills))) if (skill.ToString() == "None") continue; else if (SkillData.Skills.Any(s => s.Skill == skill)) enabled.Add(skill); else disabled.Add(skill); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.WriteLine($"\nEnabled skills ({enabled.Count}):"); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Green; Console.WriteLine(" " + string.Join("\n ", enabled.Chunk(10).Select(group => string.Join(", ", group)))); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Cyan; Console.WriteLine($"\nDisabled skills ({disabled.Count}):"); Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Red; Console.WriteLine(" " + string.Join("\n ", disabled.Chunk(10).Select(group => string.Join(", ", group)))); // Bottom border Console.ForegroundColor = (ConsoleColor)CS2ConsoleColors.Magenta; Console.WriteLine($"\n************************************************************************************************************\n"); Console.ResetColor(); } private static async Task GetLatestVersion() { using HttpClient client = new(); client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("jRandomSkills", "1.0")); const string URL = "https://api.github.com/repos/Juzlus/jRandomSkills/releases/latest"; try { string response = await client.GetStringAsync(URL); using JsonDocument doc = JsonDocument.Parse(response); if (doc.RootElement.TryGetProperty("tag_name", out JsonElement value)) return value.GetString()?.Replace("v", ""); return null; } catch (Exception) { return null; } } internal jSkill_PlayerInfo? GetPlayerInfoByIndex(uint playerIndex) { return PlayerManager.GetPlayerByIndex(playerIndex); } } public class jSkill_PlayerInfo { public required bool IsBot { get; set; } public required string PlayerName { get; set; } public required uint PlayerIndex { get; set; } public Skills Skill { get; set; } public Skills SpecialSkill { get; set; } public float? SkillChance { get; set; } public bool IsDrawing { get; set; } public DateTime SkillHudExpired { get; set; } public DateTime SkillDescriptionHudExpired { get; set; } public DateTime HudSuppressedUntil { get; set; } public string? PrintHTML { get; set; } public int HideHUD { get; set; } public bool SkillUsed = false; public bool? HudOnDeathBlocked { get; set; } public HudCacheEntry? HudCache { get; set; } } public sealed class HudCacheEntry { public uint PlayerIndex; public object? Config; public string? Header; public string? Center; public string? Extra; public bool IsDescription; public string? Content; } public class jSkill_SkillInfo(Skills skill, string color, bool display) { public Skills Skill { get; } = skill; public string Color { get; set; } = color; public bool Display { get; } = display; public static implicit operator Skills(jSkill_SkillInfo v) => v?.Skill ?? Skills.None; } public static class SkillData { public static ConcurrentBag Skills { get; } = []; private static jSkill_SkillInfo[]? _snapshot; public static jSkill_SkillInfo[] GetSnapshot() => _snapshot ??= [.. Skills]; private static Dictionary? _bySkill; public static jSkill_SkillInfo? GetInfo(Skills skill) { var map = _bySkill; if (map == null) { map = new Dictionary(); foreach (var s in Skills) map[s.Skill] = s; _bySkill = map; } return map.TryGetValue(skill, out var info) ? info : null; } public static void Invalidate() { _bySkill = null; _snapshot = null; } } public enum CS2ConsoleColors { Black = ConsoleColor.Black, White = ConsoleColor.DarkBlue, Orange = ConsoleColor.DarkGreen, Yellow = ConsoleColor.DarkCyan, LightGreen = ConsoleColor.DarkRed, Green = ConsoleColor.DarkMagenta, Green2 = ConsoleColor.DarkYellow, Green3 = ConsoleColor.Gray, Cyan = ConsoleColor.DarkGray, LightBlue = ConsoleColor.Blue, Blue = ConsoleColor.Green, DarkPurple = ConsoleColor.Cyan, Purple = ConsoleColor.Red, Magenta = ConsoleColor.Magenta, LightRed = ConsoleColor.Yellow, Red = ConsoleColor.White, } }