diff --git a/addons/sourcemod/scripting/include/multicolors.inc b/addons/sourcemod/scripting/include/multicolors.inc index 9bbfc10..454d1dd 100644 --- a/addons/sourcemod/scripting/include/multicolors.inc +++ b/addons/sourcemod/scripting/include/multicolors.inc @@ -3,7 +3,7 @@ #endif #define _multicolors_included -#define MuCo_VERSION "2.1.2" +#define MuCo_VERSION "2.2.0" #include "multicolors/morecolors" #include "multicolors/colors" @@ -15,6 +15,7 @@ * - Powerlord * - exvel * - Dr. McKay +* - Rushaway / maxime1907 (SRCDSLAB) * * Based on stamm-colors * - https://github.com/popoklopsi/Stamm/blob/master/include/stamm/stamm-colors.inc @@ -108,7 +109,7 @@ stock void CPrintToChatAll(const char[] message, any ...) { } } else { - MC_CheckTrie(); + MC_InitFastColors(); char buffer2[MAX_BUFFER_LENGTH]; @@ -143,16 +144,24 @@ stock void CPrintToChatObservers(int target, const char[] message, any ...) { CFixColors(); } - for (int client = 1; client <= MaxClients; client++) { - if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { - int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); - int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); - - if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { - CPrintToChat(client, buffer); - } - } - } + int observee; + int ObserverMode; + + for (int client = 1; client <= MaxClients; client++) { + if (!IsClientInGame(client) || IsPlayerAlive(client) || IsFakeClient(client)) { + continue; + } + + ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + if (ObserverMode != 4 && ObserverMode != 5) { + continue; + } + + observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + if (observee == target) { + CPrintToChat(client, buffer); + } + } } /** @@ -220,16 +229,23 @@ stock void CPrintToChatObserversEx(int target, const char[] message, any ...) { CFixColors(); } - for (int client = 1; client <= MaxClients; client++) { - if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { - int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); - int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); - - if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { - CPrintToChatEx(client, target, buffer); - } - } - } + int observee; + int ObserverMode; + for (int client = 1; client <= MaxClients; client++) { + if (!IsClientInGame(client) || IsPlayerAlive(client) || IsFakeClient(client)) { + continue; + } + + ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + if (ObserverMode != 4 && ObserverMode != 5) { + continue; + } + + observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + if (observee == target) { + CPrintToChatEx(client, target, buffer); + } + } } /** @@ -453,9 +469,13 @@ stock void CFixColors() { } stock bool IsSource2009() { - return (GetEngineVersion() == Engine_CSS - || GetEngineVersion() == Engine_HL2DM - || GetEngineVersion() == Engine_DODS - || GetEngineVersion() == Engine_TF2 - || GetEngineVersion() == Engine_SDK2013); + static bool checked = false; + static bool result = false; + + if (!checked) { + EngineVersion engine = GetEngineVersion(); + result = (engine == Engine_CSS || engine == Engine_HL2DM || engine == Engine_DODS || engine == Engine_TF2 || engine == Engine_SDK2013); + checked = true; + } + return result; } diff --git a/addons/sourcemod/scripting/include/multicolors/morecolors.inc b/addons/sourcemod/scripting/include/multicolors/morecolors.inc index b1a6285..30bb0b6 100644 --- a/addons/sourcemod/scripting/include/multicolors/morecolors.inc +++ b/addons/sourcemod/scripting/include/multicolors/morecolors.inc @@ -8,11 +8,12 @@ #define _more_colors_included #pragma newdecls optional -#include -#define MORE_COLORS_VERSION "2.0.0-MC" +#define MORE_COLORS_VERSION "3.0.0-MC" #define MC_MAX_MESSAGE_LENGTH 256 #define MAX_BUFFER_LENGTH (MC_MAX_MESSAGE_LENGTH * 4) +#define MAX_COLOR_TAG_LENGTH 32 +#define COLOR_HASH_SIZE 512 // 512 slots for ~172 colors = 34% load factor (optimal performance, minimal collisions) #define MCOLOR_RED 0xFF4040 #define MCOLOR_BLUE 0x99CCFF @@ -22,22 +23,25 @@ #define MC_GAME_DODS 0 bool MC_SkipList[MAXPLAYERS+1]; -StringMap MC_Trie; int MC_TeamColors[][] = {{0xCCCCCC, 0x4D7942, 0xFF4040}}; // Multi-dimensional array for games that don't support SayText2. First index is the game index (as defined by the GAME_ defines), second index is team. 0 = spectator, 1 = team1, 2 = team2 +char g_sColorKeys[COLOR_HASH_SIZE][32]; +char g_sColorValues[COLOR_HASH_SIZE][16]; +static int g_iColorHash[COLOR_HASH_SIZE]; +static int g_iColorCount = 0; static ConVar sm_show_activity; /** * Prints a message to a specific client in the chat area. * Supports color tags. - * + * * @param client Client index. * @param message Message (formatting rules). - * + * * On error/Errors: If the client is not connected an error will be thrown. */ stock void MC_PrintToChat(int client, const char[] message, any ...) { - MC_CheckTrie(); + MC_InitFastColors(); if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %i", client); @@ -61,12 +65,12 @@ stock void MC_PrintToChat(int client, const char[] message, any ...) { /** * Prints a message to all clients in the chat area. * Supports color tags. - * + * * @param client Client index. * @param message Message (formatting rules). */ stock void MC_PrintToChatAll(const char[] message, any ...) { - MC_CheckTrie(); + MC_InitFastColors(); char buffer[MAX_BUFFER_LENGTH], buffer2[MAX_BUFFER_LENGTH]; @@ -88,15 +92,15 @@ stock void MC_PrintToChatAll(const char[] message, any ...) { /** * Prints a message to a specific client in the chat area. * Supports color tags and teamcolor tag. - * + * * @param client Client index. * @param author Author index whose color will be used for teamcolor tag. * @param message Message (formatting rules). - * + * * On error/Errors: If the client or author are not connected an error will be thrown */ stock void MC_PrintToChatEx(int client, int author, const char[] message, any ...) { - MC_CheckTrie(); + MC_InitFastColors(); if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %i", client); @@ -128,11 +132,11 @@ stock void MC_PrintToChatEx(int client, int author, const char[] message, any .. * * @param author Author index whose color will be used for teamcolor tag. * @param message Message (formatting rules). - * + * * On error/Errors: If the author is not connected an error will be thrown. */ stock void MC_PrintToChatAllEx(int author, const char[] message, any ...) { - MC_CheckTrie(); + MC_InitFastColors(); if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %i", author); @@ -162,7 +166,7 @@ stock void MC_PrintToChatAllEx(int author, const char[] message, any ...) { /** * Sends a SayText2 usermessage - * + * * @param client Client to send usermessage to * @param message Message to send */ @@ -218,26 +222,15 @@ stock void MC_SendMessage(int client, const char[] message, int author = 0) { * MC_PrintToChatAll or MC_PrintToChatAllEx. It causes those functions * to skip the specified client when printing the message. * After printing the message, the client will no longer be skipped. - * + * * @param client Client index */ stock void MC_SkipNextClient(int client) { if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %i", client); } - - MC_SkipList[client] = true; -} -/** - * Checks if the colors trie is initialized and initializes it if it's not (used internally) - * - * @return No return - */ -stock void MC_CheckTrie() { - if (MC_Trie == null) { - MC_Trie = MC_InitColorTrie(); - } + MC_SkipList[client] = true; } /** @@ -247,90 +240,105 @@ stock void MC_CheckTrie() { * @param author Optional client index to use for {teamcolor} tags, or 0 for none * @param removeTags Optional boolean value to determine whether we're replacing tags with colors, or just removing tags, used by MC_RemoveTags * @param maxlen Optional value for max buffer length, used by MC_RemoveTags - * + * * On error/Errors: If the client index passed for author is invalid or not in game. */ stock void MC_ReplaceColorCodes(char[] buffer, int author = 0, bool removeTags = false, int maxlen = MAX_BUFFER_LENGTH) { - MC_CheckTrie(); + MC_InitFastColors(); if (!removeTags) { - ReplaceString(buffer, maxlen, "{default}", "\x01", false); - } - else { - ReplaceString(buffer, maxlen, "{default}", "", false); - ReplaceString(buffer, maxlen, "{teamcolor}", "", false); - } + MC_FastReplaceString(buffer, maxlen, "{default}", "\x01"); - if (author != 0 && !removeTags) { - if (author < 0 || author > MaxClients) { - ThrowError("Invalid client index %i", author); - } + if (author != 0) { + if (author < 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } - if (!IsClientInGame(author)) { - ThrowError("Client %i is not in game", author); - } + if (!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } - ReplaceString(buffer, maxlen, "{teamcolor}", "\x03", false); + MC_FastReplaceString(buffer, maxlen, "{teamcolor}", "\x03"); + } + } + else { + MC_FastReplaceString(buffer, maxlen, "{default}", ""); + MC_FastReplaceString(buffer, maxlen, "{teamcolor}", ""); } - int cursor = 0; - int value; - char tag[32], buff[32]; - char[] output = new char[maxlen]; + int outPos = 0; + int len = strlen(buffer); + int i = 0; - strcopy(output, maxlen, buffer); - // Since the string's size is going to be changing, output will hold the replaced string and we'll search buffer + char sOutputBuffer[MAX_BUFFER_LENGTH]; + char sTempTag[32]; - Regex regex = new Regex("{[#a-zA-Z0-9]+}"); - for (int i = 0; i < 1000; i++) { // The RegEx extension is quite flaky, so we have to loop here :/. This loop is supposed to be infinite and broken by return, but conditions have been added to be safe. - if (regex.Match(buffer[cursor]) < 1) { - delete regex; - strcopy(buffer, maxlen, output); - return; - } + while (i < len && outPos < maxlen - 16) { + if (buffer[i] == '{') { + int start = i + 1; + int end = -1; - regex.GetSubString(0, tag, sizeof(tag)); - MC_StrToLower(tag); - cursor = StrContains(buffer[cursor], tag, false) + cursor + 1; - strcopy(buff, sizeof(buff), tag); - ReplaceString(buff, sizeof(buff), "{", ""); - ReplaceString(buff, sizeof(buff), "}", ""); - - if (buff[0] == '#') { - if (strlen(buff) == 7) { - Format(buff, sizeof(buff), "\x07%s", buff[1]); - } - else if (strlen(buff) == 9) { - Format(buff, sizeof(buff), "\x08%s", buff[1]); - } - else { - continue; + // Find closing brace + for (int j = start; j < len && j < start + MAX_COLOR_TAG_LENGTH; j++) { + if (buffer[j] == '}') { + end = j; + break; + } } - if (removeTags) { - ReplaceString(output, maxlen, tag, "", false); - } - else { - ReplaceString(output, maxlen, tag, buff, false); + if (end != -1) { + int tagLen = end - start; + + // Extract tag + int copyLen = (tagLen < 31) ? tagLen : 31; + for (int k = 0; k < copyLen; k++) { + sTempTag[k] = buffer[start + k]; + } + sTempTag[copyLen] = '\0'; + + MC_StrToLower(sTempTag); + + char value[16]; + bool found = false; + + // Handle hex colors + if (sTempTag[0] == '#' && (tagLen == 7 || tagLen == 9)) { + if (tagLen == 7) { + value[0] = '\x07'; + strcopy(value[1], 9, sTempTag[1]); + } else { + value[0] = '\x08'; + strcopy(value[1], 9, sTempTag[1]); + } + found = true; + } else { + found = MC_FastGetColor(sTempTag, value, sizeof(value)); + } + + if (found && !removeTags) { + // Copy color code directly + int valLen = strlen(value); + for (int k = 0; k < valLen && outPos < maxlen - 1; k++) { + sOutputBuffer[outPos++] = value[k]; + } + i = end + 1; + continue; + } else if (removeTags) { + i = end + 1; + continue; + } } } - else if (!MC_Trie.GetValue(buff, value)) { - continue; - } - if (removeTags) { - ReplaceString(output, maxlen, tag, "", false); - } - else { - Format(buff, sizeof(buff), "\x07%06X", value); - ReplaceString(output, maxlen, tag, buff, false); - } + sOutputBuffer[outPos++] = buffer[i++]; } - LogError("[MORE COLORS] Infinite loop broken."); + + sOutputBuffer[outPos] = '\0'; + strcopy(buffer, maxlen, sOutputBuffer); } /** * Gets a part of a string - * + * * @param input String to get the part from * @param output Buffer to write to * @param maxlen Max length of output buffer @@ -352,43 +360,45 @@ stock void CSubString(const char[] input, char[] output, int maxlen, int start, /** * Converts a string to lowercase - * + * * @param buffer String to convert */ stock void MC_StrToLower(char[] buffer) { - int len = strlen(buffer); - for (int i = 0; i < len; i++) { - buffer[i] = CharToLower(buffer[i]); + for (int i = 0; buffer[i]; i++) { + if (buffer[i] >= 'A' && buffer[i] <= 'Z') { + buffer[i] |= 0x20; + } } } /** - * Adds a color to the colors trie + * Adds a color to the colors hash table * * @param name Color name, without braces * @param color Hexadecimal representation of the color (0xRRGGBB) * @return True if color was added successfully, false if a color already exists with that name */ stock bool MC_AddColor(const char[] name, int color) { - MC_CheckTrie(); + MC_InitFastColors(); - int value; + char value[16]; + char newName[64]; + strcopy(newName, sizeof(newName), name); + MC_StrToLower(newName); - if (MC_Trie.GetValue(name, value)) { + if (MC_FastGetColor(newName, value, sizeof(value))) { return false; } - char newName[64]; - strcopy(newName, sizeof(newName), name); - - MC_StrToLower(newName); - MC_Trie.SetValue(newName, color); + char colorValue[16]; + Format(colorValue, sizeof(colorValue), "\x07%06X", color); + MC_FastSetColor(newName, colorValue); return true; } /** * Removes color tags from a message - * + * * @param message Message to remove tags from * @param maxlen Maximum buffer length */ @@ -398,7 +408,7 @@ stock void MC_RemoveTags(char[] message, int maxlen) { /** * Replies to a command with colors - * + * * @param client Client to reply to * @param message Message (formatting rules) */ @@ -422,7 +432,7 @@ stock void MC_ReplyToCommand(int client, const char[] message, any ...) { /** * Replies to a command with colors - * + * * @param client Client to reply to * @param author Client to use for {teamcolor} * @param message Message (formatting rules) @@ -446,12 +456,12 @@ stock void MC_ReplyToCommandEx(int client, int author, const char[] message, any } /** - * Displays usage of an admin command to users depending on the - * setting of the sm_show_activity cvar. + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. * - * This version does not display a message to the originating client - * if used from chat triggers or menus. If manual replies are used - * for these cases, then this function will suffice. Otherwise, + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, * MC_ShowActivity2() is slightly more useful. * Supports color tags. * @@ -663,7 +673,7 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a /** * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. - * All users receive a message in their chat text, except for the originating client, + * All users receive a message in their chat text, except for the originating client, * who receives the message based on the current ReplySource. * Supports color tags. * @@ -705,8 +715,8 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an SetGlobalTransTarget(client); VFormat(szBuffer, sizeof(szBuffer), format, 4); - /* We don't display directly to the console because the chat text - * simply gets added to the console, so we don't want it to print + /* We don't display directly to the console because the chat text + * simply gets added to the console, so we don't want it to print * twice. */ MC_PrintToChatEx(client, client, "%s%s", szTag, szBuffer); @@ -752,7 +762,7 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an bool is_root = id.HasFlag(Admin_Root, Access_Effective); if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { char newsign[MAX_NAME_LENGTH]; - + if ((value & 8) || ((value & 16) && is_root)) { newsign = name; @@ -773,18 +783,21 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an /** * Determines whether a color name exists - * + * * @param color The color name to check * @return True if the color exists, false otherwise */ stock bool CColorExists(const char[] color) { - MC_CheckTrie(); - int temp; - return MC_Trie.GetValue(color, temp); + MC_InitFastColors(); + char temp[16]; + char colorName[64]; + strcopy(colorName, sizeof(colorName), color); + MC_StrToLower(colorName); + return MC_FastGetColor(colorName, temp, sizeof(temp)); } /** - * Returns the hexadecimal representation of a client's team color (will NOT initialize the trie) + * Returns the hexadecimal representation of a client's team color (will initialize the hash table if needed) * * @param client Client to get the team color for * @return Client's team color in hexadecimal, or green if unknown @@ -799,6 +812,8 @@ stock int CGetTeamColor(int client) { ThrowError("Client %i is not in game", client); } + MC_InitFastColors(); + int value; switch(GetClientTeam(client)) { case 1: { @@ -818,181 +833,308 @@ stock int CGetTeamColor(int client) { return value; } -stock StringMap MC_InitColorTrie() { - StringMap hTrie = new StringMap(); - hTrie.SetValue("aliceblue", 0xF0F8FF); - hTrie.SetValue("allies", 0x4D7942); // same as Allies team in DoD:S - hTrie.SetValue("ancient", 0xEB4B4B); // same as Ancient item rarity in Dota 2 - hTrie.SetValue("antiquewhite", 0xFAEBD7); - hTrie.SetValue("aqua", 0x00FFFF); - hTrie.SetValue("aquamarine", 0x7FFFD4); - hTrie.SetValue("arcana", 0xADE55C); // same as Arcana item rarity in Dota 2 - hTrie.SetValue("axis", 0xFF4040); // same as Axis team in DoD:S - hTrie.SetValue("azure", 0x007FFF); - hTrie.SetValue("beige", 0xF5F5DC); - hTrie.SetValue("bisque", 0xFFE4C4); - hTrie.SetValue("black", 0x000000); - hTrie.SetValue("blanchedalmond", 0xFFEBCD); - hTrie.SetValue("blue", 0x99CCFF); // same as BLU/Counter-Terrorist team color - hTrie.SetValue("blueviolet", 0x8A2BE2); - hTrie.SetValue("brown", 0xA52A2A); - hTrie.SetValue("burlywood", 0xDEB887); - hTrie.SetValue("cadetblue", 0x5F9EA0); - hTrie.SetValue("chartreuse", 0x7FFF00); - hTrie.SetValue("chocolate", 0xD2691E); - hTrie.SetValue("collectors", 0xAA0000); // same as Collector's item quality in TF2 - hTrie.SetValue("common", 0xB0C3D9); // same as Common item rarity in Dota 2 - hTrie.SetValue("community", 0x70B04A); // same as Community item quality in TF2 - hTrie.SetValue("coral", 0xFF7F50); - hTrie.SetValue("cornflowerblue", 0x6495ED); - hTrie.SetValue("cornsilk", 0xFFF8DC); - hTrie.SetValue("corrupted", 0xA32C2E); // same as Corrupted item quality in Dota 2 - hTrie.SetValue("crimson", 0xDC143C); - hTrie.SetValue("cyan", 0x00FFFF); - hTrie.SetValue("darkblue", 0x00008B); - hTrie.SetValue("darkcyan", 0x008B8B); - hTrie.SetValue("darkgoldenrod", 0xB8860B); - hTrie.SetValue("darkgray", 0xA9A9A9); - hTrie.SetValue("darkgrey", 0xA9A9A9); - hTrie.SetValue("darkgreen", 0x006400); - hTrie.SetValue("darkkhaki", 0xBDB76B); - hTrie.SetValue("darkmagenta", 0x8B008B); - hTrie.SetValue("darkolivegreen", 0x556B2F); - hTrie.SetValue("darkorange", 0xFF8C00); - hTrie.SetValue("darkorchid", 0x9932CC); - hTrie.SetValue("darkred", 0x8B0000); - hTrie.SetValue("darksalmon", 0xE9967A); - hTrie.SetValue("darkseagreen", 0x8FBC8F); - hTrie.SetValue("darkslateblue", 0x483D8B); - hTrie.SetValue("darkslategray", 0x2F4F4F); - hTrie.SetValue("darkslategrey", 0x2F4F4F); - hTrie.SetValue("darkturquoise", 0x00CED1); - hTrie.SetValue("darkviolet", 0x9400D3); - hTrie.SetValue("deeppink", 0xFF1493); - hTrie.SetValue("deepskyblue", 0x00BFFF); - hTrie.SetValue("dimgray", 0x696969); - hTrie.SetValue("dimgrey", 0x696969); - hTrie.SetValue("dodgerblue", 0x1E90FF); - hTrie.SetValue("exalted", 0xCCCCCD); // same as Exalted item quality in Dota 2 - hTrie.SetValue("firebrick", 0xB22222); - hTrie.SetValue("floralwhite", 0xFFFAF0); - hTrie.SetValue("forestgreen", 0x228B22); - hTrie.SetValue("frozen", 0x4983B3); // same as Frozen item quality in Dota 2 - hTrie.SetValue("fuchsia", 0xFF00FF); - hTrie.SetValue("fullblue", 0x0000FF); - hTrie.SetValue("fullred", 0xFF0000); - hTrie.SetValue("gainsboro", 0xDCDCDC); - hTrie.SetValue("genuine", 0x4D7455); // same as Genuine item quality in TF2 - hTrie.SetValue("ghostwhite", 0xF8F8FF); - hTrie.SetValue("gold", 0xFFD700); - hTrie.SetValue("goldenrod", 0xDAA520); - hTrie.SetValue("gray", 0xCCCCCC); // same as spectator team color - hTrie.SetValue("grey", 0xCCCCCC); - hTrie.SetValue("green", 0x3EFF3E); - hTrie.SetValue("greenyellow", 0xADFF2F); - hTrie.SetValue("haunted", 0x38F3AB); // same as Haunted item quality in TF2 - hTrie.SetValue("honeydew", 0xF0FFF0); - hTrie.SetValue("hotpink", 0xFF69B4); - hTrie.SetValue("immortal", 0xE4AE33); // same as Immortal item rarity in Dota 2 - hTrie.SetValue("indianred", 0xCD5C5C); - hTrie.SetValue("indigo", 0x4B0082); - hTrie.SetValue("ivory", 0xFFFFF0); - hTrie.SetValue("khaki", 0xF0E68C); - hTrie.SetValue("lavender", 0xE6E6FA); - hTrie.SetValue("lavenderblush", 0xFFF0F5); - hTrie.SetValue("lawngreen", 0x7CFC00); - hTrie.SetValue("legendary", 0xD32CE6); // same as Legendary item rarity in Dota 2 - hTrie.SetValue("lemonchiffon", 0xFFFACD); - hTrie.SetValue("lightblue", 0xADD8E6); - hTrie.SetValue("lightcoral", 0xF08080); - hTrie.SetValue("lightcyan", 0xE0FFFF); - hTrie.SetValue("lightgoldenrodyellow", 0xFAFAD2); - hTrie.SetValue("lightgray", 0xD3D3D3); - hTrie.SetValue("lightgrey", 0xD3D3D3); - hTrie.SetValue("lightgreen", 0x99FF99); - hTrie.SetValue("lightpink", 0xFFB6C1); - hTrie.SetValue("lightsalmon", 0xFFA07A); - hTrie.SetValue("lightseagreen", 0x20B2AA); - hTrie.SetValue("lightskyblue", 0x87CEFA); - hTrie.SetValue("lightslategray", 0x778899); - hTrie.SetValue("lightslategrey", 0x778899); - hTrie.SetValue("lightsteelblue", 0xB0C4DE); - hTrie.SetValue("lightyellow", 0xFFFFE0); - hTrie.SetValue("lime", 0x00FF00); - hTrie.SetValue("limegreen", 0x32CD32); - hTrie.SetValue("linen", 0xFAF0E6); - hTrie.SetValue("magenta", 0xFF00FF); - hTrie.SetValue("maroon", 0x800000); - hTrie.SetValue("mediumaquamarine", 0x66CDAA); - hTrie.SetValue("mediumblue", 0x0000CD); - hTrie.SetValue("mediumorchid", 0xBA55D3); - hTrie.SetValue("mediumpurple", 0x9370D8); - hTrie.SetValue("mediumseagreen", 0x3CB371); - hTrie.SetValue("mediumslateblue", 0x7B68EE); - hTrie.SetValue("mediumspringgreen", 0x00FA9A); - hTrie.SetValue("mediumturquoise", 0x48D1CC); - hTrie.SetValue("mediumvioletred", 0xC71585); - hTrie.SetValue("midnightblue", 0x191970); - hTrie.SetValue("mintcream", 0xF5FFFA); - hTrie.SetValue("mistyrose", 0xFFE4E1); - hTrie.SetValue("moccasin", 0xFFE4B5); - hTrie.SetValue("mythical", 0x8847FF); // same as Mythical item rarity in Dota 2 - hTrie.SetValue("navajowhite", 0xFFDEAD); - hTrie.SetValue("navy", 0x000080); - hTrie.SetValue("normal", 0xB2B2B2); // same as Normal item quality in TF2 - hTrie.SetValue("oldlace", 0xFDF5E6); - hTrie.SetValue("olive", 0x9EC34F); - hTrie.SetValue("olivedrab", 0x6B8E23); - hTrie.SetValue("orange", 0xFFA500); - hTrie.SetValue("orangered", 0xFF4500); - hTrie.SetValue("orchid", 0xDA70D6); - hTrie.SetValue("palegoldenrod", 0xEEE8AA); - hTrie.SetValue("palegreen", 0x98FB98); - hTrie.SetValue("paleturquoise", 0xAFEEEE); - hTrie.SetValue("palevioletred", 0xD87093); - hTrie.SetValue("papayawhip", 0xFFEFD5); - hTrie.SetValue("peachpuff", 0xFFDAB9); - hTrie.SetValue("peru", 0xCD853F); - hTrie.SetValue("pink", 0xFFC0CB); - hTrie.SetValue("plum", 0xDDA0DD); - hTrie.SetValue("powderblue", 0xB0E0E6); - hTrie.SetValue("purple", 0x800080); - hTrie.SetValue("rare", 0x4B69FF); // same as Rare item rarity in Dota 2 - hTrie.SetValue("red", 0xFF4040); // same as RED/Terrorist team color - hTrie.SetValue("rosybrown", 0xBC8F8F); - hTrie.SetValue("royalblue", 0x4169E1); - hTrie.SetValue("saddlebrown", 0x8B4513); - hTrie.SetValue("salmon", 0xFA8072); - hTrie.SetValue("sandybrown", 0xF4A460); - hTrie.SetValue("seagreen", 0x2E8B57); - hTrie.SetValue("seashell", 0xFFF5EE); - hTrie.SetValue("selfmade", 0x70B04A); // same as Self-Made item quality in TF2 - hTrie.SetValue("sienna", 0xA0522D); - hTrie.SetValue("silver", 0xC0C0C0); - hTrie.SetValue("skyblue", 0x87CEEB); - hTrie.SetValue("slateblue", 0x6A5ACD); - hTrie.SetValue("slategray", 0x708090); - hTrie.SetValue("slategrey", 0x708090); - hTrie.SetValue("snow", 0xFFFAFA); - hTrie.SetValue("springgreen", 0x00FF7F); - hTrie.SetValue("steelblue", 0x4682B4); - hTrie.SetValue("strange", 0xCF6A32); // same as Strange item quality in TF2 - hTrie.SetValue("tan", 0xD2B48C); - hTrie.SetValue("teal", 0x008080); - hTrie.SetValue("thistle", 0xD8BFD8); - hTrie.SetValue("tomato", 0xFF6347); - hTrie.SetValue("turquoise", 0x40E0D0); - hTrie.SetValue("uncommon", 0xB0C3D9); // same as Uncommon item rarity in Dota 2 - hTrie.SetValue("unique", 0xFFD700); // same as Unique item quality in TF2 - hTrie.SetValue("unusual", 0x8650AC); // same as Unusual item quality in TF2 - hTrie.SetValue("valve", 0xA50F79); // same as Valve item quality in TF2 - hTrie.SetValue("vintage", 0x476291); // same as Vintage item quality in TF2 - hTrie.SetValue("violet", 0xEE82EE); - hTrie.SetValue("wheat", 0xF5DEB3); - hTrie.SetValue("white", 0xFFFFFF); - hTrie.SetValue("whitesmoke", 0xF5F5F5); - hTrie.SetValue("yellow", 0xFFFF00); - hTrie.SetValue("yellowgreen", 0x9ACD32); - - return hTrie; +stock void MC_InitFastColors() { + if (g_iColorCount > 0) + return; + + MC_FastSetColor("aliceblue", "\x07F0F8FF"); + MC_FastSetColor("allies", "\x074D7942"); // same as Allies team in DoD:S + MC_FastSetColor("ancient", "\x07EB4B4B"); // same as Ancient item rarity in Dota 2 + MC_FastSetColor("antiquewhite", "\x07FAEBD7"); + MC_FastSetColor("aqua", "\x0700FFFF"); + MC_FastSetColor("aquamarine", "\x077FFFD4"); + MC_FastSetColor("arcana", "\x07ADE55C"); // same as Arcana item rarity in Dota 2 + MC_FastSetColor("axis", "\x07FF4040"); // same as Axis team in DoD:S + MC_FastSetColor("azure", "\x07007FFF"); + MC_FastSetColor("beige", "\x07F5F5DC"); + MC_FastSetColor("bisque", "\x07FFE4C4"); + MC_FastSetColor("black", "\x07000000"); + MC_FastSetColor("blanchedalmond", "\x07FFEBCD"); + MC_FastSetColor("blue", "\x0799CCFF"); // same as BLU/Counter-Terrorist team color + MC_FastSetColor("blueviolet", "\x078A2BE2"); + MC_FastSetColor("brown", "\x07A52A2A"); + MC_FastSetColor("burlywood", "\x07DEB887"); + MC_FastSetColor("cadetblue", "\x075F9EA0"); + MC_FastSetColor("chartreuse", "\x077FFF00"); + MC_FastSetColor("chocolate", "\x07D2691E"); + MC_FastSetColor("collectors", "\x07AA0000"); // same as Collector's item quality in TF2 + MC_FastSetColor("common", "\x07B0C3D9"); // same as Common item rarity in Dota 2 + MC_FastSetColor("community", "\x0770B04A"); // same as Community item quality in TF2 + MC_FastSetColor("coral", "\x07FF7F50"); + MC_FastSetColor("cornflowerblue", "\x076495ED"); + MC_FastSetColor("cornsilk", "\x07FFF8DC"); + MC_FastSetColor("corrupted", "\x07A32C2E"); // same as Corrupted item quality in Dota 2 + MC_FastSetColor("crimson", "\x07DC143C"); + MC_FastSetColor("cyan", "\x0700FFFF"); + MC_FastSetColor("darkblue", "\x0700008B"); + MC_FastSetColor("darkcyan", "\x07008B8B"); + MC_FastSetColor("darkgoldenrod", "\x07B8860B"); + MC_FastSetColor("darkgray", "\x07A9A9A9"); + MC_FastSetColor("darkgrey", "\x07A9A9A9"); + MC_FastSetColor("darkgreen", "\x07006400"); + MC_FastSetColor("darkkhaki", "\x07BDB76B"); + MC_FastSetColor("darkmagenta", "\x078B008B"); + MC_FastSetColor("darkolivegreen", "\x07556B2F"); + MC_FastSetColor("darkorange", "\x07FF8C00"); + MC_FastSetColor("darkorchid", "\x079932CC"); + MC_FastSetColor("darkred", "\x078B0000"); + MC_FastSetColor("darksalmon", "\x07E9967A"); + MC_FastSetColor("darkseagreen", "\x078FBC8F"); + MC_FastSetColor("darkslateblue", "\x07483D8B"); + MC_FastSetColor("darkslategray", "\x072F4F4F"); + MC_FastSetColor("darkslategrey", "\x072F4F4F"); + MC_FastSetColor("darkturquoise", "\x0700CED1"); + MC_FastSetColor("darkviolet", "\x079400D3"); + MC_FastSetColor("deeppink", "\x07FF1493"); + MC_FastSetColor("deepskyblue", "\x0700BFFF"); + MC_FastSetColor("dimgray", "\x07696969"); + MC_FastSetColor("dimgrey", "\x07696969"); + MC_FastSetColor("dodgerblue", "\x071E90FF"); + MC_FastSetColor("exalted", "\x07CCCCCD"); // same as Exalted item quality in Dota 2 + MC_FastSetColor("firebrick", "\x07B22222"); + MC_FastSetColor("floralwhite", "\x07FFFAF0"); + MC_FastSetColor("forestgreen", "\x07228B22"); + MC_FastSetColor("frozen", "\x074983B3"); // same as Frozen item quality in Dota 2 + MC_FastSetColor("fuchsia", "\x07FF00FF"); + MC_FastSetColor("fullblue", "\x070000FF"); + MC_FastSetColor("fullred", "\x07FF0000"); + MC_FastSetColor("gainsboro", "\x07DCDCDC"); + MC_FastSetColor("genuine", "\x074D7455"); // same as Genuine item quality in TF2 + MC_FastSetColor("ghostwhite", "\x07F8F8FF"); + MC_FastSetColor("gold", "\x07FFD700"); + MC_FastSetColor("goldenrod", "\x07DAA520"); + MC_FastSetColor("gray", "\x07CCCCCC"); // same as spectator team color + MC_FastSetColor("grey", "\x07CCCCCC"); + MC_FastSetColor("green", "\x073EFF3E"); + MC_FastSetColor("greenyellow", "\x07ADFF2F"); + MC_FastSetColor("haunted", "\x0738F3AB"); // same as Haunted item quality in TF2 + MC_FastSetColor("honeydew", "\x07F0FFF0"); + MC_FastSetColor("hotpink", "\x07FF69B4"); + MC_FastSetColor("immortal", "\x07E4AE33"); // same as Immortal item rarity in Dota 2 + MC_FastSetColor("indianred", "\x07CD5C5C"); + MC_FastSetColor("indigo", "\x074B0082"); + MC_FastSetColor("ivory", "\x07FFFFF0"); + MC_FastSetColor("khaki", "\x07F0E68C"); + MC_FastSetColor("lavender", "\x07E6E6FA"); + MC_FastSetColor("lavenderblush", "\x07FFF0F5"); + MC_FastSetColor("lawngreen", "\x077CFC00"); + MC_FastSetColor("legendary", "\x07D32CE6"); // same as Legendary item rarity in Dota 2 + MC_FastSetColor("lemonchiffon", "\x07FFFACD"); + MC_FastSetColor("lightblue", "\x07ADD8E6"); + MC_FastSetColor("lightcoral", "\x07F08080"); + MC_FastSetColor("lightcyan", "\x07E0FFFF"); + MC_FastSetColor("lightgoldenrodyellow", "\x07FAFAD2"); + MC_FastSetColor("lightgray", "\x07D3D3D3"); + MC_FastSetColor("lightgrey", "\x07D3D3D3"); + MC_FastSetColor("lightgreen", "\x0799FF99"); + MC_FastSetColor("lightpink", "\x07FFB6C1"); + MC_FastSetColor("lightsalmon", "\x07FFA07A"); + MC_FastSetColor("lightseagreen", "\x0720B2AA"); + MC_FastSetColor("lightskyblue", "\x0787CEFA"); + MC_FastSetColor("lightslategray", "\x07778899"); + MC_FastSetColor("lightslategrey", "\x07778899"); + MC_FastSetColor("lightsteelblue", "\x07B0C4DE"); + MC_FastSetColor("lightyellow", "\x07FFFFE0"); + MC_FastSetColor("lime", "\x0700FF00"); + MC_FastSetColor("limegreen", "\x0732CD32"); + MC_FastSetColor("linen", "\x07FAF0E6"); + MC_FastSetColor("magenta", "\x07FF00FF"); + MC_FastSetColor("maroon", "\x07800000"); + MC_FastSetColor("mediumaquamarine", "\x0766CDAA"); + MC_FastSetColor("mediumblue", "\x070000CD"); + MC_FastSetColor("mediumorchid", "\x07BA55D3"); + MC_FastSetColor("mediumpurple", "\x079370D8"); + MC_FastSetColor("mediumseagreen", "\x073CB371"); + MC_FastSetColor("mediumslateblue", "\x077B68EE"); + MC_FastSetColor("mediumspringgreen", "\x0700FA9A"); + MC_FastSetColor("mediumturquoise", "\x0748D1CC"); + MC_FastSetColor("mediumvioletred", "\x07C71585"); + MC_FastSetColor("midnightblue", "\x07191970"); + MC_FastSetColor("mintcream", "\x07F5FFFA"); + MC_FastSetColor("mistyrose", "\x07FFE4E1"); + MC_FastSetColor("moccasin", "\x07FFE4B5"); + MC_FastSetColor("mythical", "\x078847FF"); // same as Mythical item rarity in Dota 2 + MC_FastSetColor("navajowhite", "\x07FFDEAD"); + MC_FastSetColor("navy", "\x07000080"); + MC_FastSetColor("normal", "\x07B2B2B2"); // same as Normal item quality in TF2 + MC_FastSetColor("oldlace", "\x07FDF5E6"); + MC_FastSetColor("olive", "\x079EC34F"); + MC_FastSetColor("olivedrab", "\x076B8E23"); + MC_FastSetColor("orange", "\x07FFA500"); + MC_FastSetColor("orangered", "\x07FF4500"); + MC_FastSetColor("orchid", "\x07DA70D6"); + MC_FastSetColor("palegoldenrod", "\x07EEE8AA"); + MC_FastSetColor("palegreen", "\x0798FB98"); + MC_FastSetColor("paleturquoise", "\x07AFEEEE"); + MC_FastSetColor("palevioletred", "\x07D87093"); + MC_FastSetColor("papayawhip", "\x07FFEFD5"); + MC_FastSetColor("peachpuff", "\x07FFDAB9"); + MC_FastSetColor("peru", "\x07CD853F"); + MC_FastSetColor("pink", "\x07FFC0CB"); + MC_FastSetColor("plum", "\x07DDA0DD"); + MC_FastSetColor("powderblue", "\x07B0E0E6"); + MC_FastSetColor("purple", "\x07800080"); + MC_FastSetColor("rare", "\x074B69FF"); // same as Rare item rarity in Dota 2 + MC_FastSetColor("red", "\x07FF4040"); // same as RED/Terrorist team color + MC_FastSetColor("rosybrown", "\x07BC8F8F"); + MC_FastSetColor("royalblue", "\x074169E1"); + MC_FastSetColor("saddlebrown", "\x078B4513"); + MC_FastSetColor("salmon", "\x07FA8072"); + MC_FastSetColor("sandybrown", "\x07F4A460"); + MC_FastSetColor("seagreen", "\x072E8B57"); + MC_FastSetColor("seashell", "\x07FFF5EE"); + MC_FastSetColor("selfmade", "\x0770B04A"); // same as Self-Made item quality in TF2 + MC_FastSetColor("sienna", "\x07A0522D"); + MC_FastSetColor("silver", "\x07C0C0C0"); + MC_FastSetColor("skyblue", "\x0787CEEB"); + MC_FastSetColor("slateblue", "\x076A5ACD"); + MC_FastSetColor("slategray", "\x07708090"); + MC_FastSetColor("slategrey", "\x07708090"); + MC_FastSetColor("snow", "\x07FFFAFA"); + MC_FastSetColor("springgreen", "\x0700FF7F"); + MC_FastSetColor("steelblue", "\x074682B4"); + MC_FastSetColor("strange", "\x07CF6A32"); // same as Strange item quality in TF2 + MC_FastSetColor("tan", "\x07D2B48C"); + MC_FastSetColor("teal", "\x07008080"); + MC_FastSetColor("thistle", "\x07D8BFD8"); + MC_FastSetColor("tomato", "\x07FF6347"); + MC_FastSetColor("turquoise", "\x0740E0D0"); + MC_FastSetColor("uncommon", "\x07B0C3D9"); // same as Uncommon item rarity in Dota 2 + MC_FastSetColor("unique", "\x07FFD700"); // same as Unique item quality in TF2 + MC_FastSetColor("unusual", "\x078650AC"); // same as Unusual item quality in TF2 + MC_FastSetColor("valve", "\x07A50F79"); // same as Valve item quality in TF2 + MC_FastSetColor("vintage", "\x07476291"); // same as Vintage item quality in TF2 + MC_FastSetColor("violet", "\x07EE82EE"); + MC_FastSetColor("wheat", "\x07F5DEB3"); + MC_FastSetColor("white", "\x07FFFFFF"); + MC_FastSetColor("whitesmoke", "\x07F5F5F5"); + MC_FastSetColor("yellow", "\x07FFFF00"); + MC_FastSetColor("yellowgreen", "\x079ACD32"); +} + +/** + * Computes FNV-1a (Fowler-Noll-Vo) hash for a string + * FNV-1a provides better avalanche characteristics than FNV-1 + * and is widely used in hash tables for its speed and distribution quality + * 2166136261 is the FNV-1a 32-bit offset basis + * 16777619 is the FNV-1a 32-bit prime multiplier + * + * @param str String to hash + * @return 32-bit hash value + */ +stock int MC_FastHash(const char[] str) { + int hash = 2166136261; + for (int i = 0; str[i]; i++) { + hash ^= str[i]; + hash *= 16777619; + } + return hash & (COLOR_HASH_SIZE - 1); +} + +/** + * Inserts or updates a color in the hash table using linear probing + * Updates existing keys, adds new ones, or silently fails if table is full + * Uses FNV-1a hash with cycle detection to prevent infinite loops + * Silent fail occurs only if: empty key/value, 256+ unique colors, or hash table completely full + */ +stock void MC_FastSetColor(const char[] key, const char[] value) { + if (!key[0] || !value[0] || g_iColorCount >= COLOR_HASH_SIZE) + return; + + int hash = MC_FastHash(key); + int originalHash = hash; + int probeCount = 0; + + // Linear probing with cycle detection + do { + int existingIndex = g_iColorHash[hash]; + + // Found empty slot + if (existingIndex == 0) { + strcopy(g_sColorKeys[g_iColorCount], sizeof(g_sColorKeys[]), key); + strcopy(g_sColorValues[g_iColorCount], sizeof(g_sColorValues[]), value); + g_iColorHash[hash] = g_iColorCount + 1; + g_iColorCount++; + return; + } + + // Check if key already exists (update case) + existingIndex--; + if (strcmp(g_sColorKeys[existingIndex], key, false) == 0) { + strcopy(g_sColorValues[existingIndex], sizeof(g_sColorValues[]), value); + return; + } + + hash = (hash + 1) & (COLOR_HASH_SIZE - 1); + probeCount++; + + // Prevent infinite loop if table is full + if (probeCount >= COLOR_HASH_SIZE) { + return; // Table is completely full, cannot insert + } + } while (hash != originalHash); +} + +stock bool MC_FastGetColor(const char[] key, char[] output, int maxlen) { + if (!key[0]) + return false; + + int hash = MC_FastHash(key); + int originalHash = hash; + + do { + int index = g_iColorHash[hash]; + if (index == 0) + return false; + + index--; + if (strcmp(g_sColorKeys[index], key, false) == 0) { + strcopy(output, maxlen, g_sColorValues[index]); + return true; + } + + hash = (hash + 1) & (COLOR_HASH_SIZE - 1); + } while (hash != originalHash); + + return false; +} + +stock void MC_FastReplaceString(char[] text, int maxlen, const char[] search, const char[] replace) { + if (!search[0] || !text[0]) + return; + + char localBuffer[MAX_BUFFER_LENGTH]; + + int textLen = strlen(text); + int searchLen = strlen(search); + int replaceLen = strlen(replace); + int tempPos = 0; + int i = 0; + + while (i < textLen && tempPos < maxlen - 1) { + if (i <= textLen - searchLen) { + // Manual character comparison instead of strncmp + bool match = true; + for (int j = 0; j < searchLen; j++) { + if (text[i + j] != search[j]) { + match = false; + break; + } + } + + if (match) { + // Found match, copy replacement + for (int k = 0; k < replaceLen && tempPos < maxlen - 1; k++) { + localBuffer[tempPos++] = replace[k]; + } + i += searchLen; + continue; + } + } + + localBuffer[tempPos++] = text[i++]; + } + + localBuffer[tempPos] = '\0'; + strcopy(text, maxlen, localBuffer); }