/* * shavit's Timer - Chat * by: shavit * * This file is part of shavit's Timer. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * */ #include #include #undef REQUIRE_PLUGIN #include #include #include #define USES_CHAT_COLORS #include #pragma newdecls required #pragma semicolon 1 #pragma dynamic 131072 // cache float gF_LastMessage[MAXPLAYERS+1]; char gS_Cached_Prefix[MAXPLAYERS+1][32]; char gS_Cached_Name[MAXPLAYERS+1][MAX_NAME_LENGTH*2]; char gS_Cached_Message[MAXPLAYERS+1][255]; char gS_Cached_ClanTag[MAXPLAYERS+1][32]; StringMap gSM_Custom_Prefix = null; StringMap gSM_Custom_Name = null; StringMap gSM_Custom_Message = null; StringMap gSM_Custom_ClanTag = null; int gI_TotalChatRanks = 0; int gI_UnassignedTitle = -1; int gI_UnrankedTitle = -1; Dynamic gD_ChatRanks[64]; // limited to 64 chat ranks right now, i really don't think there's a need for more. // modules bool gB_BaseComm = false; bool gB_RTLer = false; bool gB_ChatProcessor = false; // game-related EngineVersion gEV_Type = Engine_Unknown; public Plugin myinfo = { name = "[shavit] Chat", author = "shavit", description = "Chat handler for shavit's bhop timer.", version = SHAVIT_VERSION, url = "https://github.com/shavitush/bhoptimer" } public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { // natives CreateNative("Shavit_FormatChat", Native_FormatChat); // registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins RegPluginLibrary("shavit-chat"); return APLRes_Success; } public void OnAllPluginsLoaded() { if(!LibraryExists("shavit-rankings")) { SetFailState("shavit-rankings is required for the plugin to work."); } // modules gB_BaseComm = LibraryExists("basecomm"); gB_RTLer = LibraryExists("rtler"); gB_ChatProcessor = LibraryExists("chat-processor"); } public void OnPluginStart() { LoadTranslations("shavit-chat.phrases"); // game specific gEV_Type = GetEngineVersion(); // commands RegAdminCmd("sm_reloadchat", Command_ReloadChat, ADMFLAG_ROOT, "Reload chat config."); RegConsoleCmd("sm_chatranks", Command_ChatRanks, "Shows a list of all the possible chat ranks."); RegConsoleCmd("sm_ranks", Command_ChatRanks, "Shows a list of all the possible chat ranks. Alias for sm_chatranks."); // hooks HookEvent("player_spawn", Player_Spawn); } public void OnPluginEnd() { ResetCache(); } public void OnMapStart() { LoadConfig(); } public void OnClientPutInServer(int client) { gF_LastMessage[client] = GetEngineTime(); if(gI_TotalChatRanks >= 1 && gD_ChatRanks[0].IsValid) { gD_ChatRanks[0].GetString("prefix", gS_Cached_Prefix[client], 32); gD_ChatRanks[0].GetString("name", gS_Cached_Name[client], MAX_NAME_LENGTH*2); gD_ChatRanks[0].GetString("message", gS_Cached_Message[client], 255); gD_ChatRanks[0].GetString("clantag", gS_Cached_ClanTag[client], 32); UpdateClanTag(client); } } public void OnClientSettingsChanged(int client) { UpdateClanTag(client); } void UpdateClanTag(int client) { if(IsValidClient(client) && strlen(gS_Cached_ClanTag[client]) > 0) { CS_SetClientClanTag(client, gS_Cached_ClanTag[client]); } } public void OnClientAuthorized(int client, const char[] auth) { LoadChatCache(client); } public void OnLibraryAdded(const char[] name) { if(StrEqual(name, "basecomm")) { gB_BaseComm = true; } else if(StrEqual(name, "rtler")) { gB_RTLer = true; } else if(StrEqual(name, "chat-processor")) { gB_ChatProcessor = true; } } public void OnLibraryRemoved(const char[] name) { if(StrEqual(name, "basecomm")) { gB_BaseComm = false; } else if(StrEqual(name, "rtler")) { gB_RTLer = false; } else if(StrEqual(name, "chat-processor")) { gB_ChatProcessor = false; } } public void Shavit_OnRankUpdated(int client) { LoadChatCache(client); } void LoadChatCache(int client) { // assign rank properties int iRank = Shavit_GetRank(client); int iTitle = (iRank == -1)? gI_UnassignedTitle:gI_UnrankedTitle; if(iRank <= 0) { gD_ChatRanks[iTitle].GetString("prefix", gS_Cached_Prefix[client], 32); gD_ChatRanks[iTitle].GetString("name", gS_Cached_Name[client], MAX_NAME_LENGTH*2); gD_ChatRanks[iTitle].GetString("message", gS_Cached_Message[client], 255); gD_ChatRanks[iTitle].GetString("clantag", gS_Cached_ClanTag[client], 255); } else { for(int i = 0; i < gI_TotalChatRanks; i++) { if(gD_ChatRanks[i].IsValid) { int iFrom = gD_ChatRanks[i].GetInt("rank_from"); int iTo = gD_ChatRanks[i].GetInt("rank_to"); if(iRank < iFrom || (iRank > iTo && iTo != -3)) { continue; } gD_ChatRanks[i].GetString("prefix", gS_Cached_Prefix[client], 32); gD_ChatRanks[i].GetString("name", gS_Cached_Name[client], MAX_NAME_LENGTH*2); gD_ChatRanks[i].GetString("message", gS_Cached_Message[client], 255); gD_ChatRanks[i].GetString("clantag", gS_Cached_ClanTag[client], 255); } } } char[] sAuthID = new char[32]; if(GetClientAuthId(client, AuthId_Steam3, sAuthID, 32)) { char[] sBuffer = new char[255]; if(gSM_Custom_Prefix.GetString(sAuthID, sBuffer, 255)) { strcopy(gS_Cached_Prefix[client], 32, sBuffer); } if(gSM_Custom_Name.GetString(sAuthID, sBuffer, 255)) { strcopy(gS_Cached_Name[client], MAX_NAME_LENGTH*2, sBuffer); } if(gSM_Custom_Message.GetString(sAuthID, sBuffer, 255)) { strcopy(gS_Cached_Message[client], 255, sBuffer); } if(gSM_Custom_ClanTag.GetString(sAuthID, sBuffer, 255)) { strcopy(gS_Cached_ClanTag[client], 32, sBuffer); } } UpdateClanTag(client); } void ResetCache() { for(int i = 0; i < 64; i++) { if(gD_ChatRanks[i].IsValid) { gD_ChatRanks[i].Dispose(); } } gI_TotalChatRanks = 0; gI_UnassignedTitle = -1; gI_UnrankedTitle = -1; } void LoadConfig() { delete gSM_Custom_Prefix; delete gSM_Custom_Name; delete gSM_Custom_Message; delete gSM_Custom_ClanTag; gSM_Custom_Prefix = new StringMap(); gSM_Custom_Name = new StringMap(); gSM_Custom_Message = new StringMap(); gSM_Custom_ClanTag = new StringMap(); ResetCache(); KeyValues kvConfig = new KeyValues("Chat"); char[] sFile = new char[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sFile, PLATFORM_MAX_PATH, "configs/shavit-chat.cfg"); if(!kvConfig.ImportFromFile(sFile)) { SetFailState("File %s could not be found or accessed.", sFile); } if(kvConfig.GotoFirstSubKey()) { char[] sBuffer = new char[255]; do { kvConfig.GetSectionName(sBuffer, 255); char[] sPrefix = new char[32]; kvConfig.GetString("prefix", sPrefix, 32); char[] sName = new char[MAX_NAME_LENGTH*2]; kvConfig.GetString("name", sName, MAX_NAME_LENGTH*2); char[] sMessage = new char[255]; kvConfig.GetString("message", sMessage, 255); char[] sClanTag = new char[32]; kvConfig.GetString("clantag", sClanTag, 32); // custom if(StrContains(sBuffer[0], "[U:") != -1) { if(strlen(sPrefix) > 0) { gSM_Custom_Prefix.SetString(sBuffer, sPrefix); } if(strlen(sName) > 0) { gSM_Custom_Name.SetString(sBuffer, sName); } if(strlen(sMessage) > 0) { gSM_Custom_Message.SetString(sBuffer, sMessage); } if(strlen(sClanTag) > 0) { gSM_Custom_ClanTag.SetString(sBuffer, sClanTag); } } // ranks else { int iFrom = kvConfig.GetNum("rank_from", -2); if(iFrom == -2) { LogError("Invalid \"rank_from\" value for \"%s\": %d or non-existant.", sBuffer, iFrom); continue; } char[] sTo = new char[16]; kvConfig.GetString("rank_to", sTo, 16, "-2"); int iTo = StrEqual(sTo, "infinity", false)? -3:StringToInt(sTo); if(iTo == -2) { LogError("Invalid \"rank_to\" value for \"%s\": %d or non-existant.", sBuffer, iTo); continue; } gD_ChatRanks[gI_TotalChatRanks] = Dynamic(); gD_ChatRanks[gI_TotalChatRanks].SetInt("rank_from", iFrom); gD_ChatRanks[gI_TotalChatRanks].SetInt("rank_to", iTo); gD_ChatRanks[gI_TotalChatRanks].SetString("prefix", sPrefix, 32); gD_ChatRanks[gI_TotalChatRanks].SetString("name", (strlen(sName) > 0)? sName:"{name}", MAX_NAME_LENGTH*2); gD_ChatRanks[gI_TotalChatRanks].SetString("message", (strlen(sMessage) > 0)? sMessage:"{message}", 255); gD_ChatRanks[gI_TotalChatRanks].SetString("clantag", (strlen(sClanTag) > 0)? sClanTag:"", 32); if(iFrom == -1 && gI_UnassignedTitle == -1) { gI_UnassignedTitle = gI_TotalChatRanks; } else if(iFrom == 0 && gI_UnrankedTitle == -1) { gI_UnrankedTitle = gI_TotalChatRanks; } gI_TotalChatRanks++; } } while(kvConfig.GotoNextKey()); } else { LogError("File %s might be empty?", sFile); } delete kvConfig; for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i)) // late loading { OnClientPutInServer(i); char[] sAuth = new char[32]; if(GetClientAuthId(i, AuthId_Steam3, sAuth, 32)) { OnClientAuthorized(i, sAuth); } } } } public Action Command_ChatRanks(int client, int args) { if(!IsValidClient(client)) { return Plugin_Handled; } // dummies // char[] sExample = "Example."; // I tried using this variable, but it seemed to pick up "List of Chat ranks:" instead, I wonder why.. int[] clients = new int[1]; clients[0] = client; char[] sChatMessage = new char[64]; FormatEx(sChatMessage, 64, "\x01%T", "ChatRankList", client); ChatMessage(client, clients, 1, sChatMessage); for(int i = gI_TotalChatRanks - 1; i >= 0; i--) { if(gD_ChatRanks[i].IsValid) { int iFrom = gD_ChatRanks[i].GetInt("rank_from"); if(iFrom <= 0) { continue; // don't show unranked/due-lookup 'chat ranks' } int iTo = gD_ChatRanks[i].GetInt("rank_to"); char[] sRankText = new char[16]; if(iFrom == iTo) { FormatEx(sRankText, 16, "#%d", iFrom); } else { PrintToConsole(client, "%d", iTo); if(iTo == -3) { FormatEx(sRankText, 16, "#%d - ∞", iFrom, iTo); } else { FormatEx(sRankText, 16, "#%d - #%d", iFrom, iTo); } } char[] sExampleMessage = new char[32]; FormatEx(sExampleMessage, 64, "\x01%T", "ExampleMessage", client); char[] sPrefix = new char[32]; gD_ChatRanks[i].GetString("prefix", sPrefix, 32); FormatVariables(client, sPrefix, 32, sPrefix, sExampleMessage); char[] sName = new char[MAX_NAME_LENGTH*2]; gD_ChatRanks[i].GetString("name", sName, MAX_NAME_LENGTH*2); FormatVariables(client, sName, MAX_NAME_LENGTH*2, sName, sExampleMessage); char[] sMessage = new char[255]; gD_ChatRanks[i].GetString("message", sMessage, 255); FormatVariables(client, sMessage, 255, sMessage, sExampleMessage); char[] sBuffer = new char[300]; FormatEx(sBuffer, 300, "%s\x04[%s]\x01 %s%s %s %s %s", gEV_Type == Engine_CSGO? " ":"", sRankText, strlen(sPrefix) == 0? "\x03":"", sPrefix, sName, gEV_Type == Engine_CSGO? ":\x01":"\x01:", sMessage); ChatMessage(client, clients, 1, sBuffer); } } return Plugin_Handled; } public Action Command_ReloadChat(int client, int args) { LoadConfig(); ReplyToCommand(client, "%T", "ReloadChat", client); return Plugin_Handled; } public Action OnChatMessage(int &author, ArrayList recipients, eChatFlags &flag, char[] name, char[] message, bool &bProcessColors, bool &bRemoveColors) { if(!gB_ChatProcessor) { return Plugin_Continue; } char[] sBuffer = new char[255]; char[] sPrefix = new char[32]; if(strlen(gS_Cached_Prefix[author]) > 0) { FormatVariables(author, sBuffer, 255, gS_Cached_Prefix[author], message); int iLen = strlen(sBuffer); sBuffer[iLen] = (iLen > 0)? ' ':'\0'; strcopy(sPrefix, 32, sBuffer); } char[] sName = new char[MAX_NAME_LENGTH*2]; if(strlen(gS_Cached_Name[author]) > 0) { FormatVariables(author, sBuffer, 255, gS_Cached_Name[author], message); strcopy(sName, MAX_NAME_LENGTH*2, sBuffer); } else { FormatEx(sName, MAX_NAME_LENGTH*2, "\x03%N", author); } char[] sFormattedText = new char[MAXLENGTH_MESSAGE]; strcopy(sFormattedText, MAXLENGTH_MESSAGE, message); // solve shitty exploits ReplaceString(sFormattedText, MAXLENGTH_MESSAGE, "\n", ""); ReplaceString(sFormattedText, MAXLENGTH_MESSAGE, "\t", ""); ReplaceString(sFormattedText, MAXLENGTH_MESSAGE, " ", " "); TrimString(sFormattedText); if(strlen(gS_Cached_Message[author]) > 0) { FormatVariables(author, sBuffer, 255, gS_Cached_Message[author], sFormattedText); strcopy(sFormattedText, 255, sBuffer); } else { strcopy(sFormattedText, 255, message); } FormatEx(name, MAXLENGTH_NAME, "%s%s%s", (gEV_Type == Engine_CSGO)? " ":"", sPrefix, sName); strcopy(message, MAXLENGTH_MESSAGE, sFormattedText); return Plugin_Changed; } public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { if(gB_ChatProcessor || !IsValidClient(client) || !IsClientAuthorized(client) || (gB_BaseComm && BaseComm_IsClientGagged(client))) { return Plugin_Continue; } if(GetEngineTime() - gF_LastMessage[client] < 0.70) { return Plugin_Handled; } bool bTeam = StrEqual(command, "say_team"); if(bTeam || (CheckCommandAccess(client, "sm_say", ADMFLAG_CHAT) && sArgs[0] == '@')) { return Plugin_Handled; } char[] sMessage = new char[300]; strcopy(sMessage, 300, sArgs); if(ReplaceString(sMessage[0], 4, "!", "sm_") > 0 || ReplaceString(sMessage[0], 4, "/", "sm_") > 0) { bool bCmd = false; Handle hCon = FindFirstConCommand(sMessage, 300, bCmd); if(hCon != null) { FindNextConCommand(hCon, sMessage, 300, bCmd); delete hCon; if(bCmd) { return Plugin_Handled; } } } gF_LastMessage[client] = GetEngineTime(); int iTeam = GetClientTeam(client); FormatChatLine(client, sArgs, IsPlayerAlive(client), iTeam, bTeam, sMessage, 300); int[] clients = new int[MaxClients]; int count = 0; PrintToServer("%N: %s", client, sArgs); for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i)) { if(GetClientTeam(i) == iTeam || !bTeam) { clients[count++] = i; PrintToConsole(i, "%N: %s", client, sArgs); } } } ChatMessage(client, clients, count, sMessage); return Plugin_Handled; } void FormatChatLine(int client, const char[] sMessage, bool bAlive, int iTeam, bool bTeam, char[] buffer, int maxlen) { char[] sTeam = new char[32]; char[] sTeamName = new char[32]; if(!bTeam) { if(iTeam == CS_TEAM_SPECTATOR) { FormatEx(sTeamName, 32, "%T", "TeamSpec", client); strcopy(sTeam, 32, sTeamName); } } else { switch(iTeam) { case CS_TEAM_SPECTATOR: { FormatEx(sTeamName, 32, "%T", "TeamSpectator", client); strcopy(sTeam, 32, sTeamName); } case CS_TEAM_T: { FormatEx(sTeamName, 32, "%T", "TeamT", client); strcopy(sTeam, 32, sTeamName); } case CS_TEAM_CT: { FormatEx(sTeamName, 32, "%T", "TeamCT", client); strcopy(sTeam, 32, sTeamName); } } } char[] sAuthID = new char[32]; GetClientAuthId(client, AuthId_Steam3, sAuthID, 32); char[] sBuffer = new char[255]; char[] sNewPrefix = new char[32]; if(strlen(gS_Cached_Prefix[client]) > 0) { FormatVariables(client, sBuffer, 255, gS_Cached_Prefix[client], sMessage); int iLen = strlen(sBuffer); sBuffer[iLen] = (iLen > 0)? ' ':'\0'; strcopy(sNewPrefix, 32, sBuffer); } char[] sNewName = new char[MAX_NAME_LENGTH*2]; if(strlen(gS_Cached_Name[client]) > 0) { FormatVariables(client, sBuffer, 255, gS_Cached_Name[client], sMessage); strcopy(sNewName, MAX_NAME_LENGTH*2, sBuffer); } else { FormatEx(sNewName, MAX_NAME_LENGTH*2, "\x03%N", client); } char[] sFormattedText = new char[maxlen]; strcopy(sFormattedText, maxlen, sMessage); // solve shitty exploits ReplaceString(sFormattedText, maxlen, "\n", ""); ReplaceString(sFormattedText, maxlen, "\t", ""); ReplaceString(sFormattedText, maxlen, " ", " "); TrimString(sFormattedText); if(gB_RTLer) { char[][] sExploded = new char[96][96]; // fixed size from RTLer ExplodeString(sFormattedText, " ", sExploded, 96, 96); bool bRTLify = true; for(int i = 0; i < 96; i++) { if(strlen(sExploded[i]) > 32) { bRTLify = false; break; } } if(bRTLify) { RTLify(sFormattedText, maxlen, sFormattedText); } } if(strlen(gS_Cached_Message[client]) > 0) { FormatVariables(client, sBuffer, 255, gS_Cached_Message[client], sFormattedText); strcopy(sFormattedText, 255, sBuffer); } FormatEx(buffer, maxlen, "\x01%s%s%s\x03%s%s %s %s", gEV_Type == Engine_CSGO? " ":"", (bAlive || iTeam == CS_TEAM_SPECTATOR)? "":"*DEAD* ", sTeam, sNewPrefix, sNewName, gEV_Type == Engine_CSGO? ":\x01":"\x01:", sFormattedText); } void FormatVariables(int client, char[] buffer, int maxlen, const char[] formattingrules, const char[] message) { char[] sTempFormattingRules = new char[maxlen]; strcopy(sTempFormattingRules, maxlen, formattingrules); for(int i = 0; i < sizeof(gS_GlobalColorNames); i++) { ReplaceString(sTempFormattingRules, maxlen, gS_GlobalColorNames[i], gS_GlobalColors[i]); } if(gEV_Type == Engine_CSS) { ReplaceString(sTempFormattingRules, maxlen, "{RGB}", "\x07"); ReplaceString(sTempFormattingRules, maxlen, "{RGBA}", "\x08"); char[] sColorBuffer = new char[16]; do { Format(sColorBuffer, 16, "\x07%x%x%x", RealRandomInt(1, 255), RealRandomInt(1, 255), RealRandomInt(1, 255)); } while(ReplaceStringEx(sTempFormattingRules, maxlen, "{RGBX}", sColorBuffer) > 0); do { Format(sColorBuffer, 16, "\x08%x%x%x%x", RealRandomInt(1, 255), RealRandomInt(1, 255), RealRandomInt(1, 255), RealRandomInt(1, 255)); } while(ReplaceStringEx(sTempFormattingRules, maxlen, "{RGBAX}", sColorBuffer) > 0); } else { for(int i = 0; i < sizeof(gS_CSGOColorNames); i++) { ReplaceString(sTempFormattingRules, maxlen, gS_CSGOColorNames[i], gS_CSGOColors[i]); } } char[] sName = new char[MAX_NAME_LENGTH]; GetClientName(client, sName, MAX_NAME_LENGTH); ReplaceString(sTempFormattingRules, maxlen, "{name}", sName); char[] sClanTag = new char[32]; CS_GetClientClanTag(client, sClanTag, 32); int iLen = strlen(sClanTag); sClanTag[iLen] = (iLen > 0)? ' ':'\0'; ReplaceString(sTempFormattingRules, maxlen, "{clan}", sClanTag); ReplaceString(sTempFormattingRules, maxlen, "{message}", message); strcopy(buffer, maxlen, sTempFormattingRules); } void ChatMessage(int from, int[] clients, int count, const char[] sMessage) { Handle hSayText2 = StartMessage("SayText2", clients, count); if(hSayText2 != null) { if(gEV_Type == Engine_CSGO) { PbSetInt(hSayText2, "ent_idx", from); PbSetBool(hSayText2, "chat", true); PbSetString(hSayText2, "msg_name", sMessage); for(int i = 1; i <= 4; i++) { PbAddString(hSayText2, "params", ""); } } else { BfWriteByte(hSayText2, from); BfWriteByte(hSayText2, true); BfWriteString(hSayText2, sMessage); } EndMessage(); } } public void Player_Spawn(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(!IsFakeClient(client)) { LoadChatCache(client); } } public int Native_FormatChat(Handle handler, int numParams) { int client = GetNativeCell(1); if(!IsValidClient(client)) { ThrowNativeError(200, "Invalid client index %d", client); return -1; } char[] sMessage = new char[255]; GetNativeString(2, sMessage, 255); char[] sBuffer = new char[300]; FormatChatLine(client, sMessage, IsPlayerAlive(client), GetClientTeam(client), view_as(GetNativeCell(3)), sBuffer, 300); int maxlength = GetNativeCell(5); return SetNativeString(6, sBuffer, maxlength); } // from SMLib int RealRandomInt(int min, int max) { int random = GetURandomInt(); if(random == 0) { random++; } return RoundToCeil(float(random) / (float(2147483647) / float(max - min + 1))) + min - 1; }