sourcemod-plugins/scripting/include/smlib/colors.inc
2022-07-06 15:29:19 -05:00

575 lines
17 KiB
SourcePawn

#if defined _smlib_colors_included
#endinput
#endif
#define _smlib_colors_included
#include <sourcemod>
#include <smlib/arrays>
#include <smlib/teams>
#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<ChatColorSubjectType>(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<ChatColorSubjectType>(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<ChatColorSubjectType>(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<ChatColorSubjectType>(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<ChatColorSubjectType>(3);
chatColorInfo[ChatColor_RedBlue].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(3);
chatColorInfo[ChatColor_Blue].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(2);
chatColorInfo[ChatColor_BlueRed].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(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<ChatColorSubjectType>(3);
chatColorInfo[ChatColor_RedBlue].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(3);
chatColorInfo[ChatColor_Blue].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(2);
chatColorInfo[ChatColor_BlueRed].ChatColorInfo_SubjectType = view_as<ChatColorSubjectType>(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<ChatColorSubjectType>(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<int>(type)) {
newSubject = subject;
}
}
else if (subject == CHATCOLOR_NOSUBJECT) {
int client = Team_GetAnyClient(view_as<int>(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;
}