#if defined _smlib_colors_included #endinput #endif #define _smlib_colors_included #include #include #include #define CHATCOLOR_NOSUBJECT -2 #define SMLIB_COLORS_GAMEDATAFILE "smlib_colors.games" enum ChatColorSubjectType { ChatColorSubjectType_none = -3, // Subject/Team colors ChatColorSubjectType_player = -2, ChatColorSubjectType_undefined = -1, ChatColorSubjectType_world = 0 // Anything higher is a specific team } enum struct ChatColorInfo { int ChatColorInfo_Code; int ChatColorInfo_Alternative; bool ChatColorInfo_Supported; ChatColorSubjectType ChatColorInfo_SubjectType; } enum ChatColor { ChatColor_Normal, ChatColor_Orange, ChatColor_Red, ChatColor_RedBlue, ChatColor_Blue, ChatColor_BlueRed, ChatColor_Team, ChatColor_Lightgreen, ChatColor_Gray, ChatColor_Green, ChatColor_Olivegreen, ChatColor_Black, ChatColor_MAXCOLORS } static char chatColorTags[][] = { "N", // Normal "O", // Orange "R", // Red "RB", // Red, Blue "B", // Blue "BR", // Blue, Red "T", // Team "L", // Light green "GRA", // Gray "G", // Green "OG", // Olive green "BLA" // Black }; static char chatColorNames[][] = { "normal", // Normal "orange", // Orange "red", // Red "redblue", // Red, Blue "blue", // Blue "bluered", // Blue, Red "team", // Team "lightgreen", // Light green "gray", // Gray "green", // Green "olivegreen", // Olive green "black" // Black }; static ChatColorInfo chatColorInfo[ChatColor_MAXCOLORS]; static bool checkTeamPlay = false; static ConVar mp_teamplay = null; static bool isSayText2_supported = true; static int chatSubject = CHATCOLOR_NOSUBJECT; /** * Sets the subject (a client) for the chat color parser. * Call this before Color_ParseChatText() or Client_PrintToChat(). * * @param client Client Index/Subject */ stock void Color_ChatSetSubject(int client) { chatSubject = client; } /** * Gets the subject used for the chat color parser. * * @return Client Index/Subject, or CHATCOLOR_NOSUBJECT if none */ stock int Color_ChatGetSubject() { return chatSubject; } /** * Clears the subject used for the chat color parser. * Call this after Color_ParseChatText(). */ stock void Color_ChatClearSubject() { chatSubject = CHATCOLOR_NOSUBJECT; } /** * Parses a chat string and converts all color tags to color codes. * This is a very powerful function that works recursively over the color information * table. The support colors are hardcoded, but can be overriden for each game by * creating the file gamedata/smlib_colors.games.txt. * * @param str Chat String * @param subject Output Buffer * @param size Output Buffer size * @return Returns a value for the subject */ stock int Color_ParseChatText(const char[] str, char[] buffer, int size) { bool inBracket = false; int x, x_buf, x_tag; int subject = CHATCOLOR_NOSUBJECT; char sTag[10] = ""; // This should be able to hold "\x08RRGGBBAA"\0 char colorCode[10] = ""; // This should be able to hold "\x08RRGGBBAA"\0 char currentColor[10] = "\x01"; // Initialize with normal color size--; // Every chat message has to start with a // color code, otherwise it will ignore all colors. buffer[x_buf++] = '\x01'; while (str[x] != '\0') { if (size == x_buf) { break; } char character = str[x++]; if (inBracket) { // We allow up to 9 characters in the tag (#RRGGBBAA) if (character == '}' || x_tag > 9) { inBracket = false; sTag[x_tag] = '\0'; x_tag = 0; if (character == '}') { Color_TagToCode(sTag, subject, colorCode); if (colorCode[0] == '\0') { // We got an unknown tag, ignore this // and forward it to the buffer. // Terminate buffer with \0 so Format can handle it. buffer[x_buf] = '\0'; x_buf = Format(buffer, size, "%s{%s}", buffer, sTag); // We 'r done here continue; } else if (!StrEqual(colorCode, currentColor)) { // If we are already using this color, // we don't need to set it again. // Write the color code to our buffer. // x_buf will be increased by the number of cells written. x_buf += strcopy(buffer[x_buf], size - x_buf, colorCode); // Remember the current color. strcopy(currentColor, sizeof(currentColor), colorCode); } } else { // If the tag character limit exceeds 9, // we have to do something. // Terminate buffer with \0 so Format can handle it. buffer[x_buf] = '\0'; x_buf = Format(buffer, size, "%s{%s%c", buffer, sTag, character); } } else if (character == '{' && !x_tag) { buffer[x_buf++] = '{'; inBracket = false; } else { sTag[x_tag++] = character; } } else if (character == '{') { inBracket = true; } else { buffer[x_buf++] = character; } } // Write remaining text to the buffer, // if we have been inside brackets. if (inBracket) { buffer[x_buf] = '\0'; x_buf = Format(buffer, size, "%s{%s", buffer, sTag); } buffer[x_buf] = '\0'; return subject; } /** * Converts a chat color tag to its code character. * * @param tag Color Tag String. * @param subject Subject variable to pass * @param result The result as character sequence (string). This will be \0 if the tag is unkown. */ stock void Color_TagToCode(const char[] tag, int &subject=-1, char result[10]) { // Check if the tag starts with a '#'. // We will handle it has RGB(A)-color code then. if (tag[0] == '#') { int length_tag = strlen(tag); switch (length_tag - 1) { // #RGB -> \07RRGGBB case 3: { FormatEx( result, sizeof(result), "\x07%c%c%c%c%c%c", tag[1], tag[1], tag[2], tag[2], tag[3], tag[3] ); } // #RGBA -> \08RRGGBBAA case 4: { FormatEx( result, sizeof(result), "\x08%c%c%c%c%c%c%c%c", tag[1], tag[1], tag[2], tag[2], tag[3], tag[3], tag[4], tag[4] ); } // #RRGGBB -> \07RRGGBB case 6: { FormatEx(result, sizeof(result), "\x07%s", tag[1]); } // #RRGGBBAA -> \08RRGGBBAA case 8: { FormatEx(result, sizeof(result), "\x08%s", tag[1]); } default: { result[0] = '\0'; } } return; } else { // Try to handle this string as color name int n = Array_FindString(chatColorTags, sizeof(chatColorTags), tag); // Check if this tag is invalid if (n == -1) { result[0] = '\0'; return; } // Check if the color is actually supported 'n stuff. Color_GetChatColorInfo(n, subject); result[0] = chatColorInfo[n].ChatColorInfo_Code; result[1] = '\0'; } return; } /** * Strips all color control characters in a string. * The Output buffer can be the same as the input buffer. * Original code by Psychonic, thanks. * * @param input Input String. * @param output Output String. * @param size Max Size of the Output string */ stock void Color_StripFromChatText(const char[] input, char[] output, int size) { int x = 0; for (int i=0; input[i] != '\0'; i++) { if (x+1 == size) { break; } char character = input[i]; if (character > 0x08) { output[x++] = character; } } output[x] = '\0'; } /** * Checks the gamename and sets default values. * For example if some colors are supported, or * if a game uses another color code for a specific color. * All those hardcoded default values can be overriden in * smlib's color gamedata file. */ static stock void Color_ChatInitialize() { static bool initialized = false; if (initialized) { return; } initialized = true; // Normal chatColorInfo[ChatColor_Normal].ChatColorInfo_Code = '\x01'; chatColorInfo[ChatColor_Normal].ChatColorInfo_Alternative = -1; /* None */ chatColorInfo[ChatColor_Normal].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Normal].ChatColorInfo_SubjectType = ChatColorSubjectType_none; // Orange chatColorInfo[ChatColor_Orange].ChatColorInfo_Code = '\x01'; chatColorInfo[ChatColor_Orange].ChatColorInfo_Alternative = 0; /* None */ chatColorInfo[ChatColor_Orange].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Orange].ChatColorInfo_SubjectType = ChatColorSubjectType_none; // Red chatColorInfo[ChatColor_Red].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_Red].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Red].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Red].ChatColorInfo_SubjectType = view_as(2); // Red, Blue chatColorInfo[ChatColor_RedBlue].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_RedBlue].ChatColorInfo_Alternative = 4; /* Blue */ chatColorInfo[ChatColor_RedBlue].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_RedBlue].ChatColorInfo_SubjectType = view_as(2); // Blue chatColorInfo[ChatColor_Blue].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_Blue].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Blue].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Blue].ChatColorInfo_SubjectType = view_as(3); // Blue, Red chatColorInfo[ChatColor_BlueRed].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_BlueRed].ChatColorInfo_Alternative = 2; /* Red */ chatColorInfo[ChatColor_BlueRed].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_BlueRed].ChatColorInfo_SubjectType = view_as(3); // Team chatColorInfo[ChatColor_Team].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_Team].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Team].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Team].ChatColorInfo_SubjectType = ChatColorSubjectType_player; // Light green chatColorInfo[ChatColor_Lightgreen].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_Lightgreen].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Lightgreen].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Lightgreen].ChatColorInfo_SubjectType = ChatColorSubjectType_world; // Gray chatColorInfo[ChatColor_Gray].ChatColorInfo_Code = '\x03'; chatColorInfo[ChatColor_Gray].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Gray].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Gray].ChatColorInfo_SubjectType = ChatColorSubjectType_undefined; // Green chatColorInfo[ChatColor_Green].ChatColorInfo_Code = '\x04'; chatColorInfo[ChatColor_Green].ChatColorInfo_Alternative = 0; /* Normal*/ chatColorInfo[ChatColor_Green].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Green].ChatColorInfo_SubjectType = ChatColorSubjectType_none; // Olive green chatColorInfo[ChatColor_Olivegreen].ChatColorInfo_Code = '\x05'; chatColorInfo[ChatColor_Olivegreen].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Olivegreen].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Olivegreen].ChatColorInfo_SubjectType = ChatColorSubjectType_none; // Black chatColorInfo[ChatColor_Black].ChatColorInfo_Code = '\x06'; chatColorInfo[ChatColor_Black].ChatColorInfo_Alternative = 9; /* Green */ chatColorInfo[ChatColor_Black].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Black].ChatColorInfo_SubjectType = ChatColorSubjectType_none; char gameFolderName[PLATFORM_MAX_PATH]; GetGameFolderName(gameFolderName, sizeof(gameFolderName)); chatColorInfo[ChatColor_Black].ChatColorInfo_Supported = false; if (strncmp(gameFolderName, "left4dead", 9, false) != 0 && !StrEqual(gameFolderName, "cstrike", false) && !StrEqual(gameFolderName, "tf", false)) { chatColorInfo[ChatColor_Lightgreen].ChatColorInfo_Supported = false; chatColorInfo[ChatColor_Gray].ChatColorInfo_Supported = false; } if (StrEqual(gameFolderName, "tf", false)) { chatColorInfo[ChatColor_Black].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Gray].ChatColorInfo_Code = '\x01'; chatColorInfo[ChatColor_Gray].ChatColorInfo_SubjectType = ChatColorSubjectType_none; } else if (strncmp(gameFolderName, "left4dead", 9, false) == 0) { chatColorInfo[ChatColor_Red].ChatColorInfo_SubjectType = view_as(3); chatColorInfo[ChatColor_RedBlue].ChatColorInfo_SubjectType = view_as(3); chatColorInfo[ChatColor_Blue].ChatColorInfo_SubjectType = view_as(2); chatColorInfo[ChatColor_BlueRed].ChatColorInfo_SubjectType = view_as(2); chatColorInfo[ChatColor_Orange].ChatColorInfo_Code = '\x04'; chatColorInfo[ChatColor_Green].ChatColorInfo_Code = '\x05'; } else if (StrEqual(gameFolderName, "hl2mp", false)) { chatColorInfo[ChatColor_Red].ChatColorInfo_SubjectType = view_as(3); chatColorInfo[ChatColor_RedBlue].ChatColorInfo_SubjectType = view_as(3); chatColorInfo[ChatColor_Blue].ChatColorInfo_SubjectType = view_as(2); chatColorInfo[ChatColor_BlueRed].ChatColorInfo_SubjectType = view_as(2); chatColorInfo[ChatColor_Black].ChatColorInfo_Supported = true; checkTeamPlay = true; } else if (StrEqual(gameFolderName, "dod", false)) { chatColorInfo[ChatColor_Gray].ChatColorInfo_Code = '\x01'; chatColorInfo[ChatColor_Gray].ChatColorInfo_SubjectType = ChatColorSubjectType_none; chatColorInfo[ChatColor_Black].ChatColorInfo_Supported = true; chatColorInfo[ChatColor_Orange].ChatColorInfo_Supported = false; } if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID) { isSayText2_supported = false; } char path_gamedata[PLATFORM_MAX_PATH]; BuildPath(Path_SM, path_gamedata, sizeof(path_gamedata), "gamedata/%s.txt", SMLIB_COLORS_GAMEDATAFILE); if (FileExists(path_gamedata)) { Handle gamedata = INVALID_HANDLE; if ((gamedata = LoadGameConfigFile(SMLIB_COLORS_GAMEDATAFILE)) != INVALID_HANDLE) { char keyName[32], buffer[6]; for (int i=0; i < sizeof(chatColorNames); i++) { Format(keyName, sizeof(keyName), "%s_code", chatColorNames[i]); if (GameConfGetKeyValue(gamedata, keyName, buffer, sizeof(buffer))) { chatColorInfo[i].ChatColorInfo_Code = StringToInt(buffer); } Format(keyName, sizeof(keyName), "%s_alternative", chatColorNames[i]); if (GameConfGetKeyValue(gamedata, keyName, buffer, sizeof(buffer))) { chatColorInfo[i].ChatColorInfo_Alternative = buffer[0]; } Format(keyName, sizeof(keyName), "%s_supported", chatColorNames[i]); if (GameConfGetKeyValue(gamedata, keyName, buffer, sizeof(buffer))) { chatColorInfo[i].ChatColorInfo_Supported = StrEqual(buffer, "true"); } Format(keyName, sizeof(keyName), "%s_subjecttype", chatColorNames[i]); if (GameConfGetKeyValue(gamedata, keyName, buffer, sizeof(buffer))) { chatColorInfo[i].ChatColorInfo_SubjectType = view_as(StringToInt(buffer)); } } if (GameConfGetKeyValue(gamedata, "checkteamplay", buffer, sizeof(buffer))) { checkTeamPlay = StrEqual(buffer, "true"); } CloseHandle(gamedata); } } mp_teamplay = FindConVar("mp_teamplay"); } /** * Checks if the passed color index is actually supported * for the current game. If not, the index will be overwritten * The color resolving works recursively until a valid color is found. * * @param index * @param subject A client index or CHATCOLOR_NOSUBJECT */ static stock int Color_GetChatColorInfo(int &index, int &subject=CHATCOLOR_NOSUBJECT) { Color_ChatInitialize(); if (index == -1) { index = 0; } while (!chatColorInfo[index].ChatColorInfo_Supported) { int alternative = chatColorInfo[index].ChatColorInfo_Alternative; if (alternative == -1) { index = 0; break; } index = alternative; } if (index == -1) { index = 0; } int newSubject = CHATCOLOR_NOSUBJECT; ChatColorSubjectType type = chatColorInfo[index].ChatColorInfo_SubjectType; switch (type) { case ChatColorSubjectType_none: { } case ChatColorSubjectType_player: { newSubject = chatSubject; } case ChatColorSubjectType_undefined: { newSubject = -1; } case ChatColorSubjectType_world: { newSubject = 0; } default: { if (!checkTeamPlay || mp_teamplay.BoolValue) { if (subject > 0 && subject <= MaxClients) { if (GetClientTeam(subject) == view_as(type)) { newSubject = subject; } } else if (subject == CHATCOLOR_NOSUBJECT) { int client = Team_GetAnyClient(view_as(type)); if (client != -1) { newSubject = client; } } } } } if (type > ChatColorSubjectType_none && ((subject != CHATCOLOR_NOSUBJECT && subject != newSubject) || newSubject == CHATCOLOR_NOSUBJECT || !isSayText2_supported)) { index = chatColorInfo[index].ChatColorInfo_Alternative; newSubject = Color_GetChatColorInfo(index, subject); } // Only set the subject if there is no subject set already. if (subject == CHATCOLOR_NOSUBJECT) { subject = newSubject; } return newSubject; }