From eed76d7155f072403a039ca59a99bdbe0f8e103e Mon Sep 17 00:00:00 2001 From: schwarper <75811921+schwarper@users.noreply.github.com> Date: Fri, 12 Apr 2024 19:45:44 +0300 Subject: [PATCH] 0.0.1 Initial release --- README.md | 80 +++++++++++++++++++++++ TagApi/ITagApi.cs | 18 +++++ TagApi/TagApi.cs | 24 +++++++ TagApi/TagApi.csproj | 13 ++++ cs2-tags.sln | 28 ++++++++ cs2-tags/cs2-tags.csproj | 19 ++++++ cs2-tags/cs2-tags.json | 30 +++++++++ cs2-tags/src/api/api.cs | 89 +++++++++++++++++++++++++ cs2-tags/src/command/command.cs | 44 +++++++++++++ cs2-tags/src/config/config.cs | 25 +++++++ cs2-tags/src/cs2-tag.cs | 84 ++++++++++++++++++++++++ cs2-tags/src/event/event.cs | 112 ++++++++++++++++++++++++++++++++ cs2-tags/src/json/json.cs | 52 +++++++++++++++ cs2-tags/src/lib/lib.cs | 41 ++++++++++++ 14 files changed, 659 insertions(+) create mode 100644 README.md create mode 100644 TagApi/ITagApi.cs create mode 100644 TagApi/TagApi.cs create mode 100644 TagApi/TagApi.csproj create mode 100644 cs2-tags.sln create mode 100644 cs2-tags/cs2-tags.csproj create mode 100644 cs2-tags/cs2-tags.json create mode 100644 cs2-tags/src/api/api.cs create mode 100644 cs2-tags/src/command/command.cs create mode 100644 cs2-tags/src/config/config.cs create mode 100644 cs2-tags/src/cs2-tag.cs create mode 100644 cs2-tags/src/event/event.cs create mode 100644 cs2-tags/src/json/json.cs create mode 100644 cs2-tags/src/lib/lib.cs diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b19ff6 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ + +# cs2-tags + +## Commands +``` +css_tags_reload - Reloads tag +css_toggletags - Hide/Show tag +``` + +## Colors +``` +Default +White +DarkRed +Green +LightYellow +LightBlue +Olive +Lime +Red +LightPurple +Purple +Grey +Yellow +Gold +Silver +Blue +DarkBlue +BlueGrey +Magenta +LightRed +Orange +``` + +## Configuration +```json +{ + "settings": { + "deadname": "{TeamColor}*DEAD* ", + "nonename": "{White}(NONE) ", + "specname": "{Purple}(SPEC) ", + "tname": "{TeamColor}(T) ", + "ctname": "{TeamColor}(CT) " + }, + "tags": { + "default": { + "ScoreTag": "", + "ChatTag": "", + "NameColor": "{TeamColor}", + "ChatColor": "{White}" + } + "76561198397942039": { + "ScoreTag": "ADMIN", + "ChatTag": "{yellow}", + "NameColor": "{yellow}", + "ChatColor": "{Green}" + }, + "#Owner": { + "ScoreTag": "Owner", + "ChatTag": "{DarkRed}[Owner] ", + "NameColor": "{TeamColor}", + "ChatColor": "{Green}" + }, + "@css/admin": { + "ScoreTag": "Admin", + "ChatTag": "{DarkRed}[Admin] ", + "NameColor": "{TeamColor}", + "ChatColor": "{Green}" + } + } +} +``` + +## Screenshots + +![image](https://github.com/schwarper/cs2-tags/assets/75811921/d7dea9c8-0183-415a-841b-e62ca0cc1e31) + +![image](https://github.com/schwarper/cs2-tags/assets/75811921/fa2fd391-ea45-43e3-bd76-338082970b61) + +![image](https://github.com/schwarper/cs2-tags/assets/75811921/d4bc4425-a9a9-4534-8de6-f8c6464194a2) diff --git a/TagApi/ITagApi.cs b/TagApi/ITagApi.cs new file mode 100644 index 0000000..7b567e1 --- /dev/null +++ b/TagApi/ITagApi.cs @@ -0,0 +1,18 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Capabilities; +using static TagApi.Tag; + +namespace TagApi; + +public interface ITagApi +{ + public static readonly PluginCapability Capability = new("tag:api"); + + public string GetClientTag(CCSPlayerController player, Tags tag); + public void SetClientTag(CCSPlayerController player, Tags tag, string newtag); + public void ResetClientTag(CCSPlayerController player, Tags tag); + public string GetClientColor(CCSPlayerController player, Colors color); + public void SetClientColor(CCSPlayerController player, Colors color, string newcolor); + public void ResetClientColor(CCSPlayerController player, Colors color); + public void ReloadTags(); +} \ No newline at end of file diff --git a/TagApi/TagApi.cs b/TagApi/TagApi.cs new file mode 100644 index 0000000..834ded9 --- /dev/null +++ b/TagApi/TagApi.cs @@ -0,0 +1,24 @@ +namespace TagApi; + +public abstract class Tag +{ + public class CTag + { + public string ScoreTag { get; set; } = string.Empty; + public string ChatTag { get; set; } = string.Empty; + public string ChatColor { get; set; } = string.Empty; + public string NameColor { get; set; } = string.Empty; + } + + public enum Tags + { + ScoreTag, + ChatTag + } + + public enum Colors + { + ChatColor, + NameColor + } +} \ No newline at end of file diff --git a/TagApi/TagApi.csproj b/TagApi/TagApi.csproj new file mode 100644 index 0000000..090ffae --- /dev/null +++ b/TagApi/TagApi.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/cs2-tags.sln b/cs2-tags.sln new file mode 100644 index 0000000..184dc2d --- /dev/null +++ b/cs2-tags.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cs2-tags", "cs2-tags\cs2-tags.csproj", "{8F7EF308-8867-4802-B12F-5E27A2AF9995}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagApi", "TagApi\TagApi.csproj", "{A4E0FE20-CD3C-4E3D-95CE-AE48F7A77DF7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F7EF308-8867-4802-B12F-5E27A2AF9995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F7EF308-8867-4802-B12F-5E27A2AF9995}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F7EF308-8867-4802-B12F-5E27A2AF9995}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F7EF308-8867-4802-B12F-5E27A2AF9995}.Release|Any CPU.Build.0 = Release|Any CPU + {A4E0FE20-CD3C-4E3D-95CE-AE48F7A77DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4E0FE20-CD3C-4E3D-95CE-AE48F7A77DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4E0FE20-CD3C-4E3D-95CE-AE48F7A77DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4E0FE20-CD3C-4E3D-95CE-AE48F7A77DF7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9061A6F9-8330-43EE-A1A0-6DBC08696E5D} + EndGlobalSection +EndGlobal diff --git a/cs2-tags/cs2-tags.csproj b/cs2-tags/cs2-tags.csproj new file mode 100644 index 0000000..febb6cf --- /dev/null +++ b/cs2-tags/cs2-tags.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + \ No newline at end of file diff --git a/cs2-tags/cs2-tags.json b/cs2-tags/cs2-tags.json new file mode 100644 index 0000000..6cfdeb3 --- /dev/null +++ b/cs2-tags/cs2-tags.json @@ -0,0 +1,30 @@ +{ + "deadicon": "\u2620", + "tags": { + "default": { + "ScoreTag": "", + "ChatTag": "", + "NameColor": "{TeamColor}", + "ChatColor": "{White}" + }, + "76561199165718810": { + "ScoreTag": "schwarper", + "ChatTag": "{DarkRed}[schwarper] ", + "NameColor": "{TeamColor}", + "ChatColor": "{Green}" + }, + "#Owner": { + "ScoreTag": "Owner", + "ChatTag": "{DarkRed}[Owner] ", + "NameColor": "{TeamColor}", + "ChatColor": "{Green}" + }, + "@css/admin": { + "ScoreTag": "Admin", + "ChatTag": "{DarkRed}[Admin] ", + "NameColor": "{TeamColor}", + "ChatColor": "{Green}" + } + }, + "ConfigVersion": 1 +} \ No newline at end of file diff --git a/cs2-tags/src/api/api.cs b/cs2-tags/src/api/api.cs new file mode 100644 index 0000000..d5ad4b2 --- /dev/null +++ b/cs2-tags/src/api/api.cs @@ -0,0 +1,89 @@ +using CounterStrikeSharp.API.Core; +using TagApi; +using static Tag.Tag; +using static TagApi.Tag; + +namespace Tag; + +public class TagAPI : ITagApi +{ + public TagAPI() + { + } + + public string GetClientTag(CCSPlayerController player, Tags tag) + { + return tag switch + { + Tags.ScoreTag => Instance.PlayerDatas[player.Slot].ScoreTag, + Tags.ChatTag => Instance.PlayerDatas[player.Slot].ChatTag, + _ => string.Empty + }; + } + + public void SetClientTag(CCSPlayerController player, Tags tag, string newtag) + { + if (tag == Tags.ScoreTag) + { + Instance.PlayerDatas[player.Slot].ScoreTag = newtag; + } + else + { + Instance.PlayerDatas[player.Slot].ChatTag = newtag; + } + } + + public void ResetClientTag(CCSPlayerController player, Tags tag) + { + if (tag == Tags.ScoreTag) + { + Instance.PlayerDatas[player.Slot].ScoreTag = GetTag(player).ScoreTag; + } + else + { + Instance.PlayerDatas[player.Slot].ChatTag = GetTag(player).ChatTag; + } + } + + public string GetClientColor(CCSPlayerController player, Colors color) + { + return color switch + { + Colors.NameColor => Instance.PlayerDatas[player.Slot].NameColor, + Colors.ChatColor => Instance.PlayerDatas[player.Slot].ChatColor, + _ => string.Empty + }; + } + + public void SetClientColor(CCSPlayerController player, Colors color, string newcolor) + { + if (color == Colors.ChatColor) + { + Instance.PlayerDatas[player.Slot].ChatColor = newcolor; + } + else + { + Instance.PlayerDatas[player.Slot].NameColor = newcolor; + } + } + + public void ResetClientColor(CCSPlayerController player, Colors color) + { + CTag defaultTag = GetTag(player); + + if (color == Colors.ChatColor) + { + Instance.PlayerDatas[player.Slot].ChatColor = defaultTag.ChatColor; + } + else + { + Instance.PlayerDatas[player.Slot].NameColor = defaultTag.NameColor; + } + } + + public void ReloadTags() + { + Json.ReadConfig(); + UpdatePlayerTags(); + } +} \ No newline at end of file diff --git a/cs2-tags/src/command/command.cs b/cs2-tags/src/command/command.cs new file mode 100644 index 0000000..9e5e18c --- /dev/null +++ b/cs2-tags/src/command/command.cs @@ -0,0 +1,44 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using static Tag.Tag; +using static TagApi.Tag; + +namespace Tag; + +public partial class Tag +{ + [ConsoleCommand("css_tags_reload")] + [RequiresPermissions("@css/root")] + public void Command_Tags_Reload(CCSPlayerController? player, CommandInfo info) + { + Json.ReadConfig(); + UpdatePlayerTags(); + + info.ReplyToCommand("[cs2-tags] Tags are reloaded."); + } + + [ConsoleCommand("css_toggletags")] + [RequiresPermissions("@css/admin")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void Command_Toggletags(CCSPlayerController? player, CommandInfo info) + { + if (Instance.PlayerToggleTags[player!.Slot]) + { + Instance.PlayerDatas[player.Slot] = Instance.Config.Tags.FirstOrDefault(tag => tag.Key == "default").Value ?? new CTag(); + + Instance.PlayerToggleTags[player.Slot] = false; + + info.ReplyToCommand("[cs2-tags] Toggletags is false"); + } + else + { + Instance.PlayerDatas[player.Slot] = GetTag(player); + + Instance.PlayerToggleTags[player.Slot] = true; + + info.ReplyToCommand("[cs2-tags] Toggletags is true"); + } + } +} \ No newline at end of file diff --git a/cs2-tags/src/config/config.cs b/cs2-tags/src/config/config.cs new file mode 100644 index 0000000..f99c413 --- /dev/null +++ b/cs2-tags/src/config/config.cs @@ -0,0 +1,25 @@ +using CounterStrikeSharp.API.Core; +using System.Collections.Concurrent; +using System.Text.Json.Serialization; +using static TagApi.Tag; + +namespace Tag; + +public class TagConfig : BasePluginConfig +{ + [JsonPropertyName("settings")] + public Dictionary Settings { get; set; } = new Dictionary + { + { "deadname", "☠" }, + { "nonename", "{White}(NONE)" }, + { "specname", "{Purple}(SPEC)" }, + { "tname", "{Yellow}(T)" }, + { "ctname", "{Blue}(CT)" } + }; + + [JsonPropertyName("tags")] + public ConcurrentDictionary Tags { get; set; } = new ConcurrentDictionary + { + ["default"] = new CTag { ChatColor = "", ChatTag = "{Grey}[Player]", NameColor = "", ScoreTag = "" } + }; +} \ No newline at end of file diff --git a/cs2-tags/src/cs2-tag.cs b/cs2-tags/src/cs2-tag.cs new file mode 100644 index 0000000..e2b02e2 --- /dev/null +++ b/cs2-tags/src/cs2-tag.cs @@ -0,0 +1,84 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Capabilities; +using CounterStrikeSharp.API.Modules.Admin; +using System.Collections.Concurrent; +using TagApi; +using static TagApi.Tag; + +namespace Tag; + +public partial class Tag : BasePlugin, IPluginConfig +{ + public override string ModuleName => "Tag"; + public override string ModuleVersion => "0.0.1"; + public override string ModuleAuthor => "schwarper"; + + public TagConfig Config { get; set; } = new TagConfig(); + public ConcurrentDictionary PlayerDatas { get; set; } = new ConcurrentDictionary(); + public bool[] PlayerToggleTags { get; set; } = new bool[64]; + public static Tag Instance { get; set; } = new Tag(); + public int GlobalTick { get; set; } + + public override void Load(bool hotReload) + { + Capabilities.RegisterPluginCapability(ITagApi.Capability, () => new TagAPI()); + + Instance = this; + + for (int i = 0; i < 64; i++) + { + PlayerDatas[i] = new(); + PlayerToggleTags[i] = new(); + } + + if (hotReload) + { + UpdatePlayerTags(); + } + + Event.Load(); + } + + public void OnConfigParsed(TagConfig config) + { + Json.ReadCore(); + Config = config; + } + + public static void UpdatePlayerTags() + { + foreach (CCSPlayerController player in Utilities.GetPlayers()) + { + Instance.PlayerDatas[player.Slot] = GetTag(player); + } + } + + public static CTag GetTag(CCSPlayerController player) + { + ConcurrentDictionary tags = Instance.Config.Tags; + + CTag steamidTag = tags.FirstOrDefault(tag => tag.Key == player.SteamID.ToString()).Value; + + if (steamidTag != null) + { + return steamidTag; + } + + CTag groupTag = tags.FirstOrDefault(tag => tag.Key.StartsWith('#') && AdminManager.PlayerInGroup(player, tag.Key)).Value; + + if (groupTag != null) + { + return groupTag; + } + + CTag permissionTag = tags.FirstOrDefault(tag => tag.Key.StartsWith('@') && AdminManager.PlayerHasPermissions(player, tag.Key)).Value; + + if (permissionTag != null) + { + return permissionTag; + } + + return tags.FirstOrDefault(tag => tag.Key == "default").Value ?? new CTag(); + } +} \ No newline at end of file diff --git a/cs2-tags/src/event/event.cs b/cs2-tags/src/event/event.cs new file mode 100644 index 0000000..5fd6abe --- /dev/null +++ b/cs2-tags/src/event/event.cs @@ -0,0 +1,112 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Utils; +using static CounterStrikeSharp.API.Core.Listeners; +using static Tag.Tag; +using static TagApi.Tag; + +namespace Tag; + +public static class Event +{ + public static void Load() + { + Instance.RegisterEventHandler(OnPlayerConnectFull); + Instance.AddCommandListener("say", OnPlayerChat); + Instance.AddCommandListener("say_team", OnPlayerChat); + Instance.RegisterListener(OnTick); + } + + public static HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info) + { + CCSPlayerController player = @event.Userid; + + if (player == null || !player.IsValid) + { + return HookResult.Continue; + } + + Instance.PlayerToggleTags[player.Slot] = true; + Instance.PlayerDatas[player.Slot] = GetTag(player); + + return HookResult.Continue; + } + + public static HookResult OnPlayerChat(CCSPlayerController? player, CommandInfo info) + { + if (player == null || !player.IsValid || info.GetArg(1).Length == 0) + { + return HookResult.Continue; + } + + string command = info.GetArg(1); + + if (Json.PublicChatTrigger.Any(i => command.StartsWith(i))) + { + player.ExecuteClientCommandFromServer($"css_{command.Substring(1)}"); + } + + if (Json.SilentChatTrigger.Any(i => command.StartsWith(i))) + { + return HookResult.Handled; + } + + CTag playerData = Instance.PlayerDatas[player.Slot]!; + + string deadname = player.PawnIsAlive ? string.Empty : Instance.Config.Settings["deadname"]; + bool teammessage = info.GetArg(0) == "say_team"; + string tag = playerData.ChatTag; + string namecolor = playerData.NameColor; + string chatcolor = playerData.ChatColor; + + string message = FormatMessage(deadname, teammessage ? Lib.TeamName(player.Team) : string.Empty, tag, namecolor, chatcolor, player, command); + + static string FormatMessage(string deadIcon, string teamname, string tag, string namecolor, string chatcolor, CCSPlayerController player, string text) + { + return Lib.ReplaceTags($" {deadIcon}{teamname}{tag}{namecolor}{player.PlayerName}{ChatColors.Default}: {chatcolor}{text}", player.Team); + } + + if (info.GetArg(0) == "say_team") + { + foreach (CCSPlayerController target in Utilities.GetPlayers().Where(target => target.Team == player.Team && target.IsValid && !target.IsBot)) + { + player.PrintToChat(message); + } + } + else + { + Server.PrintToChatAll(message); + } + + return HookResult.Handled; + } + + public static void OnTick() + { + Instance.GlobalTick++; + + if (Instance.GlobalTick % 200 != 0) + { + return; + } + + foreach (CCSPlayerController player in Utilities.GetPlayers()) + { + string playerclan = Instance.PlayerDatas[player.Slot].ScoreTag; + + if (playerclan == string.Empty) + { + continue; + } + + player.Clan = playerclan; + + //string playername = player.PlayerName; + //player.PlayerName = playername + ' '; + + Utilities.SetStateChanged(player, "CCSPlayerController", "m_szClan"); + Utilities.SetStateChanged(player, "CBasePlayerController", "m_iszPlayerName"); + } + } +} \ No newline at end of file diff --git a/cs2-tags/src/json/json.cs b/cs2-tags/src/json/json.cs new file mode 100644 index 0000000..f7d470f --- /dev/null +++ b/cs2-tags/src/json/json.cs @@ -0,0 +1,52 @@ +using CounterStrikeSharp.API; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Reflection; +using static Tag.Tag; + +namespace Tag; + +public class CoreConfig +{ + public JArray PublicChatTrigger { get; set; } = []; + public JArray SilentChatTrigger { get; set; } = []; +} + +public static class Json +{ + private static readonly string AssemblyName = Assembly.GetExecutingAssembly().GetName().Name ?? ""; + private static readonly string CorePath = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/configs/core.json"; + private static readonly string PluginJsonPath = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/configs/plugins/{AssemblyName}/{AssemblyName}.json"; + + public static string[] PublicChatTrigger { get; set; } = []; + public static string[] SilentChatTrigger { get; set; } = []; + + public static void ReadCore() + { + string jsonContent = File.ReadAllText(CorePath); + + dynamic? config = JsonConvert.DeserializeObject(jsonContent); + + if (config == null) + { + return; + } + + PublicChatTrigger = ((JArray)config.PublicChatTrigger).Select(x => x.ToString()).ToArray(); + SilentChatTrigger = ((JArray)config.SilentChatTrigger).Select(x => x.ToString()).ToArray(); + } + + public static void ReadConfig() + { + string jsonContent = File.ReadAllText(PluginJsonPath); + + TagConfig? config = JsonConvert.DeserializeObject(jsonContent); + + if (config == null) + { + return; + } + + Instance.Config = config; + } +} \ No newline at end of file diff --git a/cs2-tags/src/lib/lib.cs b/cs2-tags/src/lib/lib.cs new file mode 100644 index 0000000..dbbe94a --- /dev/null +++ b/cs2-tags/src/lib/lib.cs @@ -0,0 +1,41 @@ +using CounterStrikeSharp.API.Modules.Utils; +using System.Reflection; +using static Tag.Tag; + +namespace Tag; + +public static class Lib +{ + public static string TeamName(CsTeam team) + { + return team switch + { + CsTeam.Spectator => ReplaceTags(Instance.Config.Settings["specname"], CsTeam.Spectator), + CsTeam.Terrorist => ReplaceTags(Instance.Config.Settings["tname"], CsTeam.Terrorist), + CsTeam.CounterTerrorist => ReplaceTags(Instance.Config.Settings["ctname"], CsTeam.CounterTerrorist), + CsTeam.None => ReplaceTags(Instance.Config.Settings["nonename"], CsTeam.None), + _ => ReplaceTags(Instance.Config.Settings["nonename"], CsTeam.None) + }; + } + + public static string ReplaceTags(string message, CsTeam team) + { + if (message.Contains('{')) + { + string modifiedValue = message; + + foreach (FieldInfo field in typeof(ChatColors).GetFields()) + { + string pattern = $"{{{field.Name}}}"; + + if (message.Contains(pattern, StringComparison.OrdinalIgnoreCase)) + { + modifiedValue = modifiedValue.Replace(pattern, field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase); + } + } + return modifiedValue.Replace("{TeamColor}", ChatColors.ForTeam(team).ToString()); + } + + return message; + } +} \ No newline at end of file