From fad6ee3eea2523a52e59a59a4ceccf6835499459 Mon Sep 17 00:00:00 2001 From: Playa Date: Sun, 7 Jun 2020 17:01:48 +0200 Subject: [PATCH] Added new grabbing Material +Added StAC to the Grab List --- GRAB/StAC/Web.txt | 2 + .../StAC/scripting/include/color_literals.inc | 354 ++++ GRAB/StAC/scripting/include/morecolors.inc | 674 ++++++++ GRAB/StAC/scripting/stac.sp | 1536 +++++++++++++++++ GRAB/StAC/translations/stac.phrases.txt | 195 +++ GRAB/StAC/updatefile.txt | 24 + 6 files changed, 2785 insertions(+) create mode 100644 GRAB/StAC/Web.txt create mode 100644 GRAB/StAC/scripting/include/color_literals.inc create mode 100644 GRAB/StAC/scripting/include/morecolors.inc create mode 100644 GRAB/StAC/scripting/stac.sp create mode 100644 GRAB/StAC/translations/stac.phrases.txt create mode 100644 GRAB/StAC/updatefile.txt diff --git a/GRAB/StAC/Web.txt b/GRAB/StAC/Web.txt new file mode 100644 index 0000000..1163c4b --- /dev/null +++ b/GRAB/StAC/Web.txt @@ -0,0 +1,2 @@ +>>https://github.com/stephanieLGBT/StAC-tf2 +latest commit:7c80643c5c67d2275c7dc24e68b39f380fe43d86 \ No newline at end of file diff --git a/GRAB/StAC/scripting/include/color_literals.inc b/GRAB/StAC/scripting/include/color_literals.inc new file mode 100644 index 0000000..da2a4a3 --- /dev/null +++ b/GRAB/StAC/scripting/include/color_literals.inc @@ -0,0 +1,354 @@ +/** + * Constant colors include. + * A lightweight replacement for other colors includes. + * + * Converts colors to color codes at compile time, instead of adding overhead with a color + * mapping at runtime. + * + * To use these colors, use the string literal concatenation operator (...) to insert them in + * your chat messages. No additional process or checks are performed at run-time, so make sure + * your target game(s) are compatible with these constants. + * + * Should support the same games that More Colors supports: + * https://forums.alliedmods.net/showthread.php?t=185016 + */ + +#if defined __stocksoup_color_literals_included + #endinput +#endif + +#define __stocksoup_color_literals_included + +#define COLOR_DEFAULT "\x01" +#define COLOR_PLAYER "\x02" +#define COLOR_TEAM "\x03" +#define COLOR_LOCATION "\x04" + +#define CHAT_SOURCE_SERVER 0 +#define CHAT_SOURCE_SELF -1 + +#define COLOR_ALICEBLUE "\x07F0F8FF" +#define COLOR_ANTIQUEWHITE "\x07FAEBD7" +#define COLOR_AQUA "\x0700FFFF" +#define COLOR_AQUAMARINE "\x077FFFD4" +#define COLOR_AZURE "\x07007FFF" +#define COLOR_BEIGE "\x07F5F5DC" +#define COLOR_BISQUE "\x07FFE4C4" +#define COLOR_BLACK "\x07000000" +#define COLOR_BLANCHEDALMOND "\x07FFEBCD" +#define COLOR_BLUEVIOLET "\x078A2BE2" +#define COLOR_BROWN "\x07A52A2A" +#define COLOR_BURLYWOOD "\x07DEB887" +#define COLOR_CADETBLUE "\x075F9EA0" +#define COLOR_CHARTREUSE "\x077FFF00" +#define COLOR_CHOCOLATE "\x07D2691E" +#define COLOR_CORAL "\x07FF7F50" +#define COLOR_CORNFLOWERBLUE "\x076495ED" +#define COLOR_CORNSILK "\x07FFF8DC" +#define COLOR_CRIMSON "\x07DC143C" +#define COLOR_CYAN "\x0700FFFF" +#define COLOR_DARKBLUE "\x0700008B" +#define COLOR_DARKCYAN "\x07008B8B" +#define COLOR_DARKGOLDENROD "\x07B8860B" +#define COLOR_DARKGRAY "\x07A9A9A9" +#define COLOR_DARKGREY "\x07A9A9A9" +#define COLOR_DARKGREEN "\x07006400" +#define COLOR_DARKKHAKI "\x07BDB76B" +#define COLOR_DARKMAGENTA "\x078B008B" +#define COLOR_DARKOLIVEGREEN "\x07556B2F" +#define COLOR_DARKORANGE "\x07FF8C00" +#define COLOR_DARKORCHID "\x079932CC" +#define COLOR_DARKRED "\x078B0000" +#define COLOR_DARKSALMON "\x07E9967A" +#define COLOR_DARKSEAGREEN "\x078FBC8F" +#define COLOR_DARKSLATEBLUE "\x07483D8B" +#define COLOR_DARKSLATEGRAY "\x072F4F4F" +#define COLOR_DARKSLATEGREY "\x072F4F4F" +#define COLOR_DARKTURQUOISE "\x0700CED1" +#define COLOR_DARKVIOLET "\x079400D3" +#define COLOR_DEEPPINK "\x07FF1493" +#define COLOR_DEEPSKYBLUE "\x0700BFFF" +#define COLOR_DIMGRAY "\x07696969" +#define COLOR_DIMGREY "\x07696969" +#define COLOR_DODGERBLUE "\x071E90FF" +#define COLOR_FIREBRICK "\x07B22222" +#define COLOR_FLORALWHITE "\x07FFFAF0" +#define COLOR_FORESTGREEN "\x07228B22" +#define COLOR_FUCHSIA "\x07FF00FF" +#define COLOR_FULLBLUE "\x070000FF" +#define COLOR_FULLRED "\x07FF0000" +#define COLOR_GAINSBORO "\x07DCDCDC" +#define COLOR_GHOSTWHITE "\x07F8F8FF" +#define COLOR_GOLD "\x07FFD700" +#define COLOR_GOLDENROD "\x07DAA520" +#define COLOR_GREY "\x07CCCCCC" +#define COLOR_GREEN "\x073EFF3E" +#define COLOR_GREENYELLOW "\x07ADFF2F" +#define COLOR_HONEYDEW "\x07F0FFF0" +#define COLOR_HOTPINK "\x07FF69B4" +#define COLOR_INDIANRED "\x07CD5C5C" +#define COLOR_INDIGO "\x074B0082" +#define COLOR_IVORY "\x07FFFFF0" +#define COLOR_KHAKI "\x07F0E68C" +#define COLOR_LAVENDER "\x07E6E6FA" +#define COLOR_LAVENDERBLUSH "\x07FFF0F5" +#define COLOR_LAWNGREEN "\x077CFC00" +#define COLOR_LEMONCHIFFON "\x07FFFACD" +#define COLOR_LIGHTBLUE "\x07ADD8E6" +#define COLOR_LIGHTCORAL "\x07F08080" +#define COLOR_LIGHTCYAN "\x07E0FFFF" +#define COLOR_LIGHTGOLDENRODYELLOW "\x07FAFAD2" +#define COLOR_LIGHTGRAY "\x07D3D3D3" +#define COLOR_LIGHTGREY "\x07D3D3D3" +#define COLOR_LIGHTGREEN "\x0799FF99" +#define COLOR_LIGHTPINK "\x07FFB6C1" +#define COLOR_LIGHTSALMON "\x07FFA07A" +#define COLOR_LIGHTSEAGREEN "\x0720B2AA" +#define COLOR_LIGHTSKYBLUE "\x0787CEFA" +#define COLOR_LIGHTSLATEGRAY "\x07778899" +#define COLOR_LIGHTSLATEGREY "\x07778899" +#define COLOR_LIGHTSTEELBLUE "\x07B0C4DE" +#define COLOR_LIGHTYELLOW "\x07FFFFE0" +#define COLOR_LIME "\x0700FF00" +#define COLOR_LIMEGREEN "\x0732CD32" +#define COLOR_LINEN "\x07FAF0E6" +#define COLOR_MAGENTA "\x07FF00FF" +#define COLOR_MAROON "\x07800000" +#define COLOR_MEDIUMAQUAMARINE "\x0766CDAA" +#define COLOR_MEDIUMBLUE "\x070000CD" +#define COLOR_MEDIUMORCHID "\x07BA55D3" +#define COLOR_MEDIUMPURPLE "\x079370D8" +#define COLOR_MEDIUMSEAGREEN "\x073CB371" +#define COLOR_MEDIUMSLATEBLUE "\x077B68EE" +#define COLOR_MEDIUMSPRINGGREEN "\x0700FA9A" +#define COLOR_MEDIUMTURQUOISE "\x0748D1CC" +#define COLOR_MEDIUMVIOLETRED "\x07C71585" +#define COLOR_MIDNIGHTBLUE "\x07191970" +#define COLOR_MINTCREAM "\x07F5FFFA" +#define COLOR_MISTYROSE "\x07FFE4E1" +#define COLOR_MOCCASIN "\x07FFE4B5" +#define COLOR_NAVAJOWHITE "\x07FFDEAD" +#define COLOR_NAVY "\x07000080" +#define COLOR_OLDLACE "\x07FDF5E6" +#define COLOR_OLIVE "\x079EC34F" +#define COLOR_OLIVEDRAB "\x076B8E23" +#define COLOR_ORANGE "\x07FFA500" +#define COLOR_ORANGERED "\x07FF4500" +#define COLOR_ORCHID "\x07DA70D6" +#define COLOR_PALEGOLDENROD "\x07EEE8AA" +#define COLOR_PALEGREEN "\x0798FB98" +#define COLOR_PALETURQUOISE "\x07AFEEEE" +#define COLOR_PALEVIOLETRED "\x07D87093" +#define COLOR_PAPAYAWHIP "\x07FFEFD5" +#define COLOR_PEACHPUFF "\x07FFDAB9" +#define COLOR_PERU "\x07CD853F" +#define COLOR_PINK "\x07FFC0CB" +#define COLOR_PLUM "\x07DDA0DD" +#define COLOR_POWDERBLUE "\x07B0E0E6" +#define COLOR_PURPLE "\x07800080" +#define COLOR_ROSYBROWN "\x07BC8F8F" +#define COLOR_ROYALBLUE "\x074169E1" +#define COLOR_SADDLEBROWN "\x078B4513" +#define COLOR_SALMON "\x07FA8072" +#define COLOR_SANDYBROWN "\x07F4A460" +#define COLOR_SEAGREEN "\x072E8B57" +#define COLOR_SEASHELL "\x07FFF5EE" +#define COLOR_SIENNA "\x07A0522D" +#define COLOR_SILVER "\x07C0C0C0" +#define COLOR_SKYBLUE "\x0787CEEB" +#define COLOR_SLATEBLUE "\x076A5ACD" +#define COLOR_SLATEGRAY "\x07708090" +#define COLOR_SLATEGREY "\x07708090" +#define COLOR_SNOW "\x07FFFAFA" +#define COLOR_SPRINGGREEN "\x0700FF7F" +#define COLOR_STEELBLUE "\x074682B4" +#define COLOR_TAN "\x07D2B48C" +#define COLOR_TEAL "\x07008080" +#define COLOR_THISTLE "\x07D8BFD8" +#define COLOR_TOMATO "\x07FF6347" +#define COLOR_TURQUOISE "\x0740E0D0" +#define COLOR_VIOLET "\x07EE82EE" +#define COLOR_WHEAT "\x07F5DEB3" +#define COLOR_WHITE "\x07FFFFFF" +#define COLOR_WHITESMOKE "\x07F5F5F5" +#define COLOR_YELLOW "\x07FFFF00" +#define COLOR_YELLOWGREEN "\x079ACD32" + +#define COLOR_BLUE "\x0799CCFF" // same as BLU/Counter-Terrorist team color +#define COLOR_GRAY "\x07CCCCCC" // same as spectator team color +#define COLOR_RED "\x07FF4040" // same as RED/Terrorist team color + +#define COLOR_ALLIES "\x074D7942" // same as Allies team in DoD:S +#define COLOR_AXIS "\x07FF4040" // same as Axis team in DoD:S + +#define COLOR_ANCIENT "\x07EB4B4B" // same as Ancient item rarity in Dota 2 +#define COLOR_ARCANA "\x07ADE55C" // same as Arcana item rarity in Dota 2 +#define COLOR_COMMON "\x07B0C3D9" // same as Common item rarity in Dota 2 +#define COLOR_CORRUPTED "\x07A32C2E" // same as Corrupted item quality in Dota 2 +#define COLOR_EXALTED "\x07CCCCCD" // same as Exalted item quality in Dota 2 +#define COLOR_FROZEN "\x074983B3" // same as Frozen item quality in Dota 2 +#define COLOR_IMMORTAL "\x07E4AE33" // same as Immortal item rarity in Dota 2 +#define COLOR_LEGENDARY "\x07D32CE6" // same as Legendary item rarity in Dota 2 +#define COLOR_MYTHICAL "\x078847FF" // same as Mythical item rarity in Dota 2 +#define COLOR_RARE "\x074B69FF" // same as Rare item rarity in Dota 2 +#define COLOR_UNCOMMON "\x07B0C3D9" // same as Uncommon item rarity in Dota 2 + +#define COLOR_COLLECTORS "\x07AA0000" // same as Collector's item quality in TF2 +#define COLOR_COMMUNITY "\x0770B04A" // same as Community item quality in TF2 +#define COLOR_GENUINE "\x074D7455" // same as Genuine item quality in TF2 +#define COLOR_HAUNTED "\x0738F3AB" // same as Haunted item quality in TF2 +#define COLOR_NORMAL "\x07B2B2B2" // same as Normal item quality in TF2 +#define COLOR_SELFMADE "\x0770B04A" // same as Self-Made item quality in TF2 +#define COLOR_STRANGE "\x07CF6A32" // same as Strange item quality in TF2 +#define COLOR_UNIQUE "\x07FFD700" // same as Unique item quality in TF2 +#define COLOR_UNUSUAL "\x078650AC" // same as Unusual item quality in TF2 +#define COLOR_VALVE "\x07A50F79" // same as Valve item quality in TF2 +#define COLOR_VINTAGE "\x07476291" // same as Vintage item quality in TF2 + +/** + * Prints a colored message. + * + * See: http://www.teamfortress.com/post.php?id=7946 + * + * In games that support the full color range, messages can be colorized by using \x07 followed + * by a hex code in RRGGBB format, or \x08 followed by a hex code in RRGGBBAA format. You can + * also use the COLOR_* constants from this include file. + * + * Default colors include \x01-\x04, representing normal, player name color, team color, and + * location color. + * + * The team color (\x03) is based on a source player's current team. This function treats the + * server as the source; use the `PrintColoredChat*Ex` stock functions to have better control + * over the source. + * + * CS:GO supports other single-byte colors from \x05 to \x10. You can also use the CSGO_COLOR_* + * constants from this include file. + * + * This is intended as a replacement to `PrintToChat`, as using that function with color codes + * outputs the hex values to the client's developer console. + */ +stock void PrintColoredChat(int client, const char[] format, any ...) { + char messageBuffer[192]; + SetGlobalTransTarget(client); + VFormat(messageBuffer, sizeof(messageBuffer), format, 3); + SetGlobalTransTarget(LANG_SERVER); + + PrintColoredChatEx(client, CHAT_SOURCE_SERVER, "%s", messageBuffer); +} + +/** + * Prints a colored message to all players. + */ +stock void PrintColoredChatAll(const char[] format, any ...) { + char messageBuffer[192]; + for (int i = 1; i <= MaxClients; i++) { + if (!IsClientInGame(i)) { + continue; + } + + SetGlobalTransTarget(i); + VFormat(messageBuffer, sizeof(messageBuffer), format, 2); + + PrintColoredChatEx(i, CHAT_SOURCE_SERVER, "%s", messageBuffer); + } + SetGlobalTransTarget(LANG_SERVER); +} + +/** + * Prints a colored message to all players, with the option of setting a source player. + * CHAT_SOURCE_SELF treats the client as the source, meaning the team color is that of the + * source player's. + */ +stock void PrintColoredChatEx(int client, int source = CHAT_SOURCE_SELF, const char[] format, any ...) { + source = source == CHAT_SOURCE_SELF ? client : source; + + // https://forums.alliedmods.net/showpost.php?p=1909951&postcount=4 + char modifiedFormat[192] = COLOR_DEFAULT, messageBuffer[192]; + + if (strncmp(format, modifiedFormat, 1) != 0) { + // prefix doesn't match, prepend with default color + StrCat(modifiedFormat, sizeof(modifiedFormat), format); + } else { + strcopy(modifiedFormat, sizeof(modifiedFormat), format); + } + + SetGlobalTransTarget(client); + VFormat(messageBuffer, sizeof(messageBuffer), modifiedFormat, 4); + SetGlobalTransTarget(LANG_SERVER); + + Handle buffer = StartMessageOne("SayText2", client, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS); + switch (GetUserMessageType()) { + case UM_BitBuf: { + BfWrite bitbuf = UserMessageToBfWrite(buffer); + + bitbuf.WriteByte(source); + bitbuf.WriteByte(true); + bitbuf.WriteString(messageBuffer); + } + case UM_Protobuf: { + Protobuf protobuf = UserMessageToProtobuf(buffer); + + protobuf.SetBool("chat", true); + protobuf.SetInt("ent_idx", source); + + protobuf.SetString("msg_name", messageBuffer); + protobuf.AddString("params", ""); + protobuf.AddString("params", ""); + protobuf.AddString("params", ""); + protobuf.AddString("params", ""); + } + } + + EndMessage(); +} + +/** + * Prints colored chat to all players, using a specific client as the source. + * CHAT_SOURCE_SELF treats each client as the message's source. + */ +stock void PrintColoredChatAllEx(int source = CHAT_SOURCE_SELF, const char[] format, any ...) { + char messageBuffer[192]; + for (int i = 1; i <= MaxClients; i++) { + if (!IsClientInGame(i)) { + continue; + } + + SetGlobalTransTarget(i); + VFormat(messageBuffer, sizeof(messageBuffer), format, 3); + + PrintColoredChatEx(i, source, "%s", messageBuffer); + } + SetGlobalTransTarget(LANG_SERVER); +} + +/** + * Strips color characters from a message. + * + * If bStripHexCodes is true, \x07 and \x08 will also remove the RGB(A) characters that follow, + * and \n is treated as a newline (if not stripping hex codes, newline is assumed to be a + * color as is the case in CS:GO). + */ +stock int StripColorChars(const char[] input, char[] output, int maxlen, + bool bStripHexCodes = true) { + char[] outputBuffer = new char[maxlen]; + int i, o, l = strlen(input); + while (i < l && o < maxlen) { + bool bIsColorChar = input[i] < 32 && (input[i] != '\n' || !bStripHexCodes); + + if (bIsColorChar) { + if (bStripHexCodes) { + switch (input[i]) { + case '\x07': { + i += 6; // RRGGBB + } + case '\x08': { + i += 8; // RRGGBBAA + } + } + } + i++; + } + outputBuffer[o++] = input[i++]; + } + return strcopy(output, maxlen, outputBuffer); +} \ No newline at end of file diff --git a/GRAB/StAC/scripting/include/morecolors.inc b/GRAB/StAC/scripting/include/morecolors.inc new file mode 100644 index 0000000..3a416d8 --- /dev/null +++ b/GRAB/StAC/scripting/include/morecolors.inc @@ -0,0 +1,674 @@ +// MOAR COLORS +// By Dr. McKay +// Inspired by: https://forums.alliedmods.net/showthread.php?t=96831 + +#if defined _colors_included + #endinput +#endif +#define _colors_included + +#include + +#define MORE_COLORS_VERSION "1.9.1" +#define MAX_MESSAGE_LENGTH 256 +#define MAX_BUFFER_LENGTH (MAX_MESSAGE_LENGTH * 4) + +#define COLOR_RED 0xFF4040 +#define COLOR_BLUE 0x99CCFF +#define COLOR_GRAY 0xCCCCCC +#define COLOR_GREEN 0x3EFF3E + +#define GAME_DODS 0 + +new bool:CSkipList[MAXPLAYERS + 1]; +new Handle:CTrie; +new CTeamColors[][] = {{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 + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + * @noreturn + * + * On error/Errors: If the client is not connected an error will be thrown. + */ +stock CPrintToChat(client, const String:message[], any:...) { + CCheckTrie(); + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2); + CSendMessage(client, buffer2); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + * @noreturn + */ +stock CPrintToChatAll(const String:message[], any:...) { + CCheckTrie(); + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + for(new i = 1; i <= MaxClients; i++) { + if(!IsClientInGame(i) || CSkipList[i]) { + CSkipList[i] = false; + continue; + } + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 2); + CReplaceColorCodes(buffer2); + CSendMessage(i, buffer2); + } +} + +/** + * 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). + * @noreturn + * + * On error/Errors: If the client or author are not connected an error will be thrown + */ +stock CPrintToChatEx(client, author, const String:message[], any:...) { + CCheckTrie(); + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + if(author <= 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2, author); + CSendMessage(client, buffer2, author); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags and teamcolor tag. + * + * @param author Author index whose color will be used for teamcolor tag. + * @param message Message (formatting rules). + * @noreturn + * + * On error/Errors: If the author is not connected an error will be thrown. + */ +stock CPrintToChatAllEx(author, const String:message[], any:...) { + CCheckTrie(); + if(author <= 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + for(new i = 1; i <= MaxClients; i++) { + if(!IsClientInGame(i) || CSkipList[i]) { + CSkipList[i] = false; + continue; + } + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2, author); + CSendMessage(i, buffer2, author); + } +} + +/** + * Sends a SayText2 usermessage + * + * @param client Client to send usermessage to + * @param message Message to send + * @noreturn + */ +stock CSendMessage(client, const String:message[], author=0) { + if(author == 0) { + author = client; + } + decl String:buffer[MAX_MESSAGE_LENGTH], String:game[16]; + GetGameFolderName(game, sizeof(game)); + strcopy(buffer, sizeof(buffer), message); + new UserMsg:index = GetUserMessageId("SayText2"); + if(index == INVALID_MESSAGE_ID) { + if(StrEqual(game, "dod")) { + new team = GetClientTeam(author); + if(team == 0) { + ReplaceString(buffer, sizeof(buffer), "\x03", "\x04", false); // Unassigned gets green + } else { + decl String:temp[16]; + Format(temp, sizeof(temp), "\x07%06X", CTeamColors[GAME_DODS][team - 1]); + ReplaceString(buffer, sizeof(buffer), "\x03", temp, false); + } + } + PrintToChat(client, "%s", buffer); + return; + } + new Handle:buf = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + if(GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { + PbSetInt(buf, "ent_idx", author); + PbSetBool(buf, "chat", true); + PbSetString(buf, "msg_name", buffer); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + } else { + BfWriteByte(buf, author); // Message author + BfWriteByte(buf, true); // Chat message + BfWriteString(buf, buffer); // Message text + } + EndMessage(); +} + +/** + * This function should only be used right in front of + * CPrintToChatAll or CPrintToChatAllEx. 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 + * @noreturn + */ +stock CSkipNextClient(client) { + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + CSkipList[client] = true; +} + +/** + * Checks if the colors trie is initialized and initializes it if it's not (used internally) + * + * @return No return + */ +stock CCheckTrie() { + if(CTrie == INVALID_HANDLE) { + CTrie = InitColorTrie(); + } +} + +/** + * Replaces color tags in a string with color codes (used internally by CPrintToChat, CPrintToChatAll, CPrintToChatEx, and CPrintToChatAllEx + * + * @param buffer String. + * @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 CRemoveTags + * @param maxlen Optional value for max buffer length, used by CRemoveTags + * @noreturn + * + * On error/Errors: If the client index passed for author is invalid or not in game. + */ +stock CReplaceColorCodes(String:buffer[], author=0, bool:removeTags=false, maxlen=MAX_BUFFER_LENGTH) { + CCheckTrie(); + if(!removeTags) { + ReplaceString(buffer, maxlen, "{default}", "\x01", false); + } else { + ReplaceString(buffer, maxlen, "{default}", "", false); + ReplaceString(buffer, maxlen, "{teamcolor}", "", false); + } + if(author != 0 && !removeTags) { + if(author < 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + ReplaceString(buffer, maxlen, "{teamcolor}", "\x03", false); + } + new cursor = 0; + new value; + decl String:tag[32], String:buff[32], String:output[maxlen]; + 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 + + new Handle:regex = CompileRegex("{[a-zA-Z0-9]+}"); + for(new 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(MatchRegex(regex, buffer[cursor]) < 1) { + CloseHandle(regex); + strcopy(buffer, maxlen, output); + return; + } + GetRegexSubString(regex, 0, tag, sizeof(tag)); + CStrToLower(tag); + cursor = StrContains(buffer[cursor], tag, false) + cursor + 1; + strcopy(buff, sizeof(buff), tag); + ReplaceString(buff, sizeof(buff), "{", ""); + ReplaceString(buff, sizeof(buff), "}", ""); + + if(!GetTrieValue(CTrie, buff, value)) { + continue; + } + + if(removeTags) { + ReplaceString(output, maxlen, tag, "", false); + } else { + Format(buff, sizeof(buff), "\x07%06X", value); + ReplaceString(output, maxlen, tag, buff, false); + } + } + LogError("[MORE COLORS] Infinite loop broken."); +} + +/** + * 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 + * @param start Position to start at + * @param numChars Number of characters to return, or 0 for the end of the string + * @noreturn + */ +stock CSubString(const String:input[], String:output[], maxlen, start, numChars=0) { + new i = 0; + for(;;) { + if(i == maxlen - 1 || i >= numChars || input[start + i] == '\0') { + output[i] = '\0'; + return; + } + output[i] = input[start + i]; + i++; + } +} + +/** + * Converts a string to lowercase + * + * @param buffer String to convert + * @noreturn + */ +stock CStrToLower(String:buffer[]) { + new len = strlen(buffer); + for(new i = 0; i < len; i++) { + buffer[i] = CharToLower(buffer[i]); + } +} + +/** + * Adds a color to the colors trie + * + * @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:CAddColor(const String:name[], color) { + CCheckTrie(); + new value; + if(GetTrieValue(CTrie, name, value)) { + return false; + } + decl String:newName[64]; + strcopy(newName, sizeof(newName), name); + CStrToLower(newName); + SetTrieValue(CTrie, newName, color); + return true; +} + +/** + * Removes color tags from a message + * + * @param message Message to remove tags from + * @param maxlen Maximum buffer length + * @noreturn + */ +stock CRemoveTags(String:message[], maxlen) { + CReplaceColorCodes(message, 0, true, maxlen); +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param message Message (formatting rules) + * @noreturn + */ +stock CReplyToCommand(client, const String:message[], any:...) { + decl String:buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + if(GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + CRemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } else { + CPrintToChat(client, "%s", buffer); + } +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param author Client to use for {teamcolor} + * @param message Message (formatting rules) + * @noreturn + */ +stock CReplyToCommandEx(client, author, const String:message[], any:...) { + decl String:buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + if(GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + CRemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } else { + CPrintToChatEx(client, author, "%s", buffer); + } +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivity(client, const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2); + ShowActivity(client, "%s", buffer2); +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param tag Tag to prepend to the message (color tags supported) + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivityEx(client, const String:tag[], const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2); + strcopy(buffer, sizeof(buffer), tag); + CReplaceColorCodes(buffer); + ShowActivityEx(client, tag, "%s", buffer2); +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param tag Tag to prepend to the message (color tags supported) + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivity2(client, const String:tag[], const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2); + strcopy(buffer, sizeof(buffer), tag); + CReplaceColorCodes(buffer); + ShowActivity2(client, buffer, "%s", buffer2); +} + +/** + * 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 String:color[]) { + CCheckTrie(); + new temp; + return GetTrieValue(CTrie, color, temp); +} + +/** + * Returns the hexadecimal representation of a client's team color (will NOT initialize the trie) + * + * @param client Client to get the team color for + * @return Client's team color in hexadecimal, or green if unknown + * On error/Errors: If the client index passed is invalid or not in game. + */ +stock CGetTeamColor(client) { + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + new value; + switch(GetClientTeam(client)) { + case 1: { + value = COLOR_GRAY; + } + case 2: { + value = COLOR_RED; + } + case 3: { + value = COLOR_BLUE; + } + default: { + value = COLOR_GREEN; + } + } + return value; +} + +stock Handle:InitColorTrie() { + new Handle:hTrie = CreateTrie(); + SetTrieValue(hTrie, "aliceblue", 0xF0F8FF); + SetTrieValue(hTrie, "allies", 0x4D7942); // same as Allies team in DoD:S + SetTrieValue(hTrie, "ancient", 0xEB4B4B); // same as Ancient item rarity in Dota 2 + SetTrieValue(hTrie, "antiquewhite", 0xFAEBD7); + SetTrieValue(hTrie, "aqua", 0x00FFFF); + SetTrieValue(hTrie, "aquamarine", 0x7FFFD4); + SetTrieValue(hTrie, "arcana", 0xADE55C); // same as Arcana item rarity in Dota 2 + SetTrieValue(hTrie, "axis", 0xFF4040); // same as Axis team in DoD:S + SetTrieValue(hTrie, "azure", 0x007FFF); + SetTrieValue(hTrie, "beige", 0xF5F5DC); + SetTrieValue(hTrie, "bisque", 0xFFE4C4); + SetTrieValue(hTrie, "black", 0x000000); + SetTrieValue(hTrie, "blanchedalmond", 0xFFEBCD); + SetTrieValue(hTrie, "blue", 0x99CCFF); // same as BLU/Counter-Terrorist team color + SetTrieValue(hTrie, "blueviolet", 0x8A2BE2); + SetTrieValue(hTrie, "brown", 0xA52A2A); + SetTrieValue(hTrie, "burlywood", 0xDEB887); + SetTrieValue(hTrie, "cadetblue", 0x5F9EA0); + SetTrieValue(hTrie, "chartreuse", 0x7FFF00); + SetTrieValue(hTrie, "chocolate", 0xD2691E); + SetTrieValue(hTrie, "collectors", 0xAA0000); // same as Collector's item quality in TF2 + SetTrieValue(hTrie, "common", 0xB0C3D9); // same as Common item rarity in Dota 2 + SetTrieValue(hTrie, "community", 0x70B04A); // same as Community item quality in TF2 + SetTrieValue(hTrie, "coral", 0xFF7F50); + SetTrieValue(hTrie, "cornflowerblue", 0x6495ED); + SetTrieValue(hTrie, "cornsilk", 0xFFF8DC); + SetTrieValue(hTrie, "corrupted", 0xA32C2E); // same as Corrupted item quality in Dota 2 + SetTrieValue(hTrie, "crimson", 0xDC143C); + SetTrieValue(hTrie, "cyan", 0x00FFFF); + SetTrieValue(hTrie, "darkblue", 0x00008B); + SetTrieValue(hTrie, "darkcyan", 0x008B8B); + SetTrieValue(hTrie, "darkgoldenrod", 0xB8860B); + SetTrieValue(hTrie, "darkgray", 0xA9A9A9); + SetTrieValue(hTrie, "darkgrey", 0xA9A9A9); + SetTrieValue(hTrie, "darkgreen", 0x006400); + SetTrieValue(hTrie, "darkkhaki", 0xBDB76B); + SetTrieValue(hTrie, "darkmagenta", 0x8B008B); + SetTrieValue(hTrie, "darkolivegreen", 0x556B2F); + SetTrieValue(hTrie, "darkorange", 0xFF8C00); + SetTrieValue(hTrie, "darkorchid", 0x9932CC); + SetTrieValue(hTrie, "darkred", 0x8B0000); + SetTrieValue(hTrie, "darksalmon", 0xE9967A); + SetTrieValue(hTrie, "darkseagreen", 0x8FBC8F); + SetTrieValue(hTrie, "darkslateblue", 0x483D8B); + SetTrieValue(hTrie, "darkslategray", 0x2F4F4F); + SetTrieValue(hTrie, "darkslategrey", 0x2F4F4F); + SetTrieValue(hTrie, "darkturquoise", 0x00CED1); + SetTrieValue(hTrie, "darkviolet", 0x9400D3); + SetTrieValue(hTrie, "deeppink", 0xFF1493); + SetTrieValue(hTrie, "deepskyblue", 0x00BFFF); + SetTrieValue(hTrie, "dimgray", 0x696969); + SetTrieValue(hTrie, "dimgrey", 0x696969); + SetTrieValue(hTrie, "dodgerblue", 0x1E90FF); + SetTrieValue(hTrie, "exalted", 0xCCCCCD); // same as Exalted item quality in Dota 2 + SetTrieValue(hTrie, "firebrick", 0xB22222); + SetTrieValue(hTrie, "floralwhite", 0xFFFAF0); + SetTrieValue(hTrie, "forestgreen", 0x228B22); + SetTrieValue(hTrie, "frozen", 0x4983B3); // same as Frozen item quality in Dota 2 + SetTrieValue(hTrie, "fuchsia", 0xFF00FF); + SetTrieValue(hTrie, "fullblue", 0x0000FF); + SetTrieValue(hTrie, "fullred", 0xFF0000); + SetTrieValue(hTrie, "gainsboro", 0xDCDCDC); + SetTrieValue(hTrie, "genuine", 0x4D7455); // same as Genuine item quality in TF2 + SetTrieValue(hTrie, "ghostwhite", 0xF8F8FF); + SetTrieValue(hTrie, "gold", 0xFFD700); + SetTrieValue(hTrie, "goldenrod", 0xDAA520); + SetTrieValue(hTrie, "gray", 0xCCCCCC); // same as spectator team color + SetTrieValue(hTrie, "grey", 0xCCCCCC); + SetTrieValue(hTrie, "green", 0x3EFF3E); + SetTrieValue(hTrie, "greenyellow", 0xADFF2F); + SetTrieValue(hTrie, "haunted", 0x38F3AB); // same as Haunted item quality in TF2 + SetTrieValue(hTrie, "honeydew", 0xF0FFF0); + SetTrieValue(hTrie, "hotpink", 0xFF69B4); + SetTrieValue(hTrie, "immortal", 0xE4AE33); // same as Immortal item rarity in Dota 2 + SetTrieValue(hTrie, "indianred", 0xCD5C5C); + SetTrieValue(hTrie, "indigo", 0x4B0082); + SetTrieValue(hTrie, "ivory", 0xFFFFF0); + SetTrieValue(hTrie, "khaki", 0xF0E68C); + SetTrieValue(hTrie, "lavender", 0xE6E6FA); + SetTrieValue(hTrie, "lavenderblush", 0xFFF0F5); + SetTrieValue(hTrie, "lawngreen", 0x7CFC00); + SetTrieValue(hTrie, "legendary", 0xD32CE6); // same as Legendary item rarity in Dota 2 + SetTrieValue(hTrie, "lemonchiffon", 0xFFFACD); + SetTrieValue(hTrie, "lightblue", 0xADD8E6); + SetTrieValue(hTrie, "lightcoral", 0xF08080); + SetTrieValue(hTrie, "lightcyan", 0xE0FFFF); + SetTrieValue(hTrie, "lightgoldenrodyellow", 0xFAFAD2); + SetTrieValue(hTrie, "lightgray", 0xD3D3D3); + SetTrieValue(hTrie, "lightgrey", 0xD3D3D3); + SetTrieValue(hTrie, "lightgreen", 0x99FF99); + SetTrieValue(hTrie, "lightpink", 0xFFB6C1); + SetTrieValue(hTrie, "lightsalmon", 0xFFA07A); + SetTrieValue(hTrie, "lightseagreen", 0x20B2AA); + SetTrieValue(hTrie, "lightskyblue", 0x87CEFA); + SetTrieValue(hTrie, "lightslategray", 0x778899); + SetTrieValue(hTrie, "lightslategrey", 0x778899); + SetTrieValue(hTrie, "lightsteelblue", 0xB0C4DE); + SetTrieValue(hTrie, "lightyellow", 0xFFFFE0); + SetTrieValue(hTrie, "lime", 0x00FF00); + SetTrieValue(hTrie, "limegreen", 0x32CD32); + SetTrieValue(hTrie, "linen", 0xFAF0E6); + SetTrieValue(hTrie, "magenta", 0xFF00FF); + SetTrieValue(hTrie, "maroon", 0x800000); + SetTrieValue(hTrie, "mediumaquamarine", 0x66CDAA); + SetTrieValue(hTrie, "mediumblue", 0x0000CD); + SetTrieValue(hTrie, "mediumorchid", 0xBA55D3); + SetTrieValue(hTrie, "mediumpurple", 0x9370D8); + SetTrieValue(hTrie, "mediumseagreen", 0x3CB371); + SetTrieValue(hTrie, "mediumslateblue", 0x7B68EE); + SetTrieValue(hTrie, "mediumspringgreen", 0x00FA9A); + SetTrieValue(hTrie, "mediumturquoise", 0x48D1CC); + SetTrieValue(hTrie, "mediumvioletred", 0xC71585); + SetTrieValue(hTrie, "midnightblue", 0x191970); + SetTrieValue(hTrie, "mintcream", 0xF5FFFA); + SetTrieValue(hTrie, "mistyrose", 0xFFE4E1); + SetTrieValue(hTrie, "moccasin", 0xFFE4B5); + SetTrieValue(hTrie, "mythical", 0x8847FF); // same as Mythical item rarity in Dota 2 + SetTrieValue(hTrie, "navajowhite", 0xFFDEAD); + SetTrieValue(hTrie, "navy", 0x000080); + SetTrieValue(hTrie, "normal", 0xB2B2B2); // same as Normal item quality in TF2 + SetTrieValue(hTrie, "oldlace", 0xFDF5E6); + SetTrieValue(hTrie, "olive", 0x9EC34F); + SetTrieValue(hTrie, "olivedrab", 0x6B8E23); + SetTrieValue(hTrie, "orange", 0xFFA500); + SetTrieValue(hTrie, "orangered", 0xFF4500); + SetTrieValue(hTrie, "orchid", 0xDA70D6); + SetTrieValue(hTrie, "palegoldenrod", 0xEEE8AA); + SetTrieValue(hTrie, "palegreen", 0x98FB98); + SetTrieValue(hTrie, "paleturquoise", 0xAFEEEE); + SetTrieValue(hTrie, "palevioletred", 0xD87093); + SetTrieValue(hTrie, "papayawhip", 0xFFEFD5); + SetTrieValue(hTrie, "peachpuff", 0xFFDAB9); + SetTrieValue(hTrie, "peru", 0xCD853F); + SetTrieValue(hTrie, "pink", 0xFFC0CB); + SetTrieValue(hTrie, "plum", 0xDDA0DD); + SetTrieValue(hTrie, "powderblue", 0xB0E0E6); + SetTrieValue(hTrie, "purple", 0x800080); + SetTrieValue(hTrie, "rare", 0x4B69FF); // same as Rare item rarity in Dota 2 + SetTrieValue(hTrie, "red", 0xFF4040); // same as RED/Terrorist team color + SetTrieValue(hTrie, "rosybrown", 0xBC8F8F); + SetTrieValue(hTrie, "royalblue", 0x4169E1); + SetTrieValue(hTrie, "saddlebrown", 0x8B4513); + SetTrieValue(hTrie, "salmon", 0xFA8072); + SetTrieValue(hTrie, "sandybrown", 0xF4A460); + SetTrieValue(hTrie, "seagreen", 0x2E8B57); + SetTrieValue(hTrie, "seashell", 0xFFF5EE); + SetTrieValue(hTrie, "selfmade", 0x70B04A); // same as Self-Made item quality in TF2 + SetTrieValue(hTrie, "sienna", 0xA0522D); + SetTrieValue(hTrie, "silver", 0xC0C0C0); + SetTrieValue(hTrie, "skyblue", 0x87CEEB); + SetTrieValue(hTrie, "slateblue", 0x6A5ACD); + SetTrieValue(hTrie, "slategray", 0x708090); + SetTrieValue(hTrie, "slategrey", 0x708090); + SetTrieValue(hTrie, "snow", 0xFFFAFA); + SetTrieValue(hTrie, "springgreen", 0x00FF7F); + SetTrieValue(hTrie, "steelblue", 0x4682B4); + SetTrieValue(hTrie, "strange", 0xCF6A32); // same as Strange item quality in TF2 + SetTrieValue(hTrie, "tan", 0xD2B48C); + SetTrieValue(hTrie, "teal", 0x008080); + SetTrieValue(hTrie, "thistle", 0xD8BFD8); + SetTrieValue(hTrie, "tomato", 0xFF6347); + SetTrieValue(hTrie, "turquoise", 0x40E0D0); + SetTrieValue(hTrie, "uncommon", 0xB0C3D9); // same as Uncommon item rarity in Dota 2 + SetTrieValue(hTrie, "unique", 0xFFD700); // same as Unique item quality in TF2 + SetTrieValue(hTrie, "unusual", 0x8650AC); // same as Unusual item quality in TF2 + SetTrieValue(hTrie, "valve", 0xA50F79); // same as Valve item quality in TF2 + SetTrieValue(hTrie, "vintage", 0x476291); // same as Vintage item quality in TF2 + SetTrieValue(hTrie, "violet", 0xEE82EE); + SetTrieValue(hTrie, "wheat", 0xF5DEB3); + SetTrieValue(hTrie, "white", 0xFFFFFF); + SetTrieValue(hTrie, "whitesmoke", 0xF5F5F5); + SetTrieValue(hTrie, "yellow", 0xFFFF00); + SetTrieValue(hTrie, "yellowgreen", 0x9ACD32); + return hTrie; +} \ No newline at end of file diff --git a/GRAB/StAC/scripting/stac.sp b/GRAB/StAC/scripting/stac.sp new file mode 100644 index 0000000..d9fc604 --- /dev/null +++ b/GRAB/StAC/scripting/stac.sp @@ -0,0 +1,1536 @@ +// see the readme for more info: +// https://github.com/stephanieLGBT/StAC-tf2/blob/master/README.md +// i love my partners +#pragma semicolon 1 + +#include +#include +#include +#include +#include +#include +#include +#undef REQUIRE_PLUGIN +#include +#include + +#define PLUGIN_VERSION "3.1.2" +#define UPDATE_URL "https://raw.githubusercontent.com/stephanieLGBT/StAC-tf2/master/updatefile.txt" + +public Plugin myinfo = +{ + name = "Steph's AntiCheat (StAC)", + author = "stephanie", + description = "Anticheat plugin [tf2 only] written by Stephanie. Originally forked from IntegriTF2 by Miggy (RIP)", + version = PLUGIN_VERSION, + url = "https://steph.anie.dev/" +} + +// TIMER HANDLES +Handle g_hQueryTimer[MAXPLAYERS+1]; +Handle g_hTriggerTimedStuffTimer; +// TPS INFO +float tickinterv; +float tps; +// DETECTIONS PER CLIENT +int turnTimes[MAXPLAYERS+1]; +int fovDesired[MAXPLAYERS+1]; +int fakeAngDetects[MAXPLAYERS+1]; +int pSilentDetects[MAXPLAYERS+1]; +int aimSnapDetects[MAXPLAYERS+1]; +// TIME SINCE LAST ACTION PER CLIENT +float timeSinceSpawn[MAXPLAYERS+1]; +float timeSinceTaunt[MAXPLAYERS+1]; +float timeSinceTeled[MAXPLAYERS+1]; +// STORED ANGLES PER CLIENT +float angCur[MAXPLAYERS+1][2]; +float angPrev1[MAXPLAYERS+1][2]; +float angPrev2[MAXPLAYERS+1][2]; +// STORED VARS FOR INDIVIDUAL CLIENTS +bool playerTaunting[MAXPLAYERS+1]; +float interpFor[MAXPLAYERS+1] = -1.0; +float interpRatioFor[MAXPLAYERS+1] = -1.0; +float updaterateFor[MAXPLAYERS+1] = -1.0; +float REALinterpFor[MAXPLAYERS+1] = -1.0; +bool userBanQueued[MAXPLAYERS+1]; + +// SOURCEBANS BOOL +bool SOURCEBANS; + +// CVARS +ConVar stac_enabled; +ConVar stac_verbose_info; +ConVar stac_autoban_enabled; +ConVar stac_max_allowed_turn_secs; +ConVar stac_kick_for_pingmasking; +ConVar stac_max_psilent_detections; +ConVar stac_max_fakeang_detections; +ConVar stac_min_interp_ms; +ConVar stac_max_interp_ms; +ConVar stac_min_randomcheck_secs; +ConVar stac_max_randomcheck_secs; + +// VARIOUS DETECTION BOUNDS & CVAR VALUES +bool DEBUG = false; +bool autoban = true; +float maxAllowedTurnSecs = -1.0; +bool kickForPingMasking = false; +int maxPsilentDetections = 15; +int maxFakeAngDetections = 10; +int min_interp_ms = -1; +int max_interp_ms = 101; +// RANDOM CVARS CHECK MIN/MAX BOUNDS (in seconds) +float minRandCheckVal = 60.0; +float maxRandCheckVal = 300.0; + + +// STORED VALUES FOR "sv_client_min/max_interp_ratio" (defaults to -2 for sanity checking) +int MinInterpRatio = -2; +int MaxInterpRatio = -2; +bool NoVRAD = false; + +// STUFF FOR FUTURE AIMSNAP TEST +//float sensFor[MAXPLAYERS+1] = -1.0; +//float maxSensToCheck = 4.0; + +public OnPluginStart() +{ + if (GetEngineVersion() != Engine_TF2) + { + SetFailState("[StAC] This plugin is only supported for TF2! Aborting!"); + } + + // updater + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATE_URL); + } + + // get rid of any possible exploits by using teleporters and fov + SetConVarInt(FindConVar("tf_teleporter_fov_start"), 90); + SetConVarFloat(FindConVar("tf_teleporter_fov_time"), 0.0); + // reg admin commands + RegAdminCmd("sm_stac_checkall", ForceCheckAll, ADMFLAG_GENERIC, "Force check all client convars (ALL CLIENTS) for anticheat stuff"); + RegAdminCmd("sm_stac_detections", ShowDetections, ADMFLAG_GENERIC, "Show all current detections on all connected clients"); + // RegAdminCmd("sm_forcecheck", ForceCheck, ADMFLAG_GENERIC, "Force check all client convars (SINGLE CLIENT) for anticheat stuff"); + // get tick interval - some modded tf2 servers run at >66.7 tick! + tickinterv = GetTickInterval(); + // reset random server seed + ActuallySetRandomSeed(); + // grab round start events for calculating tps + HookEvent("teamplay_round_start", eRoundStart); + // grab player spawns + HookEvent("player_spawn", ePlayerSpawned); + // grab player teleports + HookEvent("player_teleported", ePlayerTeled); + // check sourcebans capibility + CreateTimer(2.0, checkSourceBans); + // check EVERYONE's cvars on plugin reload + CreateTimer(3.0, checkEveryone); + // hook interp ratio cvars + MinInterpRatio = GetConVarInt(FindConVar("sv_client_min_interp_ratio")); + MaxInterpRatio = GetConVarInt(FindConVar("sv_client_max_interp_ratio")); + NoVRAD = GetConVarBool(FindConVar("mat_fullbright")); + HookConVarChange(FindConVar("sv_client_min_interp_ratio"), GenericCvarChanged); + HookConVarChange(FindConVar("sv_client_max_interp_ratio"), GenericCvarChanged); + HookConVarChange(FindConVar("mat_fullbright"), GenericCvarChanged); + // Create ConVars for adjusting settings + initCvars(); + // load translations + LoadTranslations("stac.phrases.txt"); + LogMessage("[StAC] Plugin loaded"); +} + +initCvars() +{ + AutoExecConfig_SetFile("stac"); + AutoExecConfig_SetCreateFile(true); + + char buffer[16]; + // plugin enabled + stac_enabled = + AutoExecConfig_CreateConVar + ( + "stac_enabled", + "1", + "[StAC] enable/disable plugin (setting this to 0 immediately unloads stac.smx)", + FCVAR_NONE, + true, + 0.0, + true, + 1.0 + ); + HookConVarChange(stac_enabled, stacVarChanged); + // verbose mode + if (DEBUG) + { + buffer = "1"; + } + else if (!DEBUG) + { + buffer = "0"; + } + stac_verbose_info = + AutoExecConfig_CreateConVar + ( + "stac_verbose_info", + buffer, + "[StAC] enable/disable showing verbose info about players' cvars and other similar info in admin console\n(recommended 1)", + FCVAR_NONE, + true, + 0.0, + true, + 1.0 + ); + HookConVarChange(stac_verbose_info, stacVarChanged); + // autoban + if (autoban) + { + buffer = "1"; + } + else if (!autoban) + { + buffer = "0"; + } + stac_autoban_enabled = + AutoExecConfig_CreateConVar + ( + "stac_autoban_enabled", + buffer, + "[StAC] enable/disable autobanning for anything at all\n(recommended 1)", + FCVAR_NONE, + true, + 0.0, + true, + 1.0 + ); + HookConVarChange(stac_autoban_enabled, stacVarChanged); + // turn seconds + FloatToString(maxAllowedTurnSecs, buffer, sizeof(buffer)); + stac_max_allowed_turn_secs = + AutoExecConfig_CreateConVar + ( + "stac_max_allowed_turn_secs", + buffer, + "[StAC] maximum allowed time in seconds before client is autokicked for using turn binds (+right/+left inputs). -1 to disable autokicking, 0 instakicks\n(recommended -1.0 unless you're using this in a competitive setting)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_allowed_turn_secs, stacVarChanged); + // pingmasking + if (kickForPingMasking) + { + buffer = "1"; + } + else if (!kickForPingMasking) + { + buffer = "0"; + } + stac_kick_for_pingmasking = + AutoExecConfig_CreateConVar + ( + "stac_kick_for_pingmasking", + buffer, + "[StAC] kick clients for masking their ping with nonnumerical characters in their cl_cmdrate cvar\n(defaults to 0)", + FCVAR_NONE, + true, + 0.0, + true, + 1.0 + ); + HookConVarChange(stac_kick_for_pingmasking, stacVarChanged); + // psilent detections + IntToString(maxPsilentDetections, buffer, sizeof(buffer)); + stac_max_psilent_detections = + AutoExecConfig_CreateConVar + ( + "stac_max_psilent_detections", + buffer, + "[StAC] maximum silent aim/norecoil detecions before banning a client. -1 to disable\n(recommended 15 or higher)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_psilent_detections, stacVarChanged); + // fakeang detections + IntToString(maxFakeAngDetections, buffer, sizeof(buffer)); + stac_max_fakeang_detections = + AutoExecConfig_CreateConVar + ( + "stac_max_fakeang_detections", + buffer, + "[StAC] maximum fake angle / wrong angle detecions before banning a client. -1 to disable\n(recommended 10)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_fakeang_detections, stacVarChanged); + // min interp + IntToString(min_interp_ms, buffer, sizeof(buffer)); + stac_min_interp_ms = + AutoExecConfig_CreateConVar + ( + "stac_min_interp_ms", + buffer, + "[StAC] minimum interp (lerp) in milliseconds that a client is allowed to have before getting autokicked. set this to -1 to disable having a min interp\n(recommended disabled, but if you want to enable it, feel free. interp values below 15.1515151 ms don't seem to have any noticable effects on anything meaningful)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_min_interp_ms, stacVarChanged); + // min interp + IntToString(max_interp_ms, buffer, sizeof(buffer)); + stac_max_interp_ms = + AutoExecConfig_CreateConVar + ( + "stac_max_interp_ms", + buffer, + "[StAC] maximum interp (lerp) in milliseconds that a client is allowed to have before getting autokicked. set this to -1 to disable having a max interp\n(recommended 101)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_interp_ms, stacVarChanged); + // min random check secs + FloatToString(minRandCheckVal, buffer, sizeof(buffer)); + stac_min_randomcheck_secs = + AutoExecConfig_CreateConVar + ( + "stac_min_randomcheck_secs", + buffer, + "[StAC] check AT LEAST this often in seconds for clients with violating cvar values/netprops\n(recommended 60)", + FCVAR_NONE, + true, + 5.0, + false, + _ + ); + HookConVarChange(stac_min_randomcheck_secs, stacVarChanged); + // min random check secs + FloatToString(maxRandCheckVal, buffer, sizeof(buffer)); + stac_max_randomcheck_secs = + AutoExecConfig_CreateConVar + ( + "stac_max_randomcheck_secs", + buffer, + "[StAC] check AT MOST this often in seconds for clients with violating cvar values/netprops\n(recommended 300)", + FCVAR_NONE, + true, + 15.0, + false, + _ + ); + HookConVarChange(stac_max_randomcheck_secs, stacVarChanged); + + // actually exec the cfg after initing cvars lol + AutoExecConfig_ExecuteFile(); + AutoExecConfig_CleanFile(); +} + +stacVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (convar == stac_enabled) + { + if (StringToInt(newValue) != 1) + { + LogMessage("[StAC] unloading plugin!!!"); + ServerCommand("sm plugins unload stac"); + } + } + if (convar == stac_verbose_info) + { + if (StringToInt(newValue) != 1) + { + DEBUG = false; + } + else if (StringToInt(newValue) == 1) + { + DEBUG = true; + } + } + else if (convar == stac_autoban_enabled) + { + if (StringToInt(newValue) != 1) + { + autoban = false; + } + else if (StringToInt(newValue) == 1) + { + autoban = true; + } + } + // clamp to -1, 0, or higher + else if (convar == stac_max_allowed_turn_secs) + { + if (StringToFloat(newValue) < 0.0 && StringToFloat(newValue) != -1.0) + { + maxAllowedTurnSecs = 0.0; + } + else + { + maxAllowedTurnSecs = StringToFloat(newValue); + } + } + // either or + else if (convar == stac_kick_for_pingmasking) + { + if (StringToInt(newValue) != 1) + { + kickForPingMasking = false; + } + else if (StringToInt(newValue) == 1) + { + kickForPingMasking = true; + } + } + // clamp to -1 if 0 + else if (convar == stac_max_psilent_detections) + { + if (StringToInt(newValue) == 0) + { + maxPsilentDetections = 1; + } + else + { + maxPsilentDetections = StringToInt(newValue); + } + } + // clamp to -1 if 0 + else if (convar == stac_max_fakeang_detections) + { + if (StringToInt(newValue) == 0) + { + maxFakeAngDetections = -1; + } + else + { + maxFakeAngDetections = StringToInt(newValue); + } + } + // clamp to -1 if 0 + else if (convar == stac_min_interp_ms) + { + if (StringToInt(newValue) == 0) + { + min_interp_ms = -1; + } + else + { + min_interp_ms = StringToInt(newValue); + } + } + // clamp to -1 if 0 + else if (convar == stac_max_interp_ms) + { + if (StringToInt(newValue) == 0) + { + max_interp_ms = -1; + } + else + { + max_interp_ms = StringToInt(newValue); + } + } + // these have a cvar set minimum we don't need to clamp them + else if (convar == stac_min_randomcheck_secs) + { + minRandCheckVal = StringToFloat(newValue); + } + else if (convar == stac_max_randomcheck_secs) + { + maxRandCheckVal = StringToFloat(newValue); + } +} + +GenericCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (convar == FindConVar("sv_client_min_interp_ratio")) + { + MinInterpRatio = StringToInt(newValue); + } + else if (convar == FindConVar("sv_client_min_interp_ratio")) + { + MaxInterpRatio = StringToInt(newValue); + } + else if (convar == FindConVar("mat_fullbright")) + { + if (StringToInt(newValue) != 0) + { + NoVRAD = true; + } + else + { + NoVRAD = false; + } + } +} + +public Action checkSourceBans(Handle timer) +{ + if (GetFeatureStatus(FeatureType_Native, "SBPP_BanPlayer") == FeatureStatus_Available) + { + SOURCEBANS = true; + if (DEBUG) + { + LogMessage("[StAC] Sourcebans detected! Using Sourcebans as default ban handler."); + } + } + else + { + SOURCEBANS = false; + if (DEBUG) + { + LogMessage("[StAC] No Sourcebans installation detected! Using TF2's default ban handler."); + } + } +} + +public Action checkEveryone(Handle timer) +{ + ForceCheckAll(0, 0); +} + +public Action ShowDetections(int callingCl, int args) +{ + ReplyToCommand(callingCl, "[StAC] == CURRENT DETECTIONS == "); + for (int Cl = 0; Cl < MaxClients + 1; Cl++) + { + if (IsValidClient(Cl)) + { + if ( + turnTimes[Cl] >= 1 + || pSilentDetects[Cl] >= 1 + || fakeAngDetects[Cl] >= 1 + ) + { + ReplyToCommand(callingCl, "Detections for %L", Cl); + if (turnTimes[Cl] >= 1) + { + ReplyToCommand(callingCl, "- %i turnTimes (frames) for %N", turnTimes[Cl], Cl); + } + if (pSilentDetects[Cl] >= 1) + { + ReplyToCommand(callingCl, "- %i pSilentDetects for %N", pSilentDetects[Cl], Cl); + } + if (fakeAngDetects[Cl] >= 1) + { + ReplyToCommand(callingCl, "- %i fakeAngDetects for %N", fakeAngDetects[Cl], Cl); + } + } + } + } + ReplyToCommand(callingCl, "[StAC] == END DETECTIONS == "); +} + +public OnPluginEnd() +{ + NukeTimers(); + OnMapEnd(); + LogMessage("[StAC] Plugin unloaded"); +} + +// reseed random server seed to help prevent certain nospread stuff from working (probably) +ActuallySetRandomSeed() +{ + int seed = GetURandomInt(); + if (DEBUG) + { + LogMessage("[StAC] setting random server seed to %i", seed); + } + SetRandomSeed(seed); +} + +// NUKE the client timers from orbit on plugin and map reload +NukeTimers() +{ + for (int Cl = 0; Cl < MaxClients + 1; Cl++) + { + if (g_hQueryTimer[Cl] != null) + { + if (DEBUG) + { + LogMessage("[StAC] Destroying timer for %L", Cl); + } + KillTimer(g_hQueryTimer[Cl]); + g_hQueryTimer[Cl] = null; + } + } + if (g_hTriggerTimedStuffTimer != null) + { + if (DEBUG) + { + LogMessage("[StAC] Destroying reseeding timer"); + } + KillTimer(g_hTriggerTimedStuffTimer); + g_hTriggerTimedStuffTimer = null; + } +} + +// recreate the timers we just nuked +ResetTimers() +{ + for (int Cl = 0; Cl < MaxClients + 1; Cl++) + { + if (IsValidClient(Cl)) + { + if (DEBUG) + { + LogMessage("[StAC] Creating timer for %L", Cl); + } + g_hQueryTimer[Cl] = CreateTimer(GetRandomFloat(minRandCheckVal, maxRandCheckVal), Timer_CheckClientConVars, GetClientUserId(Cl)); + } + } + // create timer to reset seed every 15 mins + g_hTriggerTimedStuffTimer = CreateTimer(900.0, timer_TriggerTimedStuff, _, TIMER_REPEAT); +} + +public Action eRoundStart(Handle event, char[] name, bool dontBroadcast) +{ + DoTPSMath(); + // might as well do this here! + ActuallySetRandomSeed(); +} + +public Action ePlayerSpawned(Handle event, char[] name, bool dontBroadcast) +{ + int Cl = GetClientOfUserId(GetEventInt(event, "userid")); + if (IsValidClient(Cl)) + { + timeSinceSpawn[Cl] = GetEngineTime(); + } +} + +public Action ePlayerTeled(Handle event, char[] name, bool dontBroadcast) +{ + int Cl = GetClientOfUserId(GetEventInt(event, "userid")); + if (IsValidClient(Cl)) + { + timeSinceTeled[Cl] = GetEngineTime(); + } +} + +public TF2_OnConditionAdded(int Cl, TFCond condition) +{ + if (IsValidClient(Cl)) + { + if (condition == TFCond_Taunting) + { + playerTaunting[Cl] = true; + } + } +} + +public TF2_OnConditionRemoved(int Cl, TFCond condition) +{ + if (IsValidClient(Cl)) + { + if (condition == TFCond_Taunting) + { + timeSinceTaunt[Cl] = GetEngineTime(); + playerTaunting[Cl] = false; + } + } +} + +public Action timer_TriggerTimedStuff(Handle timer) +{ + ActuallySetRandomSeed(); +} + +// set stuff for tps based checking here. +DoTPSMath() +{ + tickinterv = GetTickInterval(); + tps = Pow(tickinterv, -1.0); + if (DEBUG) + { + LogMessage("tickinterv %f tps %f", tickinterv, tps); + } +} + +public OnMapStart() +{ + ActuallySetRandomSeed(); + DoTPSMath(); + ResetTimers(); +} + +public OnMapEnd() +{ + ActuallySetRandomSeed(); + DoTPSMath(); + NukeTimers(); +} + +public OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +} + +ClearClBasedVars(userid) +{ + // get fresh cli id + int Cl = GetClientOfUserId(userid); + // clear all old values for cli id based stuff + turnTimes[Cl] = 0; + pSilentDetects[Cl] = 0; + fakeAngDetects[Cl] = 0; + aimSnapDetects[Cl] = 0; + timeSinceSpawn[Cl] = 0.0; + timeSinceTaunt[Cl] = 0.0; + timeSinceTeled[Cl] = 0.0; + interpFor[Cl] = -1.0; + interpRatioFor[Cl] = -1.0; + updaterateFor[Cl] = -1.0; + REALinterpFor[Cl] = -1.0; + userBanQueued[Cl] = false; +} + +public OnClientPostAdminCheck(Cl) +{ + if (IsValidClient(Cl)) + { + int userid = GetClientUserId(Cl); + // TODO - test this and see if it's accurate enough to autokick people with + char ip[17]; + char country_name[45]; + GetClientIP(Cl, ip, sizeof(ip)); + GeoipCountry(ip, country_name, sizeof(country_name)); + if ( + StrContains(country_name, "Anonymous", false) != -1 || + StrContains(country_name, "Proxy", false) != -1 + ) + { + PrintToImportant("{hotpink}[StAC]{white} Player %N is likely using a proxy!", Cl); + } + // clear per client values + ClearClBasedVars(userid); + // clear timer + g_hQueryTimer[Cl] = null; + // query convars on player connect + LogMessage("[StAC] %N joined. Checking cvars", Cl); + g_hQueryTimer[Cl] = CreateTimer(GetRandomFloat(minRandCheckVal, maxRandCheckVal), Timer_CheckClientConVars, userid); + } +} + +public OnClientDisconnect(Cl) +{ + int userid = GetClientUserId(Cl); + // clear per client values + ClearClBasedVars(userid); + if (g_hQueryTimer[Cl] != null) + { + KillTimer(g_hQueryTimer[Cl]); + g_hQueryTimer[Cl] = null; + } +} + +public Action ForceCheckAll(int client, int args) +{ + QueryEverythingAllClients(); + ReplyToCommand(client, "[StAC] Checking cvars on all clients."); +} + +/* +in OnPlayerRunCmd, we check for: +- SILENT AIM +- FAKE ANGLES +- TURN BINDS +- (EVENTUALLY) AIM SNAPS +*/ +public Action OnPlayerRunCmd + ( + int Cl, + int& buttons, + int& impulse, + float vel[3], + float angles[3], + int& weapon, + int& subtype, + int& cmdnum, + int& tickcount, + int& seed, + int mouse[2] + ) +{ + // grab current time to compare to time since last spawn/taunt/tele + float engineTime = GetEngineTime(); + if ( + // make sure client is real, not a bot, on a team, AND didnt spawn, taunt, or teleport in the last .1 seconds AND isnt taunting AND isn't already queued to be banned + IsValidClient(Cl) + && IsClientPlaying(Cl) + && !playerTaunting[Cl] + && engineTime - 0.1 > timeSinceSpawn[Cl] + && engineTime - 0.1 > timeSinceTaunt[Cl] + && engineTime - 0.1 > timeSinceTeled[Cl] + && !userBanQueued[Cl] + ) + { + // we need this later for decrimenting psilent and fakeang detections after 20 minutes! + int userid = GetClientUserId(Cl); + + // grab angles (probably expensive but who cares) + // thanks to nosoop from the sm discord for some help with this + angPrev2[Cl][0] = angPrev1[Cl][0]; + angPrev2[Cl][1] = angPrev1[Cl][1]; + angPrev1[Cl][0] = angCur[Cl][0]; + angPrev1[Cl][1] = angCur[Cl][1]; + angCur[Cl][0] = angles[0]; + angCur[Cl][1] = angles[1]; + /* + SILENT AIM DETECTION + silent aim works by aimbotting for 1 frame and then snapping your viewangle back to what it was + example snap: + L 03/25/2020 - 06:03:50: [stac.smx] [StAC] pSilent detection: curang angles: x 5.120096 y 9.763162 + L 03/25/2020 - 06:03:50: [stac.smx] [StAC] pSilent detection: prev1 angles: x 1.635611 y 12.876886 + L 03/25/2020 - 06:03:50: [stac.smx] [StAC] pSilent detection: prev2 angles: x 5.120096 y 9.763162 + we can just look for these snaps and log them as detections! + note that this won't detect some snaps when a player is moving their strafe keys and mouse @ the same time while they are aimlocking. + i'll *try* to work mouse movement into this function at SOME point but it works reasonably well for right now. + */ + if + ( + // so the current and 2nd previous angles match... + ( + angCur[Cl][0] == angPrev2[Cl][0] + && + angCur[Cl][1] == angPrev2[Cl][1] + ) + && + // BUT the 1st previous (in between) angle doesnt? + ( + + angPrev1[Cl][0] != angCur[Cl][0] + && angPrev1[Cl][1] != angCur[Cl][0] + && angPrev1[Cl][0] != angPrev2[Cl][0] + && angPrev1[Cl][1] != angPrev2[Cl][1] + ) + && + // make sure we dont get any fake detections on startup (might not really be needed? but just in case) + // this also ignores weird angle resets in mge / dm + ( + angCur[Cl][0] != 0.000000 + && angCur[Cl][1] != 0.000000 + && angPrev1[Cl][0] != 0.000000 + && angPrev1[Cl][1] != 0.000000 + && angPrev2[Cl][0] != 0.000000 + && angPrev2[Cl][1] != 0.000000 + ) + ) + /* + ok - lets make sure there's a difference of at least 0.5 degrees on either axis to avoid most fake detections + these are probably caused by packets arriving out of order but i'm not a fucking network engineer (yet) so idk + examples of fake detections we want to avoid: + 03/25/2020 - 18:18:11: [stac.smx] [StAC] pSilent detection on [redacted]: curang angles: x 14.871331 y 154.979812 + 03/25/2020 - 18:18:11: [stac.smx] [StAC] pSilent detection on [redacted]: prev1 angles: x 14.901910 y 155.010391 + 03/25/2020 - 18:18:11: [stac.smx] [StAC] pSilent detection on [redacted]: prev2 angles: x 14.871331 y 154.979812 + and + 03/25/2020 - 22:16:36: [stac.smx] [StAC] pSilent detection on [redacted2]: curang angles: x 21.516006 y -140.723709 + 03/25/2020 - 22:16:36: [stac.smx] [StAC] pSilent detection on [redacted2]: prev1 angles: x 21.560007 y -140.943710 + 03/25/2020 - 22:16:36: [stac.smx] [StAC] pSilent detection on [redacted2]: prev2 angles: x 21.516006 y -140.723709 + doing this might make it harder to detect legitcheaters but like. legitcheating in a 12 yr old dead game OMEGALUL who fucking cares + */ + { + // refactored from smac - make sure we don't fuck up angles near the x/y axes! + float aDiff[2]; + aDiff[0] = angCur[Cl][0] - angPrev1[Cl][0]; + aDiff[1] = angCur[Cl][1] - angPrev1[Cl][1]; + if (aDiff[0] > 180.0) + { + aDiff[0] = FloatAbs(aDiff[0] - 360); + } + if (aDiff[1] > 180.0) + { + aDiff[1] = FloatAbs(aDiff[1] - 360); + } + // actual angle calculation here + // needs to be more than a degree + if (aDiff[0] >= 1.0 || aDiff[1] >= 1.0) + { + pSilentDetects[Cl]++; + // have this detection expire in 20 minutes + CreateTimer(1200.0, Timer_decr_pSilent, userid); + // print a bunch of bullshit + PrintToImportant("{hotpink}[StAC]{white} pSilent / NoRecoil detection on %N.\nDetections so far: {palegreen}%i", Cl, pSilentDetects[Cl]); + PrintToImportant("|----- curang angles: x %f y %f", angCur[Cl][0], angCur[Cl][1]); + PrintToImportant("|----- prev 1 angles: x %f y %f", angPrev1[Cl][0], angPrev1[Cl][1]); + PrintToImportant("|----- prev 2 angles: x %f y %f", angPrev2[Cl][0], angPrev2[Cl][1]); + // BAN USER if they trigger too many detections + if (pSilentDetects[Cl] >= maxPsilentDetections && maxPsilentDetections != -1) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "pSilentBanMsg", Cl, pSilentDetects[Cl]); + BanUser(userid, reason); + CPrintToChatAll("%t", "pSilentBanAllChat", Cl, pSilentDetects[Cl]); + LogMessage("[StAC] Player %N was banned for using pSilent or NoRecoil! Total detections: %i.", Cl, pSilentDetects[Cl]); + } + } + } + /* + BASIC AIMSNAP TEST (not currently hooked up) + */ + /* + float SmouseFl[2]; + SmouseFl[0] = float(abs(mouse[0])) / sensFor[Cl]; + SmouseFl[1] = float(abs(mouse[1])) / sensFor[Cl]; + PrintToServer("mouse0 %f mouse1 %f", SmouseFl[0], SmouseFl[1]); + //PrintToServer("raw : x %f y %f, mouse0 %i, mouse1 %i", angCur[Cl][0], angCur[Cl][1], mouse[0], mouse[1]); + //PrintToServer("sc : x %f y %f, mouse0 %f, mouse1 %f", angCur[Cl][0], angCur[Cl][1], mouseFl[0] / sensFor[Cl], mouseFl[1] / sensFor[Cl]); + if ( + // ignore stupidly high sens players + // technically can be spoofed but also it would be annoying for the cheater + ( + // def "max sens" is 4 + //sensFor[Cl] < maxSensToCheck && + sensFor[Cl] != -1 + ) + && + ( + // hopefully detect snaps of over 10.0 degrees + FloatAbs(FloatAbs(angCur[Cl][0]) - FloatAbs(angPrev1[Cl][0])) > 10.0 || + FloatAbs(FloatAbs(angCur[Cl][1]) - FloatAbs(angPrev1[Cl][1])) > 10.0 + ) + && + // make sure theres minimal mouse movement (less than 20 scaled to client sens) + ( + abs(mouse[0]) / sensFor[Cl] < 20 && + abs(mouse[1]) / sensFor[Cl] < 20 + ) + && + // ignore angle resets + ( + angCur[Cl][0] != 0.000000 && + angCur[Cl][1] != 0.000000 && + angPrev1[Cl][0] != 0.000000 && + angPrev1[Cl][1] != 0.000000 && + angPrev2[Cl][0] != 0.000000 && + angPrev2[Cl][1] != 0.000000 + ) + ) + { + // check if we hit a player here + if (ClDidHitPlayer(Cl)) + // maybe use TR_DidHit ? i dont know + { + aimSnapDetects[Cl]++; + PrintToChatAll("aimSnapDetects = %i", aimSnapDetects[Cl]); + PrintColoredChatAll("[StAC] snap %f d on %N: curang angles: x %f y %f", (FloatAbs(angCur[Cl][0] - angPrev1[Cl][0])), Cl, angCur[Cl][0], angCur[ Cl][1]); + PrintColoredChatAll("[StAC] snap on %N: prev1 angles: x %f y %f", Cl, angPrev1[Cl][0], angPrev1[Cl][1]); + PrintColoredChatAll("[StAC] snap on %N: prev2 angles: x %f y %f", Cl, angPrev2[Cl][0], angPrev2[Cl][1]); + PrintColoredChatAll("[StAC] mouse movement at time of snap: mouse x %i, y %i", abs(mouse[0]), abs(mouse[1])); + } + } + */ + /* + EYE ANGLES TEST + if clients are outside of allowed angles in tf2, which are + +/- 89.0 x (up / down) + +/- 180 y (left / right, but we don't check this atm because there's things that naturally fuck up y angles) + +/- 50 z (roll / tilt) + while they are not in spec & on a map camera, we should log it. + we would fix it but cheaters can just ignore server-enforced viewangle changes so there's no point + + these bounds were lifted from lilac. Thanks lilac + */ + if ( + ( + angles[0] < -89.01 + || angles[0] > 89.01 + || angles[2] < -50.01 + || angles[2] > 50.01 + ) + ) + { + fakeAngDetects[Cl]++; + PrintToImportant("{hotpink}[StAC]{white} Player %N has {mediumpurple}invalid eye angles{white}!\nCurrent angles: {mediumpurple}%.2f %.2f %.2f{white}.\nDetections so far: {palegreen}%i", Cl, angles[0], angles[1], angles[2], fakeAngDetects[Cl]); + LogMessage("[StAC] Player %N has invalid eye angles! Current angles: %.2f %.2f %.2f Detections so far: %i", Cl, angles[0], angles[1], angles[2], fakeAngDetects[Cl]); + if (fakeAngDetects[Cl] >= maxFakeAngDetections && maxFakeAngDetections != -1) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "fakeangBanMsg", Cl, fakeAngDetects[Cl]); + BanUser(userid, reason); + CPrintToChatAll("%t", "fakeangBanAllChat", Cl, fakeAngDetects[Cl]); + LogMessage( "%t", "fakeangBanMsg", Cl, fakeAngDetects[Cl]); + } + } + /* + TURN BIND TEST + */ + if (buttons & IN_LEFT || buttons & IN_RIGHT) + { + if (maxAllowedTurnSecs != -1.0) + { + turnTimes[Cl]++; + float turnSec = turnTimes[Cl] * tickinterv; + PrintToImportant("%t", "turnbindAdminMsg", Cl, turnSec); + if (turnSec < maxAllowedTurnSecs) + { + CPrintToChat(Cl, "%t", "turnbindWarnPlayer"); + } + else if (turnSec >= maxAllowedTurnSecs) + { + KickClient(Cl, "%t", "turnbindKickMsg"); + LogMessage("%t", "turnbindLogMsg", Cl); + CPrintToChatAll("%t", "turnbindAllChat", Cl); + } + } + } + } +} + +public Action Timer_decr_pSilent(Handle timer, any userid) +{ + int Cl = GetClientOfUserId(userid); + + if (IsValidClient(Cl)) + { + if (fakeAngDetects[Cl] > 0) + { + pSilentDetects[Cl]--; + } + } +} + +public Action Timer_decrFakeAngs(Handle timer, any userid) +{ + int Cl = GetClientOfUserId(userid); + + if (IsValidClient(Cl)) + { + if (fakeAngDetects[Cl] > 0) + { + fakeAngDetects[Cl]--; + } + } +} + +char cvarsToCheck[][] = +{ + // possible cheat vars + "cl_interpolate", + "r_drawothermodels", + "fov_desired", + "mat_fullbright", + "cl_thirdperson", + // network cvars + "cl_interp", + "cl_cmdrate", + "cl_interp_ratio", + "cl_updaterate", + // other vars + //"sensitivity" +}; + +public ConVarCheck(QueryCookie cookie, int Cl, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue) +{ + // don't bother checking bots or users who already queued to be banned + if (!IsValidClient(Cl) || userBanQueued[Cl]) + { + return; + } + int userid = GetClientUserId(Cl); + // log something about cvar errors xcept for cheat only cvars + if (result != ConVarQuery_Okay) + { + PrintToImportant("{hotpink}[StAC]{white} Could not query CVar %s on Player %N", Cl); + LogMessage("[StAC] Could not query CVar %s on Player %N", cvarName, Cl); + } + /* + POSSIBLE CHEAT VARS + */ + // cl_interpolate (hidden cvar! should NEVER not be 1) + if (StrEqual(cvarName, "cl_interpolate")) + { + if (StringToInt(cvarValue) != 1) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "nolerpBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "nolerpBanAllChat", Cl); + LogMessage("%t", "nolerpBanMsg", Cl); + } + } + // r_drawothermodels (if u get banned by this you are a clown) + else if (StrEqual(cvarName, "r_drawothermodels")) + { + if (StringToInt(cvarValue) != 1) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "othermodelsBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "othermodelsBanAllChat", Cl); + LogMessage("%t", "othermodelsBanMsg", Cl); + } + } + // fov check #1 (if u get banned by this you are a clown) + else if (StrEqual(cvarName, "fov_desired")) + { + // save fov to var to reset later with netpropcheck + fovDesired[Cl] = StringToInt(cvarValue); + if (StringToInt(cvarValue) > 90) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "fovBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "fovBanAllChat", Cl); + LogMessage("%t", "fovBanMsg", Cl); + } + } + // mat_fullbright + // make sure we're not on an map + if (StrEqual(cvarName, "mat_fullbright")) + { + // can only ever be 0 unless you're cheating or on a map with uncompiled lighting so check for both of these + if (StringToInt(cvarValue) != 0 && !NoVRAD) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "fullbrightBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "fovBanAllChat", Cl); + LogMessage("%t", "fullbrightBanMsg", Cl); + } + } + // thirdperson (hidden cvar) + else if (StrEqual(cvarName, "cl_thirdperson")) + { + if (StringToInt(cvarValue) != 0) + { + char reason[512]; + Format(reason, sizeof(reason), "%t", "tpBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "tpBanAllChat", Cl); + LogMessage("%t", "tpBanMsg", Cl); + } + } + /* + NETWORK CVARS + */ + // cl_interp + else if (StrEqual(cvarName, "cl_interp")) + { + interpFor[Cl] = StringToFloat(cvarValue); + } + // cl_cmdrate + else if (StrEqual(cvarName, "cl_cmdrate")) + { + if (!kickForPingMasking) + { + return; + } + if (!cvarValue[0]) + { + LogMessage("[StAC] Null string returned as cvar result when querying cvar %s on %N", cvarName, Cl); + PrintToImportant("{hotpink}[StAC]{white} Null string returned as cvar result when querying cvar %s on %N", cvarName, Cl); + } + // cl_cmdrate needs to not have any non numerical chars (xcept the . sign if its a float) in it because otherwise player ping gets messed up on the scoreboard + else if (SimpleRegexMatch(cvarValue, "^\\d*\\.?\\d*$") <= 0) + { + KickClient(Cl, "%t", "pingmaskingKickMsg", cvarValue); + LogMessage("%t", "pingmaskingLogMsg", Cl, cvarValue); + CPrintToChatAll("%t", "pingmaskingAllChat", Cl, cvarValue); + } + } + // cl_interp_ratio + else if (StrEqual(cvarName, "cl_interp_ratio")) + { + // we have to clamp interp ratio to make sure we're getting the "real" client interp + // https://github.com/TheAlePower/TeamFortress2/blob/1b81dded673d49adebf4d0958e52236ecc28a956/tf2_src/game/server/gameinterface.cpp#L2845 + interpRatioFor[Cl] = StringToFloat(cvarValue); + if (interpRatioFor[Cl] == 0.0) + { + interpRatioFor[Cl] = 1.0; + } + if (MinInterpRatio && MaxInterpRatio && float(MinInterpRatio) != -1) + { + interpRatioFor[Cl] = Math_Clamp(interpRatioFor[Cl], float(MinInterpRatio), float(MaxInterpRatio)); + } + else + { + if (interpRatioFor[Cl] == 0.0) + { + interpRatioFor[Cl] = 1.0; + } + } + } + // cl_updaterate + else if (StrEqual(cvarName, "cl_updaterate")) + { + updaterateFor[Cl] = StringToFloat(cvarValue); + // calculate real lerp here + REALinterpFor[Cl] = (fMax(interpFor[Cl], interpRatioFor[Cl] / updaterateFor[Cl])) * 1000; + if (DEBUG) + { + LogMessage("%f ms interp on %N", REALinterpFor[Cl], Cl); + } + // don't bother actually doing anything about lerp if tickrate isnt default + if (tps < 70.0 && tps > 60.0) + { + if ( + REALinterpFor[Cl] < min_interp_ms && min_interp_ms != -1 + || + REALinterpFor[Cl] > max_interp_ms && max_interp_ms != -1 + ) + { + KickClient(Cl, "%t", "interpKickMsg", REALinterpFor[Cl], min_interp_ms, max_interp_ms); + LogMessage("%t", "interpLogMsg", Cl, REALinterpFor[Cl]); + CPrintToChatAll("%t", "interpAllChat", Cl, REALinterpFor[Cl]); + } + } + } + /* will be used later for aimsnap checking + else if (StrEqual(cvarName, "sensitivity")) + { + sensFor[Cl] = StringToFloat(cvarValue); + //PrintToChatAll("Client %N's sens is %f", Cl, sensFor[Cl]); + // min bounds is actually .000100 so + if (StringToFloat(cvarValue) < 0.000100) + // do somethin about it! + { + // + } + } + */ + if (DEBUG) + { + LogMessage("[StAC] Checked cvar %s value %s on %N", cvarName, cvarValue, Cl); + PrintToConsoleAllAdmins("[StAC] Checked cvar %s value %s on %N", cvarName, cvarValue, Cl); + } +} + +// ban on invalid characters (newlines, carriage returns, etc) +public Action OnClientSayCommand(int Cl, const char[] command, const char[] sArgs) +{ + if ( + StrContains(sArgs, "\n", false) != -1 + || + StrContains(sArgs, "\r", false) != -1 + ) + { + int userid = GetClientUserId(Cl); + char reason[512]; + Format(reason, sizeof(reason), "%t", "newlineBanMsg", Cl); + BanUser(userid, reason); + CPrintToChatAll("%t", "newlineBanAllChat", Cl); + LogMessage("%t", "newlineBanMsg", Cl); + return Plugin_Stop; + } + return Plugin_Continue; +} + +public BanUser(userid, char[] reason) +{ + if (!autoban) + { + CPrintToChatAll("{hotpink}[StAC]{white} Autoban cvar set to 0. Not banning!"); + return; + } + int Cl = GetClientOfUserId(userid); + if (userBanQueued[Cl]) + { + return; + } + if (SOURCEBANS) + { + SBPP_BanPlayer(0, Cl, 0, reason); + userBanQueued[Cl] = true; + } + else + { + BanClient(Cl, 0, BANFLAG_AUTO, reason, reason, _, _); + userBanQueued[Cl] = true; + } +} + +// todo- try GetClientModel for detecting possible chams? don't think that would work though as you can't check client's specific models for other things afaik +NetPropCheck(int userid) +{ + int Cl = GetClientOfUserId(userid); + + if (IsValidClient(Cl)) + { + // set real fov from client here - overrides cheat values (works with ncc, untested on others) + // we don't want to touch fov if a client is zoomed in while sniping... + if (!TF2_IsPlayerInCondition(Cl, TFCond_Zoomed)) + { + SetEntProp(Cl, Prop_Send, "m_iFOV", fovDesired[Cl]); + } + // forcibly disables thirdperson with some cheats + ClientCommand(Cl, "firstperson"); + if (DEBUG) + { + LogMessage("[StAC] Executed firstperson command on Player %N", Cl); + PrintToConsoleAllAdmins("[StAC] Executed firstperson command on Player %N", Cl); + } + // lerp check (again). this time we check the netprop. Just in case. + if (tps < 70.0 && tps > 60.0) + { + float lerp = GetEntPropFloat(Cl, Prop_Data, "m_fLerpTime") * 1000; + if ( + lerp < min_interp_ms && min_interp_ms != -1 + || + lerp > max_interp_ms && max_interp_ms != -1 + ) + { + KickClient(Cl, "%t", "interpKickMsg", lerp, min_interp_ms, max_interp_ms); + LogMessage("%t", "interpLogMsg", Cl, lerp); + CPrintToChatAll("%t", "interpAllChat", Cl, lerp); + } + } + } +} + +// these 3 functions are a god damn mess + +QueryEverything(int userid) +{ + int Cl = GetClientOfUserId(userid); + if (IsValidClient(Cl)) + { + // check cvars! + int i = 0; + QueryCvars(userid, i); + } +} + +QueryCvars(int userid, int i) +{ + int Cl = GetClientOfUserId(userid); + if (IsValidClient(Cl)) + { + if (i < sizeof(cvarsToCheck) || i == 0) + { + DataPack pack; + QueryClientConVar(Cl, cvarsToCheck[i], ConVarQueryFinished:ConVarCheck); + i++; + CreateDataTimer(1.0, timerqC, pack); + WritePackCell(pack, userid); + WritePackCell(pack, i); + } + else if (i >= sizeof(cvarsToCheck)) + { + // checks a bunch of AC related netprops + NetPropCheck(userid); + } + } +} + +// timer for checking the next cvar in the list (waits a second to balance out server load) +public Action timerqC(Handle timer, DataPack pack) +{ + ResetPack(pack, false); + int userid = ReadPackCell(pack); + int i = ReadPackCell(pack); + int Cl = GetClientOfUserId(userid); + if (IsValidClient(Cl)) + { + QueryCvars(userid, i); + } +} + +// timer for checking ALL cvars and net props and everything else +public Action Timer_CheckClientConVars(Handle timer, any userid) +{ + // get actual client index + int Cl = GetClientOfUserId(userid); + // null out timer here + g_hQueryTimer[Cl] = null; + if (IsValidClient(Cl)) + { + if (DEBUG) + { + LogMessage("[StAC] Checking client id, %i, %N", Cl, Cl); + } + // query the client! + QueryEverything(userid); + // check randomly (every 1 - 5 minutes) for violating clients, then recheck with a new random value + g_hQueryTimer[Cl] = CreateTimer(GetRandomFloat(minRandCheckVal, maxRandCheckVal), Timer_CheckClientConVars, userid); + } +} + +// expensive! +QueryEverythingAllClients() +{ + LogMessage("[StAC] Querying all clients"); + for (int Cl = 1; Cl <= MaxClients; Cl++) + { + if (IsValidClient(Cl)) + { + int userid = GetClientUserId(Cl); + QueryEverything(userid); + } + } +} + +//////////// +// STONKS // +//////////// + +// cleaned up IsValidClient Stock +stock bool IsValidClient(client) +{ + if ( + client <= 0 + || client > MaxClients + || !IsClientConnected(client) + || IsFakeClient(client) + ) + { + return false; + } + return IsClientInGame(client); +} + +// is client on a team and not dead +stock bool IsClientPlaying(client) +{ + TFTeam team = TF2_GetClientTeam(client); + if ( + IsPlayerAlive(client) + && + ( + team == TFTeam_Red + || + team == TFTeam_Blue + ) + ) + { + return true; + } + return false; +} + +// print colored chat to all server/sourcemod admins +stock PrintColoredChatToAdmins(const char[] format, any ...) +{ + char buffer[254]; + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && CheckCommandAccess(i, "sm_ban", ADMFLAG_ROOT)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + CPrintToChat(i, "%s", buffer); + } + } +} + +// print to all server/sourcemod admin's consoles +stock PrintToConsoleAllAdmins(const char[] format, any ...) +{ + char buffer[254]; + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && CheckCommandAccess(i, "sm_ban", ADMFLAG_ROOT)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + PrintToConsole(i, "%s", buffer); + } + } +} + +// print to important ppl on server +stock PrintToImportant(const char[] format, any ...) +{ + char buffer[254]; + VFormat(buffer, sizeof(buffer), format, 2); + PrintColoredChatToAdmins("%s", buffer); + CPrintToSTV("%s", buffer); +} + +// these stocks are adapted & deuglified from f2stocks +// Finds STV Bot to use for CPrintToSTV +CachedSTV; +stock FindSTV() +{ + if (! + ( + CachedSTV >= 1 + && CachedSTV <= MaxClients + && IsClientConnected(CachedSTV) + && IsClientInGame(CachedSTV) + && IsClientSourceTV(CachedSTV) + ) + ) + { + CachedSTV = -1; + for (int client = 1; client <= MaxClients; client++) + { + if ( + IsClientConnected(client) + && IsClientInGame(client) + && IsClientSourceTV(client) + ) + { + CachedSTV = client; + break; + } + } + } + return CachedSTV; +} + +// print to stv (now with color) +// requires color-literals.inc +stock CPrintToSTV(const char[] format, any:...) +{ + int stv = FindSTV(); + if (stv < 1) + { + return; + } + + char buffer[512]; + VFormat(buffer, sizeof(buffer), format, 2); + CPrintToChat(stv, "%s", buffer); +} + +// float max + +stock float fMax(float a, float b) { + return a > b ? a : b; +} + +// stolen from smlib + +stock any Math_Min(any value, any min) +{ + if (value < min) { + value = min; + } + + return value; +} + +stock any Math_Max(any value, any max) +{ + if (value > max) { + value = max; + } + + return value; +} + +stock any Math_Clamp(any value, any min, any max) +{ + value = Math_Min(value, min); + value = Math_Max(value, max); + + return value; +} diff --git a/GRAB/StAC/translations/stac.phrases.txt b/GRAB/StAC/translations/stac.phrases.txt new file mode 100644 index 0000000..574e5b4 --- /dev/null +++ b/GRAB/StAC/translations/stac.phrases.txt @@ -0,0 +1,195 @@ +"Phrases" +{ + /* + - You can disable printing to allchat by having the string after the "en" be blank ("") + - kick and ban messages already have a period at the end of them in TF2, so don't add one + - ban messages get printed to banned client as the kick reason if not using sourcebans and posted to sourcebans as the "ban reason" if you have it installed + */ + + /* + _____ _ __ __ + ____ / ___/(_) /__ ____ / /_ + / __ \\__ \/ / / _ \/ __ \/ __/ + / /_/ /__/ / / / __/ / / / /_ + / .___/____/_/_/\___/_/ /_/\__/ + /_/ + + */ + "pSilentBanAllChat" + { + "#format" "{1:N},{2:i}" + "en" "{hotpink}[StAC]{white} Player {1} was using {mediumpurple}pSilentAim{white} or {mediumpurple}NoRecoil{white} Total detections: {mediumpurple}{2}{white}. {palegreen}BANNED from server!" + } + // msg that gets written to sourcebans + "pSilentBanMsg" + { + "#format" "{1:N},{2:i}" + "en" "[StAC] Player {1} was banned for using pSilentAim or NoRecoil. Total detections: {2}" + } + /* + ____ __ + / __/____ _ / /__ ___ ____ _ ____ ____ _ _____ + / /_ / __ `// //_// _ \ / __ `// __ \ / __ `// ___/ + / __// /_/ // ,< / __// /_/ // / / // /_/ /(__ ) + /_/ \__,_//_/|_| \___/ \__,_//_/ /_/ \__, //____/ + /____/ + + */ + "fakeangBanAllChat" + { + "#format" "{1:N},{2:i}" + "en" "{hotpink}[StAC]{white} Player {1} had too many {mediumpurple}invalid eye angle detections{white}! Total detections: {mediumpurple}{2}{white}. {palegreen}BANNED from server!" + } + "fakeangBanMsg" + { + "#format" "{1:N},{2:i}" + "en" "[StAC] Player {1} was banned for having too many fake angle detections. Total detections: {2}" + } + /* + __ __ _ __ + / /___ ___________ / /_ (_)___ ____/ /____ + / __/ / / / ___/ __ \ / __ \/ / __ \/ __ / ___/ + / /_/ /_/ / / / / / / / /_/ / / / / / /_/ (__ ) + \__/\__,_/_/ /_/ /_/ /_.___/_/_/ /_/\__,_/____/ + + */ + "turnbindKickMsg" + { + "en" "[StAC] Usage of turn binds or spin binds is not allowed. Kicked from server" + } + "turnbindAdminMsg" + { + "#format" "{1:N},{2:.1f}" + "en" "{hotpink}[StAC]{white} Detected turn bind on player {1} for {palegreen}{2}{white} seconds" + } + "turnbindWarnPlayer" + { + "en" "{hotpink}[StAC]{white} Turn binds and spin binds are not allowed on this server. If you continue to use them you will be autokicked!" + } + "turnbindAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was using turn binds. {palegreen}Kicked from server." + } + "turnbindLogMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was using turn binds. Kicked from server." + } + /* + __ __ __ + ____ / /_/ /_ ___ _____ / /_ ____ _____ ____ ___ _________ ______ + / __ \/ __/ __ \/ _ \/ ___/ / __ \/ __ `/ __ \ / __ `__ \/ ___/ __ `/ ___/ + / /_/ / /_/ / / / __/ / / /_/ / /_/ / / / / / / / / / (__ ) /_/ (__ ) + \____/\__/_/ /_/\___/_/ /_.___/\__,_/_/ /_/ /_/ /_/ /_/____/\__, /____/ + /____/ + + */ + // nolerp + "nolerpBanAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was using {mediumpurple}NoLerp{white}! {palegreen}BANNED from server." + } + "nolerpBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for using NoLerp" + } + // othermodels + "othermodelsBanAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was {mediumpurple}wallhacking{white}! {palegreen}BANNED from server." + } + "othermodelsBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for wallhacking" + } + // fov + "fovBanAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was {mediumpurple}fov cheating{white}! {palegreen}BANNED from server." + } + "fovBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for fov cheating" + } + // fullbright + "fullbrightAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was {mediumpurple}fullbright cheating{white}! {palegreen}BANNED from server." + } + "fullbrightBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for fullbright cheating" + } + // thirdperson + "tpBanAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} was {mediumpurple}cheating with 3rd person{white}! {palegreen}BANNED from server." + } + "tpBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for cheating with thirdperson" + } + // newline prints + "newlineBanAllChat" + { + "#format" "{1:N}" + "en" "{hotpink}[StAC]{white} Player {1} attempted to {mediumpurple}print a newline character{white}! {palegreen}BANNED from server." + } + "newlineBanMsg" + { + "#format" "{1:N}" + "en" "[StAC] Player {1} was banned for attempting to print a newline character" + } + /* + __ _ __ + / /__(_)____/ /__ ____ ___ _________ ______ + / //_/ / ___/ //_/ / __ `__ \/ ___/ __ `/ ___/ + / ,< / / /__/ ,< / / / / / (__ ) /_/ (__ ) + /_/|_/_/\___/_/|_| /_/ /_/ /_/____/\__, /____/ + /____/ + + */ + // lerp violations + "interpKickMsg" + { + "#format" "{1:.1f},{2:i},{3:i}" + "en" "[StAC] Your interp was out of bounds! Your interp: {1}ms. Minimum allowed: {2}ms. Maximum allowed: {3}ms. Please fix this before rejoining" + } + "interpAllChat" + { + "#format" "{1:N},{2:.1f}" + "en" "{hotpink}[StAC]{white} Player {1}'s {mediumpurple}interp{white} was {mediumpurple}{2}{white}ms, indicating interp exploitation. {palegreen}Kicked from server." + } + "interpLogMsg" + { + "#format" "{1:N},{2:.1f}" + "en" "[StAC] Player {1}'s interp was {2}ms, indicating interp exploitation. Kicked from server." + } + // pingmasking violations + "pingmaskingKickMsg" + { + "#format" "{1:s}" + "en" "[StAC] CVar cl_cmdrate = {1}, indicating pingmasking. Remove any non numeric characters before reconnecting" + } + "pingmaskingAllChat" + { + "#format" "{1:N},{2:s}" + "en" "{hotpink}[StAC]{white} Player {1} was {mediumpurple}ping masking{white} using a cl_cmdrate setting of {mediumpurple}{2}. Kicked from server" + } + "pingmaskingLogMsg" + { + "#format" "{1:N},{2:s}" + "en" "[StAC] Player {1} was ping masking using a cl_cmdrate setting of {2}" + } +} \ No newline at end of file diff --git a/GRAB/StAC/updatefile.txt b/GRAB/StAC/updatefile.txt new file mode 100644 index 0000000..9082177 --- /dev/null +++ b/GRAB/StAC/updatefile.txt @@ -0,0 +1,24 @@ +"Updater" +{ + "Information" + { + "Version" + { + "Latest" "3.1.2" + } + + "Notes" "Changes in 3.1.0/3.1.2" + "Notes" "- added translations" + "Notes" "- fixed fakeang detections again" + "Notes" "- reenabled autoban for fakeangs" + "Notes" "- refactored lots of code, cleaned up lots of various other things" + "Notes" "- fixed accidentally printing to chat too often if client is already queued for a ban" + "Notes" "- added stac_kick_for_pingmasking, defaults to 0" + } + + "Files" + { + "Plugin" "Path_SM/plugins/stac.smx" + "Plugin" "Path_SM/translations/stac.phrases.txt" + } +}