shavit-credits/scripting/include/smlib/colors.inc

522 lines
14 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 ChatColorInfo
{
ChatColorInfo_Code,
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
}
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 int chatColorInfo[][ChatColorInfo] =
{
// Code , alternative , Is Supported? Chat color subject type Color name
{ '\x01', -1/* None */ , true, ChatColorSubjectType_none, }, // Normal
{ '\x01', 0 /* None */ , true, ChatColorSubjectType_none, }, // Orange
{ '\x03', 9 /* Green */ , true, view_as<ChatColorSubjectType>(2) }, // Red
{ '\x03', 4 /* Blue */ , true, view_as<ChatColorSubjectType>(2) }, // Red, Blue
{ '\x03', 9 /* Green */ , true, view_as<ChatColorSubjectType>(3) }, // Blue
{ '\x03', 2 /* Red */ , true, view_as<ChatColorSubjectType>(3) }, // Blue, Red
{ '\x03', 9 /* Green */ , true, ChatColorSubjectType_player }, // Team
{ '\x03', 9 /* Green */ , true, ChatColorSubjectType_world }, // Light green
{ '\x03', 9 /* Green */ , true, ChatColorSubjectType_undefined},// GRAy
{ '\x04', 0 /* Normal*/ , true, ChatColorSubjectType_none }, // Green
{ '\x05', 9 /* Green */ , true, ChatColorSubjectType_none }, // Olive green
{ '\x06', 9 /* Green */ , true, ChatColorSubjectType_none } // BLAck
};
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
* @noreturn
*/
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().
*
* @noreturn
*/
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 = 0, x_buf = 0, x_tag = 0, subject = CHATCOLOR_NOSUBJECT;
size--;
char sTag[10] = "", // This should be able to hold "\x08RRGGBBAA"\0
colorCode[10] = "", // This should be able to hold "\x08RRGGBBAA"\0
currentColor[10] = "\x01"; // Initialize with normal color
// 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.
* @noreturn
*/
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';
}
}
}
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';
}
}
/**
* 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
* @noreturn
*/
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.
*
* @noreturn
*/
static stock void Color_ChatInitialize()
{
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
char gameFolderName[32];
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 = null;
if ((gamedata = LoadGameConfigFile(SMLIB_COLORS_GAMEDATAFILE)) != null) {
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");
}
delete 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
* @return new subject client index for team colors
*/
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;
}