using System; using System.Linq; using System.Text.RegularExpressions; using System.Collections.Generic; using System.Text; using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Oxide.Core.Plugins; using ConVar; using UnityEngine; using CompanionServer; using Pool = Facepunch.Pool; using Random = System.Random; namespace Oxide.Plugins { [Info("Coloured Chat", "collect_vood", "2.2.87")] [Description("Allows players to change their name & message colour in chat")] class ColouredChat : CovalencePlugin { [PluginReference] private Plugin BetterChat, BetterChatMute, ZoneManager; #region Fields private readonly StringBuilder _sharedStringBuilder = new StringBuilder(); private const string ColourRegex = "^#(?:[0-9a-fA-f]{3}){1,2}$"; private const string ChatFormat = "{0}: {1}"; #endregion #region Localization protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary() { { "NoPermission", "You don't have permission to use this command." }, { "NoPermissionSetOthers", "You don't have permission to set other players {0} colours." }, { "NoPermissionGradient", "You don't have permission to use {0} gradients." }, { "NoPermissionRandom", "You don't have permission to use random {0} colours." }, { "NoPermissionRainbow", "You don't have permission to use the rainbow colours." }, { "IncorrectGradientUsage", "Incorrect usage! To use gradients please use /{0} gradient hexCode1 hexCode2 ..." }, { "IncorrectGradientUsageArgs", "Incorrect usage! A gradient requires at least two different valid colours!"}, { "GradientChanged", "{0} gradient changed to {1}!"}, { "GradientChangedFor", "{0}'s gradient {1} colour changed to {2}!"}, { "IncorrectUsage", "Incorrect usage! /{0} \nFor detailed help do /{1}" }, { "IncorrectSetUsage", "Incorrect set usage! /{0} set \nFor a list of colours do /colours" }, { "PlayerNotFound", "Player {0} was not found." }, { "InvalidCharacters", "The character '{0}' is not allowed in colours. Please remove it." }, { "ColourRemoved", "{0} colour removed!" }, { "ColourRemovedFor", "{0}'s {1} colour was removed!" }, { "ColourChanged", "{0} colour changed to {1}!" }, { "ColourChangedFor", "{0}'s {1} colour changed to {2}!" }, { "ColoursInfo", "You can only use hexcodes, eg '#ffff94'\nTo remove your colour, use 'clear', 'reset' or 'remove'\n\nAvailable Commands: {0}\n\n{1}"}, { "InvalidColour", "That colour is not valid. Do /colours for more information on valid colours." }, { "RndColour", "{0} colour was randomized to {1}" }, { "RndColourFor", "{0} colour of {1} randomized to {2}."}, { "RainbowColour", "{0} colour was set to rainbow." }, { "RainbowColourFor", "{0} colour of {1} set to rainbow."}, { "IncorrectGroupUsage", "Incorrect group usage! /{0} group \nFor a list of colours do /colours" }, }, this); } #endregion #region Config private Configuration _configuration; private class Configuration { //General [JsonProperty(PropertyName = "Player Inactivity Data Removal (days)")] public int InactivityRemovalTime = 7; [JsonProperty(PropertyName = "Block messages of muted players (requires BetterChatMute)")] public bool BlockChatMute = true; [JsonProperty(PropertyName = "Rainbow Colours")] public string[] RainbowColours = { "#ff0000", "#ffa500", "#ffff94", "#008000", "#0000ff", "#4b0082", "#ee82ee" }; [JsonProperty(PropertyName = "Blocked Characters")] public string[] BlockedValues = { "{", "}", "size" }; //Name [JsonProperty(PropertyName = "Name colour commands", ObjectCreationHandling = ObjectCreationHandling.Replace)] public string[] NameColourCommands = new string[] { "colour", "color" }; [JsonProperty(PropertyName = "Name colour commands (Help)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public string[] NameColoursCommands = new string[] { "colours", "colors" }; [JsonProperty(PropertyName = "Name show colour permission")] public string NamePermShow = "colouredchat.name.show"; [JsonProperty(PropertyName = "Name use permission")] public string NamePermUse = "colouredchat.name.use"; [JsonProperty(PropertyName = "Name use gradient permission")] public string NamePermGradient = "colouredchat.name.gradient"; [JsonProperty(PropertyName = "Name default rainbow name permission")] public string NamePermRainbow = "colouredchat.name.rainbow"; [JsonProperty(PropertyName = "Name bypass restrictions permission")] public string NamePermBypass = "colouredchat.name.bypass"; [JsonProperty(PropertyName = "Name set others colour permission")] public string NamePermSetOthers = "colouredchat.name.setothers"; [JsonProperty(PropertyName = "Name get random colour permission")] public string NamePermRandomColour = "colouredchat.name.random"; [JsonProperty(PropertyName = "Name use blacklist")] public bool NameUseBlacklist = true; [JsonProperty(PropertyName = "Name blocked colour hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List NameBlockColoursHex = new List { { "#000000" } }; [JsonProperty(PropertyName = "Name blocked colours range hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List NameBlacklistedRangeColoursHex = new List { { new ColourRange("#000000", "#000000") } }; [JsonProperty(PropertyName = "Name use whitelist")] public bool NameUseWhitelist = false; [JsonProperty(PropertyName = "Name whitelisted colours hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List NameWhitelistedColoursHex = new List { { "#000000" } }; [JsonProperty(PropertyName = "Name whitelisted colour range hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List NameWhitelistedRangeColoursHex = new List { { new ColourRange("#000000", "#FFFFFF") } }; //Message [JsonProperty(PropertyName = "Message colour commands", ObjectCreationHandling = ObjectCreationHandling.Replace)] public string[] MessageColourCommands = new string[] { "mcolour", "mcolor" }; [JsonProperty(PropertyName = "Message colour commands (Help)", ObjectCreationHandling = ObjectCreationHandling.Replace)] public string[] MessageColoursCommands = new string[] { "mcolours", "mcolors" }; [JsonProperty(PropertyName = "Message show colour permission")] public string MessagePermShow = "colouredchat.message.show"; [JsonProperty(PropertyName = "Message use permission")] public string MessagePermUse = "colouredchat.message.use"; [JsonProperty(PropertyName = "Message use gradient permission")] public string MessagePermGradient = "colouredchat.message.gradient"; [JsonProperty(PropertyName = "Message default rainbow name permission")] public string MessagePermRainbow = "colouredchat.message.rainbow"; [JsonProperty(PropertyName = "Message bypass restrictions permission")] public string MessagePermBypass = "colouredchat.message.bypass"; [JsonProperty(PropertyName = "Message set others colour permission")] public string MessagePermSetOthers = "colouredchat.message.setothers"; [JsonProperty(PropertyName = "Message get random colour permission")] public string MessagePermRandomColour = "colouredchat.message.random"; [JsonProperty(PropertyName = "Message use blacklist")] public bool MessageUseBlacklist = true; [JsonProperty(PropertyName = "Message blocked colours hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List MessageBlockColoursHex = new List { { "#000000" } }; [JsonProperty(PropertyName = "Message blocked colour range hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List MessageBlacklistedRangeColoursHex = new List { { new ColourRange("#000000", "#000000") } }; [JsonProperty(PropertyName = "Message use whitelist")] public bool MessageUseWhitelist = false; [JsonProperty(PropertyName = "Message whitelisted colours hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List MessageWhitelistedColoursHex = new List { { "#000000" } }; [JsonProperty(PropertyName = "Message whitelisted colour range hex", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List MessageWhitelistedRangeColoursHex = new List { { new ColourRange("#000000", "#FFFFFF") } }; } protected override void LoadDefaultConfig() { PrintWarning("Creating a new configuration file"); _configuration = new Configuration(); } protected override void LoadConfig() { base.LoadConfig(); _configuration = Config.ReadObject(); SaveConfig(); } protected override void SaveConfig() => Config.WriteObject(_configuration); #region Colour Range Class public class ColourRange { [JsonProperty(PropertyName = "From")] public string _from; [JsonProperty(PropertyName = "To")] public string _to; public ColourRange(string from, string to) { _from = from; _to = to; } } #endregion #endregion #region Data private Dictionary cachedData = new Dictionary(); private StoredData storedData; private Dictionary allColourData => storedData.AllColourData; private class StoredData { public Dictionary AllColourData { get; private set; } = new Dictionary(); } private class PlayerData { [JsonProperty("Name Colour")] public string NameColour = string.Empty; [JsonProperty("Name Gradient Args")] public string[] NameGradientArgs = null; [JsonProperty("Message Colour")] public string MessageColour = string.Empty; [JsonProperty("Message Gradient Args")] public string[] MessageGradientArgs = null; [JsonProperty("Last active")] public long LastActive = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); public PlayerData() { } public PlayerData(string nameColour = "", string[] nameGradientArgs = null, string messageColour = "", string[] messageGradientArgs = null) { NameColour = nameColour; NameGradientArgs = nameGradientArgs; MessageColour = messageColour; MessageGradientArgs = messageGradientArgs; } public PlayerData(bool isGroup) { LastActive = 0; } } private class CachePlayerData { public string NameColourGradient; public string PrimaryGroup; public CachePlayerData(string nameColourGradient = "", string primaryGroup = "") { NameColourGradient = nameColourGradient; PrimaryGroup = primaryGroup; } } private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData); private void OnServerSave() { ClearUpData(); SaveData(); } private void Unload() => SaveData(); private void ChangeNameColour(string key, string colour, string[] colourArgs) { var playerData = new PlayerData(colour, colourArgs); if (!allColourData.ContainsKey(key)) allColourData.Add(key, playerData); allColourData[key].NameColour = colour; allColourData[key].NameGradientArgs = colourArgs; } private void ChangeMessageColour(string key, string colour, string[] colourArgs) { var playerData = new PlayerData(string.Empty, null, colour, colourArgs); if (!allColourData.ContainsKey(key)) allColourData.Add(key, playerData); allColourData[key].MessageColour = colour; allColourData[key].MessageGradientArgs = colourArgs; } #endregion #region Hooks private void Init() { if (_configuration.MessageUseBlacklist && _configuration.MessageUseWhitelist || _configuration.NameUseBlacklist && _configuration.NameUseWhitelist) PrintWarning("You are using both black/- and whitelist! This might cause issues."); permission.RegisterPermission(_configuration.NamePermShow, this); permission.RegisterPermission(_configuration.MessagePermShow, this); permission.RegisterPermission(_configuration.NamePermRainbow, this); permission.RegisterPermission(_configuration.MessagePermRainbow, this); permission.RegisterPermission(_configuration.NamePermGradient, this); permission.RegisterPermission(_configuration.MessagePermGradient, this); permission.RegisterPermission(_configuration.NamePermUse, this); permission.RegisterPermission(_configuration.MessagePermUse, this); permission.RegisterPermission(_configuration.NamePermBypass, this); permission.RegisterPermission(_configuration.MessagePermBypass, this); permission.RegisterPermission(_configuration.NamePermSetOthers, this); permission.RegisterPermission(_configuration.MessagePermSetOthers, this); permission.RegisterPermission(_configuration.NamePermRandomColour, this); permission.RegisterPermission(_configuration.MessagePermRandomColour, this); AddCovalenceCommand(_configuration.NameColourCommands, nameof(cmdNameColour)); AddCovalenceCommand(_configuration.NameColoursCommands, nameof(cmdNameColours)); AddCovalenceCommand(_configuration.MessageColourCommands, nameof(cmdMessageColour)); AddCovalenceCommand(_configuration.MessageColoursCommands, nameof(cmdMessageColours)); storedData = Interface.Oxide.DataFileSystem.ReadObject(Name); ClearUpData(); } private void OnUserConnected(IPlayer player) { if (!allColourData.ContainsKey(player.Id)) return; allColourData[player.Id].LastActive = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } private void OnUserDisconnected(IPlayer player) { if (!allColourData.ContainsKey(player.Id)) return; allColourData[player.Id].LastActive = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } private void OnUserNameUpdated(string id, string oldName, string newName) => ClearCache(id); private void OnUserGroupAdded(string id, string groupName) => ClearCache(id); private void OnUserGroupRemoved(string id, string groupName) => ClearCache(id); private void OnGroupDeleted(string name) => ClearCache(); private void OnGroupPermissionGranted(string name, string perm) => ClearCache(); private void OnGroupPermissionRevoked(string name, string perm) => ClearCache(); private object OnPlayerChat(BasePlayer player, string message, Chat.ChatChannel channel) { if (BetterChatIns()) return null; if (_configuration.BlockChatMute && BetterChatMuteIns()) if (BetterChatMute.Call("API_IsMuted", player.IPlayer)) return null; if (player == null) return null; if (ZoneManagerIns() && ZoneManager.Call("PlayerHasFlag", player, "nochat")) return false; if (Chat.serverlog) { var objArray = new object[] { ConsoleColor.DarkYellow, null, null, null }; objArray[1] = string.Concat(new object[] { "[", channel, "] ", player.displayName.EscapeRichText(), ": " }); objArray[2] = ConsoleColor.DarkGreen; objArray[3] = message; ServerConsole.PrintColoured(objArray); } var colouredChatMessage = FromMessage(player.IPlayer, channel, message); var colouredChatMessageDict = colouredChatMessage.GetDictionary(); #region API foreach (var plugin in plugins.GetAll()) { var obj = plugin.CallHook("OnColouredChat", colouredChatMessageDict); if (obj is Dictionary) { try { colouredChatMessageDict = obj as Dictionary; } catch (Exception e) { PrintError($"Failed to load modified OnColouredChat hook data from plugin '{plugin.Title} ({plugin.Version})':{Environment.NewLine}{e}"); continue; } } else if (obj != null) return obj; } colouredChatMessage = ColouredChatMessage.FromDictionary(colouredChatMessageDict); #endregion SendMessage(colouredChatMessage); return true; } private Dictionary OnBetterChat(Dictionary dict) { if (dict != null) { var player = dict["Player"] as IPlayer; var colouredChatMessage = FromMessage(player, (Chat.ChatChannel)dict["ChatChannel"], dict["Message"].ToString()); #region API var colouredChatMessageDict = colouredChatMessage.GetDictionary(); foreach (Plugin plugin in plugins.GetAll()) { object obj = plugin.CallHook("OnColouredChat", colouredChatMessageDict); if (obj is Dictionary) { try { colouredChatMessageDict = obj as Dictionary; } catch (Exception e) { PrintError($"Failed to load modified OnColouredChat hook data from plugin '{plugin.Title} ({plugin.Version})':{Environment.NewLine}{e}"); continue; } } else if (obj != null) { if (obj is bool) { dict["CancelOption"] = 2; } } } colouredChatMessage = ColouredChatMessage.FromDictionary(colouredChatMessageDict); #endregion if (!string.IsNullOrEmpty(colouredChatMessage.Name)) dict["Username"] = colouredChatMessage.Name; if (!string.IsNullOrEmpty(colouredChatMessage.Colour)) { ((Dictionary)dict["UsernameSettings"])["Color"] = colouredChatMessage.Colour; } dict["Message"] = colouredChatMessage.Message; } return dict; } #endregion #region Commands void cmdNameColour(IPlayer player, string cmd, string[] args) => ProcessColourCommand(player, cmd, args); void cmdNameColours(IPlayer player, string cmd, string[] args) => ProcessColoursCommand(player, cmd, args); void cmdMessageColour(IPlayer player, string cmd, string[] args) => ProcessColourCommand(player, cmd, args, true); void cmdMessageColours(IPlayer player, string cmd, string[] args) => ProcessColoursCommand(player, cmd, args, true); #endregion #region Helpers private bool BetterChatIns() => (BetterChat != null && BetterChat.IsLoaded); private bool BetterChatMuteIns() => (BetterChatMute != null && BetterChatMute.IsLoaded); private bool ZoneManagerIns() => (ZoneManager != null && ZoneManager.IsLoaded); private bool IsValidColour(string input) => Regex.Match(input, ColourRegex).Success; private string GetMessage(string key, IPlayer player, params string[] args) => string.Format(lang.GetMessage(key, this, player.Id), args); //Name private bool HasNameShowPerm(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermShow)); private bool HasNamePerm(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermUse)); private bool HasNameRainbow(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermRainbow)); private bool CanNameGradient(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermGradient)); private bool CanNameBypass(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermBypass)); private bool CanNameSetOthers(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermSetOthers)); private bool CanNameRandomColour(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.NamePermRandomColour)); //Message private bool HasMessageShowPerm(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermShow)); private bool HasMessagePerm(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermUse)); private bool HasMessageRainbow(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermRainbow)); private bool CanMessageGradient(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermGradient)); private bool CanMessageBypass(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermBypass)); private bool CanMessageSetOthers(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermSetOthers)); private bool CanMessageRandomColour(IPlayer player) => (player.IsAdmin || permission.UserHasPermission(player.Id, _configuration.MessagePermRandomColour)); private bool IsValidName(string input, IPlayer iPlayer = null) { if (iPlayer != null && CanNameBypass(iPlayer)) return true; if (_configuration.NameUseBlacklist) { bool inRange = false; foreach (var colourRange in _configuration.NameBlacklistedRangeColoursHex) { inRange = IsInHexRange(input, colourRange._from, colourRange._to); if (inRange) break; } return !_configuration.NameBlockColoursHex.Any(x => (input == x)) && !inRange; } else if (_configuration.NameUseWhitelist) { bool inRange = false; foreach (var colourRange in _configuration.NameWhitelistedRangeColoursHex) { inRange = IsInHexRange(input, colourRange._from, colourRange._to); if (!inRange) break; } return _configuration.NameWhitelistedColoursHex.Any(x => (input == x)) || inRange; } return true; } private bool IsValidMessage(string input, IPlayer iPlayer = null) { if (iPlayer != null && CanMessageBypass(iPlayer)) return true; if (_configuration.MessageUseBlacklist) { bool inRange = false; foreach (var colourRange in _configuration.MessageBlacklistedRangeColoursHex) { inRange = IsInHexRange(input, colourRange._from, colourRange._to); if (inRange) break; } return !_configuration.MessageBlockColoursHex.Any(x => (input == x)) && !inRange; } else if (_configuration.MessageUseWhitelist) { bool inRange = false; foreach (var colourRange in _configuration.MessageWhitelistedRangeColoursHex) { inRange = IsInHexRange(input, colourRange._from, colourRange._to); if (!inRange) break; } return _configuration.MessageWhitelistedColoursHex.Any(x => (input == x)) || inRange; } return true; } private void SendMessage(ColouredChatMessage colouredChatMessage) { var player = colouredChatMessage.Player.Object as BasePlayer; if (player == null) return; // cannot send if player does not exist var formattedLogMessage = _sharedStringBuilder; formattedLogMessage.Clear(); switch (colouredChatMessage.ChatChannel) { case Chat.ChatChannel.Global: case Chat.ChatChannel.Server: { ConsoleNetwork.BroadcastToAllClients("chat.add2", (int) colouredChatMessage.ChatChannel, player.userID, colouredChatMessage.Message, colouredChatMessage.Name, colouredChatMessage.Colour); formattedLogMessage.Append("[CHAT] "); break; } case Chat.ChatChannel.Team: { if (player.Team == null) return; // Broadcast to rust+ app users player.Team.BroadcastTeamChat(player.userID, player.displayName, colouredChatMessage.Message, colouredChatMessage.Colour); ConsoleNetwork.SendClientCommand(player.Team.GetOnlineMemberConnections(), "chat.add2", (int) colouredChatMessage.ChatChannel, player.userID, colouredChatMessage.Message, colouredChatMessage.Name, colouredChatMessage.Colour); formattedLogMessage.Append("[TEAM CHAT] "); break; } case Chat.ChatChannel.Cards: { if (!player.isMounted) return; var cardTable = player.GetMountedVehicle() as CardTable; if (cardTable == null || !cardTable.GameController.PlayerIsInGame(player)) return; var cardTableConnections = Pool.GetList(); cardTable.GameController.GetConnectionsInGame(cardTableConnections); if (cardTableConnections.Count > 0) { ConsoleNetwork.SendClientCommand(cardTableConnections, "chat.add2", (int) colouredChatMessage.ChatChannel, player.userID, colouredChatMessage.Message, colouredChatMessage.Name, colouredChatMessage.Colour); } Pool.FreeList(ref cardTableConnections); formattedLogMessage.Append("[CARDS CHAT] "); break; } case Chat.ChatChannel.Local: { var num = Chat.localChatRange * Chat.localChatRange; var senderPosition = player.transform.position; var closeByConnections = Pool.GetList(); foreach (var basePlayer in BasePlayer.activePlayerList) { var sqrMagnitude = (basePlayer.transform.position - senderPosition).sqrMagnitude; if (sqrMagnitude > num) continue; closeByConnections.Add(basePlayer.Connection); } ConsoleNetwork.SendClientCommand(closeByConnections, "chat.add2", (int) colouredChatMessage.ChatChannel, player.userID, colouredChatMessage.Message, colouredChatMessage.Name, colouredChatMessage.Colour); Pool.FreeList(ref closeByConnections); formattedLogMessage.Append("[CHAT] "); break; } } // Console logging formattedLogMessage.Append(player.displayName.EscapeRichText()).Append('[').Append(player.UserIDString) .Append("] : ").Append(colouredChatMessage.Message); DebugEx.Log(formattedLogMessage); // Rcon logging var chatEntry = new Chat.ChatEntry { Channel = colouredChatMessage.ChatChannel, Message = colouredChatMessage.Message, UserId = player.UserIDString, Username = player.displayName, Color = colouredChatMessage.Colour, Time = Facepunch.Math.Epoch.Current }; Facepunch.RCon.Broadcast(Facepunch.RCon.LogType.Chat, chatEntry); } private void ProcessColourCommand(IPlayer player, string cmd, string[] args, bool isMessage = false) { if (args.Length < 1) { player.Reply(GetMessage("IncorrectUsage", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0], isMessage ? _configuration.MessageColoursCommands[0] : _configuration.NameColoursCommands[0])); return; } var colLower = string.Empty; if (args[0] == "set") { if ((!isMessage && !CanNameSetOthers(player)) || (isMessage && !CanMessageSetOthers(player))) { player.Reply(GetMessage("NoPermissionSetOthers", player, isMessage ? "message" : "name")); return; } if (args.Length < 3) { player.Reply(GetMessage("IncorrectSetUsage", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0])); return; } var target = covalence.Players.FindPlayer(args[1]); if (target == null) { player.Reply(GetMessage("PlayerNotFound", player, args[1])); return; } colLower = args[2].ToLower(); ProcessColour(player, target, colLower, args.Skip(2).ToArray(), isMessage); } else if (args[0] == "group") { if (!player.IsAdmin) { player.Reply(GetMessage("NoPermission", player)); return; } if (args.Length < 3) { player.Reply(GetMessage("IncorrectGroupUsage", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0])); return; } if (!permission.GroupExists(args[1])) permission.CreateGroup(args[1], string.Empty, 0); colLower = args[2].ToLower(); ProcessColour(player, player, colLower, args.Skip(3).ToArray(), isMessage, args[1]); } else { if ((!isMessage && !HasNamePerm(player)) || (isMessage && !HasMessagePerm(player))) { player.Reply(GetMessage("NoPermission", player)); return; } if (args.Length < 1) { player.Reply(GetMessage("IncorrectUsage", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0])); return; } colLower = args[0].ToLower(); ProcessColour(player, player, colLower, args.Skip(1).ToArray(), isMessage); } } private void ProcessColoursCommand(IPlayer player, string cmd, string[] args, bool isMessage = false) { if ((!isMessage && !HasNamePerm(player)) || (isMessage && !HasMessagePerm(player))) { player.Reply(GetMessage("NoPermission", player)); return; } var availableCommandsBuilder = _sharedStringBuilder; availableCommandsBuilder.Clear(); if (isMessage) { var commandName = _configuration.MessageColourCommands[0]; availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" #ff6666"); if (CanMessageRandomColour(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" random"); } if (CanMessageGradient(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" gradient #ff6666 #ff6666").AppendLine(); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" gradient #ff6666 #ffff94 #90ee90"); } if (CanMessageSetOthers(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" set playerIdOrName #ff6666"); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" set playerIdOrName gradient #ff6666 #ffff94"); } if (player.IsAdmin) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" group groupName #ff6666"); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" group groupName gradient #ff6666 #ffff94"); } } else { var commandName = _configuration.NameColourCommands[0]; availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" #ff6666"); if (CanNameRandomColour(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" random"); } if (CanNameGradient(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" gradient #ff6666 #ff6666").AppendLine(); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" gradient #ff6666 #ffff94 #90ee90"); } if (CanNameSetOthers(player)) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" set playerIdOrName #ff6666"); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" set playerIdOrName gradient #ff6666 #ffff94"); } if (player.IsAdmin) { availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" group groupName #ff6666"); availableCommandsBuilder.AppendLine().Append('/').Append(commandName).Append(" group groupName gradient #ff6666 #ffff94"); } } var availableCommands = availableCommandsBuilder.ToString(); var additionalInfoBuilder = _sharedStringBuilder; additionalInfoBuilder.Clear(); if (isMessage && _configuration.MessageUseWhitelist) { additionalInfoBuilder.AppendLine("Whitelisted Colours:"); foreach (var colour in _configuration.MessageWhitelistedColoursHex) { additionalInfoBuilder.Append("- ").Append(colour).AppendLine(""); } foreach (var colourRange in _configuration.MessageWhitelistedRangeColoursHex) { additionalInfoBuilder.Append("- From ") .Append(colourRange._from).Append(" to ") .Append(colourRange._to).AppendLine(""); } } else if (isMessage && _configuration.MessageUseBlacklist) { additionalInfoBuilder.AppendLine("Blacklisted Colours:"); foreach (var colour in _configuration.MessageBlockColoursHex) { additionalInfoBuilder.Append("- ").Append(colour).Append(""); } foreach (var colourRange in _configuration.MessageBlacklistedRangeColoursHex) { additionalInfoBuilder.Append("- From ") .Append(colourRange._from).Append(" to ") .Append(colourRange._to).AppendLine(""); } } else if (!isMessage && _configuration.NameUseWhitelist) { additionalInfoBuilder.AppendLine("Whitelisted Colours:"); foreach (var colour in _configuration.NameWhitelistedColoursHex) { additionalInfoBuilder.Append("- ").Append(colour).AppendLine(""); } foreach (var colourRange in _configuration.NameWhitelistedRangeColoursHex) { additionalInfoBuilder.Append("- From ") .Append(colourRange._from).Append(" to ") .Append(colourRange._to).AppendLine(""); } } else if (!isMessage && _configuration.NameUseBlacklist) { additionalInfoBuilder.AppendLine("Blacklisted Colours:"); foreach (var colour in _configuration.NameBlockColoursHex) { additionalInfoBuilder.Append("- ").Append(colour).AppendLine(""); } foreach (var colourRange in _configuration.NameBlacklistedRangeColoursHex) { additionalInfoBuilder.Append("- From ") .Append(colourRange._from).Append(" to ") .Append(colourRange._to).AppendLine(""); } } player.Reply(GetMessage("ColoursInfo", player, availableCommands, additionalInfoBuilder.ToString())); } private string ProcessColourMessage(string message, string colour) => $"" + message + ""; private void ProcessColour(IPlayer player, IPlayer target, string colLower, string[] colours, bool isMessage = false, string groupName = "") { var isGroup = !string.IsNullOrEmpty(groupName); var isCalledOnto = player != target && !isGroup; var key = isGroup ? groupName : target.Id; if (!isGroup && !allColourData.ContainsKey(target.Id)) allColourData.Add(target.Id, new PlayerData()); else if (isGroup && !allColourData.ContainsKey(groupName)) allColourData.Add(groupName, new PlayerData(true)); if (colLower == "gradient") { if ((!isMessage && !CanNameGradient(player)) || (isMessage && !CanMessageGradient(player))) { player.Reply(GetMessage("NoPermissionGradient", player, isMessage ? "message" : "name")); return; } colours = colours.Where(col => isMessage ? IsValidMessage(col, player) : IsValidName(col, player) && IsValidColour(col) && IsInvalidCharacter(col) == null).ToArray(); if (colours.Length < 2) { player.Reply(GetMessage("IncorrectGradientUsageArgs", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0])); return; } string gradientName = ProcessGradient(isMessage ? "Example Message" : target.Name, colours, isMessage, player); if (gradientName.Equals(string.Empty)) { player.Reply(GetMessage("IncorrectGradientUsage", player, isMessage ? _configuration.MessageColourCommands[0] : _configuration.NameColourCommands[0])); return; } if (isMessage) { allColourData[key].MessageColour = string.Empty; allColourData[key].MessageGradientArgs = colours; } else { allColourData[key].NameColour = string.Empty; allColourData[key].NameGradientArgs = colours; if (!isGroup) { if (!cachedData.ContainsKey(key)) cachedData.Add(key, new CachePlayerData(gradientName, GetPrimaryUserGroup(player.Id))); else cachedData[key].NameColourGradient = gradientName; } } if (isGroup) ClearCache(); if (target.IsConnected) target.Reply(GetMessage("GradientChanged", target, GetCorrectLang(isGroup, isMessage, key), gradientName)); if (isCalledOnto) player.Reply(GetMessage("GradientChangedFor", player, target.Name, isMessage ? "message" : "name", gradientName)); return; } if (colLower == "reset" || colLower == "clear" || colLower == "remove") { if (isMessage) { allColourData[key].MessageColour = string.Empty; allColourData[key].MessageGradientArgs = null; } else { allColourData[key].NameColour = string.Empty; allColourData[key].NameGradientArgs = null; if (cachedData.ContainsKey(key)) cachedData.Remove(key); } if (string.IsNullOrEmpty(allColourData[key].NameColour) && allColourData[key].NameGradientArgs == null && string.IsNullOrEmpty(allColourData[key].MessageColour) && allColourData[key].MessageGradientArgs == null) { allColourData.Remove(key); } if (isGroup) { ClearCache(); } if (target.IsConnected) target.Reply(GetMessage("ColourRemoved", target, GetCorrectLang(isGroup, isMessage, key))); if (isCalledOnto) player.Reply(GetMessage("ColourRemovedFor", player, target.Name, isMessage ? "message" : "name")); return; } if (colLower == "random") { if (!isMessage && !CanNameRandomColour(player) || isMessage && !CanMessageRandomColour(player)) { player.Reply(GetMessage("NoPermissionRandom", player, isMessage ? "message" : "name")); return; } colLower = GetRndColour(); if (isMessage) ChangeMessageColour(key, colLower, null); else ChangeNameColour(key, colLower, null); if (isGroup) ClearCache(); if (target.IsConnected) target.Reply(GetMessage("RndColour", target, GetCorrectLang(isGroup, isMessage, key), colLower)); if (isCalledOnto) player.Reply(GetMessage("RndColourFor", player, isMessage ? "Message" : "Name", target.Name, colLower)); return; } if (colLower == "rainbow") { if (isMessage && !HasMessageRainbow(player) || !HasNameRainbow(player)) { player.Reply(GetMessage("NoPermissionRainbow", player)); return; } if (isMessage) ChangeMessageColour(key, string.Empty, _configuration.RainbowColours); else ChangeNameColour(key, string.Empty, _configuration.RainbowColours); if (isGroup) ClearCache(); if (target.IsConnected) target.Reply(GetMessage("RainbowColour", target, GetCorrectLang(isGroup, isMessage, key))); if (isCalledOnto) player.Reply(GetMessage("RainbowColourFor", player, isMessage ? "Message" : "Name", target.Name)); return; } string invalidChar; if ((invalidChar = IsInvalidCharacter(colLower)) != null) { player.Reply(GetMessage("InvalidCharacters", player, invalidChar)); return; } if (!IsValidColour(colLower)) { player.Reply(GetMessage("InvalidColour", player)); return; } if (isMessage ? !IsValidMessage(colLower, player) : !IsValidName(colLower, player)) { player.Reply(GetMessage("InvalidColour", player)); return; } if (isMessage) ChangeMessageColour(key, colLower, null); else ChangeNameColour(key, colLower, null); if (isCalledOnto) player.Reply(GetMessage("ColourChangedFor", player, target.Name, isMessage ? "message" : "name", colLower)); else if (isGroup && target.IsConnected) target.Reply(GetMessage("ColourChangedFor", player, key, isMessage ? "message" : "name", colLower)); else if (target.IsConnected) target.Reply(GetMessage("ColourChanged", target, isMessage ? "Message" : "Name", colLower)); if (isGroup) ClearCache(); } private string ProcessGradient(string name, string[] colourArgs, bool isMessage = false, IPlayer iPlayer = null) { var gradientName = _sharedStringBuilder; gradientName.Clear(); var colours = Pool.GetList(); Color startColour; Color endColour; var nameLength = name.Length; var gradientsSteps = nameLength / (colourArgs.Length - 1); if (gradientsSteps <= 1) { for (var i = 0; i < nameLength; i++) { if (i > colourArgs.Length - 1) ColorUtility.TryParseHtmlString(colourArgs[colourArgs.Length - 1], out startColour); else ColorUtility.TryParseHtmlString(colourArgs[i], out startColour); colours.Add(startColour); } } else { var gradientIterations = nameLength / gradientsSteps; for (var i = 0; i < gradientIterations; i++) { if (colours.Count >= nameLength) continue; if (i > colourArgs.Length - 1) ColorUtility.TryParseHtmlString(colourArgs[colourArgs.Length - 1], out startColour); else ColorUtility.TryParseHtmlString(colourArgs[i], out startColour); if (i >= colourArgs.Length - 1) endColour = startColour; else ColorUtility.TryParseHtmlString(colourArgs[i + 1], out endColour); GetAndAddGradients(startColour, endColour, gradientsSteps, colours); } if (colours.Count < nameLength) { ColorUtility.TryParseHtmlString(colourArgs[colourArgs.Length - 1], out endColour); while (colours.Count < name.Length) colours.Add(endColour); } } for (var i = 0; i < colours.Count; i++) { gradientName.Append("") .Append(name[i]).Append(""); } Pool.FreeList(ref colours); return gradientName.ToString(); } /// /// Gets and adds gradient colours to provided results list /// /// /// /// /// private void GetAndAddGradients(Color start, Color end, int steps, List results) { var stepR = ((end.r - start.r) / (steps - 1)); var stepG = ((end.g - start.g) / (steps - 1)); var stepB = ((end.b - start.b) / (steps - 1)); for (var i = 0; i < steps; i++) results.Add(new Color(start.r + (stepR * i), start.g + (stepG * i), start.b + (stepB * i))); } private readonly Random _random = new Random(); private string GetRndColour() => $"#{_random.Next(0x1000000):X6}"; private string IsInvalidCharacter(string input) => _configuration.BlockedValues.FirstOrDefault(x => input.Contains(x)); private void ClearUpData() { if (_configuration.InactivityRemovalTime == 0) return; var copy = new Dictionary(allColourData); foreach (var colData in copy) { if (colData.Value.LastActive == 0) continue; if (colData.Value.LastActive + (_configuration.InactivityRemovalTime * 86400) < DateTimeOffset.UtcNow.ToUnixTimeSeconds()) allColourData.Remove(colData.Key); } } private void ClearCache() { var cachedCopy = new Dictionary(cachedData); foreach (var cache in cachedCopy) cachedData.Remove(cache.Key); } private void ClearCache(string Id) { if (cachedData.ContainsKey(Id)) cachedData.Remove(Id); } private string GetCorrectLang(bool isGroup, bool isMessage, string key) => isGroup ? isMessage ? $"Group {key} message" : $"Group {key} name" : isMessage ? "Message" : "Name"; private string GetPrimaryUserGroup(string Id) { var groups = permission.GetUserGroups(Id); var primaryGroup = string.Empty; var groupRank = -1; foreach (var group in groups) { if (!allColourData.ContainsKey(group)) continue; var currentGroupRank = permission.GetGroupRank(group); if (currentGroupRank > groupRank) { groupRank = currentGroupRank; primaryGroup = group; } } return primaryGroup; } private ColouredChatMessage FromMessage(IPlayer player, Chat.ChatChannel channel, string message) { PlayerData playerData; if (!allColourData.TryGetValue(player.Id, out playerData) || playerData == null) playerData = new PlayerData(); var colouredNameData = GetColouredName(player, playerData); colouredNameData.ChatChannel = channel; colouredNameData.Message = GetColouredMessage(player, playerData, message); return colouredNameData; } private ColouredChatMessage GetColouredName(IPlayer player, PlayerData playerData) { var playerUserName = player.Name; var playerColour = player.IsAdmin ? "#af5" : "#5af"; var playerColourNonModified = playerColour; CachePlayerData cachedPlayerData; if (!cachedData.TryGetValue(player.Id, out cachedPlayerData) || cachedPlayerData == null) { var gradientName = string.Empty; if (playerData?.NameGradientArgs != null) gradientName = ProcessGradient(player.Name, playerData.NameGradientArgs, false, player); cachedData.Add(player.Id, cachedPlayerData = new CachePlayerData(gradientName, GetPrimaryUserGroup(player.Id))); } if (HasNameShowPerm(player)) { //Gradient Handling if (playerData?.NameGradientArgs != null) playerUserName = cachedPlayerData.NameColourGradient; else if (!string.IsNullOrEmpty(playerData?.NameColour)) playerColour = playerData.NameColour; else if (playerUserName == player.Name && !string.IsNullOrEmpty(cachedPlayerData.NameColourGradient)) playerUserName = cachedPlayerData.NameColourGradient; } if (allColourData.ContainsKey(cachedPlayerData.PrimaryGroup)) { var groupData = allColourData[cachedPlayerData.PrimaryGroup]; if (playerUserName == player.Name && playerColour == playerColourNonModified) { if (groupData?.NameGradientArgs != null) { playerUserName = string.IsNullOrEmpty(cachedPlayerData.NameColourGradient) ? cachedPlayerData.NameColourGradient = ProcessGradient(player.Name, groupData.NameGradientArgs, false, player) : cachedPlayerData.NameColourGradient; } else if (!string.IsNullOrEmpty(groupData?.NameColour)) playerColour = groupData.NameColour; } } return new ColouredChatMessage() { Player = player, Name = playerUserName, Colour = (playerColour == playerColourNonModified && BetterChatIns()) ? string.Empty : playerColour }; } private string GetColouredMessage(IPlayer player, PlayerData playerData, string message) { var playerMessage = message; if (HasNameShowPerm(player)) { if (playerData?.MessageGradientArgs != null) playerMessage = ProcessGradient(message, playerData.MessageGradientArgs, true, player); else if (!string.IsNullOrEmpty(playerData?.MessageColour)) playerMessage = ProcessColourMessage(message, playerData.MessageColour); } //Group Handling var userPrimaryGroup = cachedData[player.Id].PrimaryGroup; if (allColourData.ContainsKey(userPrimaryGroup)) { var groupData = allColourData[userPrimaryGroup]; if (playerMessage == message) { if (groupData?.MessageGradientArgs != null) playerMessage = ProcessGradient(message, groupData.MessageGradientArgs, true, player); else if (!string.IsNullOrEmpty(groupData?.MessageColour)) playerMessage = ProcessColourMessage(message, groupData.MessageColour); } } return playerMessage; } private bool IsInHexRange(string hexCode,string rangeHexCode1, string rangeHexCode2) { Color mainColour; ColorUtility.TryParseHtmlString(hexCode, out mainColour); Color start; ColorUtility.TryParseHtmlString(rangeHexCode1, out start); Color end; ColorUtility.TryParseHtmlString(rangeHexCode2, out end); if ((mainColour.r >= start.r && mainColour.r <= end.r) && (mainColour.g >= start.g && mainColour.g <= end.g) && (mainColour.b >= start.b && mainColour.b <= end.b)) { return true; } return false; } #endregion #region API private string API_GetNameColourHex(IPlayer player) { var playerData = new PlayerData(); if (allColourData.ContainsKey(player.Id)) playerData = allColourData[player.Id]; var colouredData = GetColouredName(player, playerData); if (string.IsNullOrEmpty(colouredData.Colour)) { return player.IsAdmin ? "#af5" : "#5af"; } return colouredData.Colour; } private string API_GetColouredName(IPlayer player) { var playerData = new PlayerData(); if (allColourData.ContainsKey(player.Id)) playerData = allColourData[player.Id]; var colouredData = GetColouredName(player, playerData); if (!string.IsNullOrEmpty(colouredData.Colour)) return $"{player.Name}"; return colouredData.Name; } private string API_GetColouredMessage(IPlayer player, string message) { var playerData = new PlayerData(); if (allColourData.ContainsKey(player.Id)) playerData = allColourData[player.Id]; return GetColouredMessage(player, playerData, message); } private string API_GetColouredChatMessage(IPlayer iPlayer, Chat.ChatChannel channel, string message) { var colouredChatMessage = FromMessage(iPlayer, channel, message); var formattedMessage = colouredChatMessage.GetChatOutput(); if (BetterChatIns()) { Dictionary betterChatMessageData = BetterChat.CallHook("API_GetMessageData", iPlayer, message) as Dictionary; if (!string.IsNullOrEmpty(colouredChatMessage.Name)) betterChatMessageData["Username"] = colouredChatMessage.Name; if (!string.IsNullOrEmpty(colouredChatMessage.Colour)) { ((Dictionary)betterChatMessageData["UsernameSettings"])["Color"] = colouredChatMessage.Colour; } formattedMessage = BetterChat.CallHook("API_GetFormattedMessageFromDict", betterChatMessageData) as string; } return formattedMessage; } public struct ColouredChatMessage { public IPlayer Player; public Chat.ChatChannel ChatChannel; public string Name; public string Colour; public string Message; private static readonly Dictionary _colouredChatDictionary = new Dictionary(); public ColouredChatMessage(IPlayer player, Chat.ChatChannel chatChannel, string name, string colour, string message) { Player = player; ChatChannel = chatChannel; Name = name; Colour = colour; Message = message; } /// /// Gets coloured chat dictionary /// Note: this is a shared dictionary instance over all ColouredChatMessage's, do not store random stuff in here! /// /// public Dictionary GetDictionary() { _colouredChatDictionary[nameof(Player)] = Player; _colouredChatDictionary[nameof(ChatChannel)] = ChatChannel; _colouredChatDictionary[nameof(Name)] = Name; _colouredChatDictionary[nameof(Colour)] = Colour; _colouredChatDictionary[nameof(Message)] = Message; return _colouredChatDictionary; } public static ColouredChatMessage FromDictionary(Dictionary dict) { return new ColouredChatMessage() { Player = dict[nameof(Player)] as IPlayer, ChatChannel = (Chat.ChatChannel)dict[nameof(ChatChannel)], Name = dict[nameof(Name)] as string, Colour = dict[nameof(Colour)] as string, Message = dict[nameof(Message)] as string, }; } public string GetChatOutput() { return string.Format(ChatFormat, $"{Name}", Message); } } #endregion } }