diff --git a/.github/workflows/test.yml b/.github/workflows/gamedata.yml similarity index 67% rename from .github/workflows/test.yml rename to .github/workflows/gamedata.yml index 3988a7e..c654009 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/gamedata.yml @@ -1,9 +1,9 @@ -name: Test +name: Gamedata on: pull_request: push: schedule: - - cron: "0 17 * * *" + - cron: "0 0,8,16 * * *" repository_dispatch: jobs: @@ -33,6 +33,7 @@ jobs: shell: pwsh run: cd test && ./test.ps1 -USE_BUILDX:$True -SUPPRESS_BUILD:$True - name: Upload Binaries + if: success() || failure() uses: actions/upload-artifact@v3 with: name: binaries @@ -40,27 +41,9 @@ jobs: test/bin !test/bin/.gitkeep - name: Upload Logs + if: success() || failure() uses: actions/upload-artifact@v3 with: name: gdc-logs path: | - test/logs - plugins: - runs-on: ubuntu-latest - name: Build Plugins - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Build Plugins - shell: pwsh - run: cd src && ./build.ps1 - - name: Upload Plugins - uses: actions/upload-artifact@v3 - with: - if-no-files-found: error - name: plugins - path: | - src/plugins - !src/plugins/.gitkeep + test/logs \ No newline at end of file diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml new file mode 100644 index 0000000..2dd499f --- /dev/null +++ b/.github/workflows/plugins.yml @@ -0,0 +1,26 @@ +name: Plugins +on: + pull_request: + push: + repository_dispatch: + +jobs: + plugins: + runs-on: ubuntu-latest + name: Build Plugins + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Build Plugins + shell: pwsh + run: cd src && ./build.ps1 + - name: Upload Plugins + uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: plugins + path: | + src/plugins + !src/plugins/.gitkeep diff --git a/.gitignore b/.gitignore index 069ee56..c0683f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,18 @@ +# Test outputs /test/bin/*.dll /test/bin/*.so + +# Test logs test/output.temp test/exporter.temp -/src/plugins/*.smx -/src/scripting/private/ test/output.temp test/cache -test/logs \ No newline at end of file +test/logs + +# Plugin things +/src/plugins/*.smx +/src/scripting/private/ + +# Package things +package/ +sourceforks.zip \ No newline at end of file diff --git a/README.md b/README.md index 11dd20a..9e92673 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Maintained forks of various sourcemod plugins and gamedatas ## Plugins +**Current SourceMod Version:** 1.11 + ### Antilag *Originally by Backwards* @@ -17,6 +19,9 @@ You can use the cvar `sourceforks_antilag_punishment` to configure the punishmen - **2**: Kick the cheater if they continue - **3**: Permanently ban the cheater if they continue. +> **Note**: It is impossible for any legitimate player on a vanilla client to reach the "attempted DDOS" alert. +> If you see this, or they were banned for DDoSing, they are cheating. Period. +> It is possible, however, for the "suspicious network activity" alert to trigger on really, really bad connections on 93 tick or above servers. ### MovementUnlocker *Originally by Peace-Maker* diff --git a/package.ps1 b/package.ps1 new file mode 100644 index 0000000..1bcd9bf --- /dev/null +++ b/package.ps1 @@ -0,0 +1,50 @@ + +Write-Host ":::: ==========================" +Write-Host ":::: TESTING GAMEDATA FOR CS:GO" +Write-Host ":::: ==========================" + +$base = Get-Location + +Set-Location test +& ./test.ps1 -SUPPRESS_BUILD:$True + +Write-Host ":::: ===========================" +Write-Host ":::: BUILDING INDIVIDUAL PLUGINS" +Write-Host ":::: ===========================" + +Set-Location $base +Set-Location src +& ./build.ps1 + +Write-Host ":::: ============================" +Write-Host ":::: PACKAGING SOURCEFORKS ASSETS" +Write-Host ":::: ============================" + +Set-Location $base + +if (Test-Path -PathType Container package) +{ + Write-Host "* Clearing old package" + Remove-Item -Path package -Recurse +} + +Write-Host "* Creating package directory" +New-Item -Path package -Type Directory + +Write-Host "* Creating directories" +New-Item -Path package/addons -Type Directory +New-Item -Path package/addons/sourcemod -Type Directory + +Copy-Item -Recurse -Path gamedata -Destination package/addons/sourcemod +Remove-Item -Path package/addons/sourcemod/gamedata/partial -Recurse +# ^ Remove partial gamedatas + +Copy-Item -Recurse -Path src/plugins -Destination package/addons/sourcemod +Copy-Item -Recurse -Path src/cfg -Destination package/ + +Write-Host ":::: ==========================" +Write-Host ":::: COMPRESSING PACKAGE TO ZIP" +Write-Host ":::: ==========================" + +Compress-Archive -Force -Path package/* -DestinationPath sourceforks.zip +Write-Host "* Done!" \ No newline at end of file diff --git a/src/Dockerfile b/src/Dockerfile index 4d51773..04c822a 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,6 +1,6 @@ FROM alpine AS sourcemod_huge -RUN wget -c https://sm.alliedmods.net/smdrop/1.12/sourcemod-1.12.0-git6982-linux.tar.gz -O - | tar -xz +RUN wget -c https://sm.alliedmods.net/smdrop/1.11/sourcemod-1.11.0-git6931-linux.tar.gz -O - | tar -xz FROM bitnami/minideb:bullseye as sourcemod diff --git a/src/cfg/sourcemod/sourceforks_antilag.cfg b/src/cfg/sourcemod/sourceforks_antilag.cfg new file mode 100644 index 0000000..4f38a27 --- /dev/null +++ b/src/cfg/sourcemod/sourceforks_antilag.cfg @@ -0,0 +1,8 @@ +// This file was auto-generated by SourceMod (v1.12.0.6968) +// ConVars for plugin "sourceforks_antilag.smx" + + +// 0 = None, 1 = Alert Admins, 2 = Kick, 3 = Permanent Ban (Default) +// - +// Default: "3" +sourceforks_antilag_punishment "3" \ No newline at end of file diff --git a/src/scripting/include/admin_utils.sp b/src/scripting/include/admin_utils.sp new file mode 100644 index 0000000..f056486 --- /dev/null +++ b/src/scripting/include/admin_utils.sp @@ -0,0 +1,50 @@ +// Utilities to assist with printing messages to admins + +#include +#include + +#define BUFFER_SIZE 512 + +stock void PrintToAdmins(const char[] format, AdminFlag flags, ...) +{ + char buffer[BUFFER_SIZE]; + VFormat(buffer, sizeof(buffer), format, 3); + + PrintToServer("[SourceForks Server]: %s", buffer); + + for (int admin = 1; admin < MAXPLAYERS; admin++) + { + if (!CheckCommandAccess(admin, "", int:flags, true)) + continue; + + PrintToChat(admin, "[SourceForks]: %s", buffer); + } +} + +stock void CPrintToAdmins(const char[] in_format, AdminFlag flags, ...) +{ + char buffer[BUFFER_SIZE]; + char format[BUFFER_SIZE]; + + strcopy(format, sizeof(format), in_format); + // Consume buffer early to print to server + { + CRemoveTags(format, sizeof(format)); + VFormat(buffer, sizeof(buffer), format, 3); + PrintToServer("[SourceForks Server]: %s", buffer); + } + strcopy(format, sizeof(format), in_format); + + // Now do color formatting + { + CFormatColor(format, sizeof(format), -1); + VFormat(buffer, sizeof(buffer), format, 3); + } + for (int admin = 1; admin < MAXPLAYERS; admin++) + { + if (!CheckCommandAccess(admin, "", int:flags, true)) + continue; + + PrintToChat(admin, "[SourceForks]: %s", buffer); + } +} \ No newline at end of file diff --git a/src/scripting/include/multicolors.inc b/src/scripting/include/multicolors.inc new file mode 100644 index 0000000..9bbfc10 --- /dev/null +++ b/src/scripting/include/multicolors.inc @@ -0,0 +1,461 @@ +#if defined _multicolors_included + #endinput +#endif +#define _multicolors_included + +#define MuCo_VERSION "2.1.2" + +#include "multicolors/morecolors" +#include "multicolors/colors" + +/* +* +* Credits: +* - Popoklopsi +* - Powerlord +* - exvel +* - Dr. McKay +* +* Based on stamm-colors +* - https://github.com/popoklopsi/Stamm/blob/master/include/stamm/stamm-colors.inc +* +*/ + + + +#define PREFIX_MAX_LENGTH 64 +#define PREFIX_SEPARATOR "{default} " + +/* Global var to check whether colors are fixed or not */ +static bool g_bCFixColors; + +static char g_sCPrefix[PREFIX_MAX_LENGTH]; + +/** + * Add a chat prefix before all chat msg + * + * @param sPrefix Prefix + */ +stock void CSetPrefix(const char[] sPrefix, any ...) { + if (!sPrefix[0]) { + return; + } + + SetGlobalTransTarget(LANG_SERVER); + VFormat(g_sCPrefix, sizeof(g_sCPrefix) - strlen(PREFIX_SEPARATOR), sPrefix, 2); + + // Add ending space + Format(g_sCPrefix, sizeof(g_sCPrefix), "%s%s", g_sCPrefix, PREFIX_SEPARATOR); +} + +/** + * Add a chat prefix before all chat msg + * + * @param sPrefix Prefix + */ +stock void CClearPrefix() { + g_sCPrefix[0] = '\0'; +} + +/** + * Writes a message to a client with the correct stock for the game. + * + * @param client Client index. + * @param message Message (formatting rules). + * + * @error If the client is not connected an error will be thrown. + */ +stock void CPrintToChat(int client, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChat(client, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChat(client, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules) + */ +stock void CPrintToChatAll(const char[] message, any ...) { + if (!g_bCFixColors) { + CFixColors(); + } + + char buffer[MAX_BUFFER_LENGTH]; + if (!IsSource2009()) { + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), message, 2); + + C_PrintToChat(i, "%s", buffer); + } + + C_SkipList[i] = false; + } + } + else { + MC_CheckTrie(); + + char buffer2[MAX_BUFFER_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || MC_SkipList[i]) { + MC_SkipList[i] = false; + continue; + } + + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 2); + + MC_ReplaceColorCodes(buffer2); + MC_SendMessage(i, buffer2); + } + } +} + +/** + * Writes a message to all of a client's observers. + * + * @param target Client index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatObservers(int target, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + for (int client = 1; client <= MaxClients; client++) { + if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { + int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + + if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { + CPrintToChat(client, buffer); + } + } + } +} + +/** + * Writes a message to a client with the correct stock for the game. + * + * @param client Client index. + * @param author Author index. + * @param message Message (formatting rules). + * + * @error If the client is not connected an error will be thrown. + */ +stock void CPrintToChatEx(int client, int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChatEx(client, author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChatEx(client, author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Writes a message to all clients with the correct stock for the game. + * + * @param author Author index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatAllEx(int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChatAllEx(author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChatAllEx(author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Writes a message to all of a client's observers with the correct + * game stock. + * + * @param target Client index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatObserversEx(int target, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + for (int client = 1; client <= MaxClients; client++) { + if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { + int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + + if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { + CPrintToChatEx(client, target, buffer); + } + } + } +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param message Message (formatting rules) + */ +stock void CReplyToCommand(int client, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ReplyToCommand(client, "%s%s", g_sCPrefix, buffer); + } + else { + MC_ReplyToCommand(client, "%s%s", g_sCPrefix, 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) + */ +stock void CReplyToCommandEx(int client, int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ReplyToCommandEx(client, author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_ReplyToCommandEx(client, author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Remove all tags and print to server + * + * @param message Message (formatting rules) + */ +stock void CPrintToServer(const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + char prefixBuffer[PREFIX_MAX_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 2); + strcopy(prefixBuffer, sizeof(prefixBuffer), g_sCPrefix); + + if (!g_bCFixColors) { + CFixColors(); + } + + CRemoveTags(buffer, sizeof(buffer)); + CRemoveTags(prefixBuffer, sizeof(prefixBuffer)); + + PrintToServer("%s%s", prefixBuffer, buffer); +} + +/** + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. + * + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, + * CShowActivity2() is slightly more useful. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ +stock void CShowActivity(int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivity(author, "%s", buffer); + } + else { + MC_ShowActivity(author, "%s", buffer); + } +} + +/** + * Same as C_ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing). + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ +stock void CShowActivityEx(int author, const char[] tag, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivityEx(author, tag, "%s", buffer); + } + else { + MC_ShowActivityEx(author, tag, "%s", buffer); + } +} + +/** + * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. + * All users receive a message in their chat text, except for the originating client, + * who receives the message based on the current ReplySource. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ + stock void CShowActivity2(int author, const char[] tag, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivity2(author, tag, "%s", buffer); + } + else { + MC_ShowActivity2(author, tag, "%s", buffer); + } +} + + +/** + * Replaces color tags in a string with color codes + * + * @param message String. + * @param maxlength Maximum length of the string buffer. + */ +stock void CFormatColor(char[] message, int maxlength, int author = -1) { + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + if (author == 0) { + author = -1; + } + + C_Format(message, maxlength, author); + } + else { + if (author == -1) { + author = 0; + } + + MC_ReplaceColorCodes(message, author, false, maxlength); + } +} + +/** + * Removes color tags from a message + * + * @param message Message to remove tags from + * @param maxlen Maximum buffer length + */ +stock void CRemoveTags(char[] message, int maxlen) { + if (!IsSource2009()) { + C_RemoveTags(message, maxlen); + } + else { + MC_RemoveTags(message, maxlen); + } +} + +/** + * Fixes missing Lightgreen color. + */ +stock void CFixColors() { + g_bCFixColors = true; + + // Replace lightgreen if not exists + if (!C_ColorAllowed(Color_Lightgreen)) { + if (C_ColorAllowed(Color_Lime)) { + C_ReplaceColor(Color_Lightgreen, Color_Lime); + } + else if (C_ColorAllowed(Color_Olive)) { + C_ReplaceColor(Color_Lightgreen, Color_Olive); + } + } +} + +stock bool IsSource2009() { + return (GetEngineVersion() == Engine_CSS + || GetEngineVersion() == Engine_HL2DM + || GetEngineVersion() == Engine_DODS + || GetEngineVersion() == Engine_TF2 + || GetEngineVersion() == Engine_SDK2013); +} diff --git a/src/scripting/include/multicolors/colors.inc b/src/scripting/include/multicolors/colors.inc new file mode 100644 index 0000000..170ec97 --- /dev/null +++ b/src/scripting/include/multicolors/colors.inc @@ -0,0 +1,905 @@ +/************************************************************************** + * * + * Colored Chat Functions * + * Author: exvel, Editor: Popoklopsi, Powerlord, Bara * + * Version: 2.0.0-MC * + * * + **************************************************************************/ + + +#if defined _colors_included + #endinput +#endif +#define _colors_included + +#define MAX_MESSAGE_LENGTH 250 +#define MAX_COLORS 18 + +#define SERVER_INDEX 0 +#define NO_INDEX -1 +#define NO_PLAYER -2 + +enum C_Colors { + Color_Default = 0, + Color_Darkred, + Color_Green, + Color_Lightgreen, + Color_Red, + Color_Blue, + Color_Olive, + Color_Lime, + Color_Lightred, + Color_Purple, + Color_Grey, + Color_Yellow, + Color_Orange, + Color_Bluegrey, + Color_Lightblue, + Color_Darkblue, + Color_Grey2, + Color_Orchid, + Color_Lightred2 +} + +/* C_Colors' properties */ +char C_Tag[][] = {"{default}", "{darkred}", "{green}", "{lightgreen}", "{red}", "{blue}", "{olive}", "{lime}", "{lightred}", "{purple}", "{grey}", "{yellow}", "{orange}", "{bluegrey}", "{lightblue}", "{darkblue}", "{grey2}", "{orchid}", "{lightred2}"}; +char C_TagCode[][] = {"\x01", "\x02", "\x04", "\x03", "\x03", "\x03", "\x05", "\x06", "\x07", "\x03", "\x08", "\x09", "\x10", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F"}; +bool C_TagReqSayText2[] = {false, false, false, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool C_EventIsHooked; +bool C_SkipList[MAXPLAYERS+1]; + +/* Game default profile */ +bool C_Profile_Colors[] = {true, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +int C_Profile_TeamIndex[] = {NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX}; +bool C_Profile_SayText2; + +static ConVar sm_show_activity; + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the client is not connected an error will be thrown. + */ +stock void C_PrintToChat(int client, const char[] szMessage, any ...) { + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + + if (!IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + char szCMessage[MAX_MESSAGE_LENGTH]; + + SetGlobalTransTarget(client); + + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); + VFormat(szCMessage, sizeof(szCMessage), szBuffer, 3); + + int index = C_Format(szCMessage, sizeof(szCMessage)); + + if (index == NO_INDEX) { + PrintToChat(client, "%s", szCMessage); + } + else { + C_SayText2(client, index, szCMessage); + } +} + +/** + * Reples to a message in a command. A client index of 0 will use PrintToServer(). + * If the command was from the console, PrintToConsole() is used. If the command was from chat, C_PrintToChat() is used. + * Supports color tags. + * + * @param client Client index, or 0 for server. + * @param szMessage Formatting rules. + * @param ... Variable number of format parameters. + * @return No return + * + * On error/Errors: If the client is not connected or invalid. + */ +stock void C_ReplyToCommand(int client, const char[] szMessage, any ...) { + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(szCMessage, sizeof(szCMessage), szMessage, 3); + + if (client == 0) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToServer("%s", szCMessage); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToConsole(client, "%s", szCMessage); + } + else { + C_PrintToChat(client, "%s", szCMessage); + } +} + +/** + * Reples to a message in a command. A client index of 0 will use PrintToServer(). + * If the command was from the console, PrintToConsole() is used. If the command was from chat, C_PrintToChat() is used. + * Supports color tags. + * + * @param client Client index, or 0 for server. + * @param author Author index whose color will be used for teamcolor tag. + * @param szMessage Formatting rules. + * @param ... Variable number of format parameters. + * @return No return + * + * On error/Errors: If the client is not connected or invalid. + */ +stock void C_ReplyToCommandEx(int client, int author, const char[] szMessage, any ...) { + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(szCMessage, sizeof(szCMessage), szMessage, 4); + + if (client == 0) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToServer("%s", szCMessage); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToConsole(client, "%s", szCMessage); + } + else { + C_PrintToChatEx(client, author, "%s", szCMessage); + } +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param szMessage Message (formatting rules) + * @return No return + */ +stock void C_PrintToChatAll(const char[] szMessage, any ...) { + char szBuffer[MAX_MESSAGE_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { + SetGlobalTransTarget(i); + VFormat(szBuffer, sizeof(szBuffer), szMessage, 2); + + C_PrintToChat(i, "%s", szBuffer); + } + + C_SkipList[i] = false; + } +} + +/** + * 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 szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the client or author are not connected an error will be thrown. + */ +stock void C_PrintToChatEx(int client, int author, const char[] szMessage, any ...) { + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + + if (!IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + + if (author < 0 || author > MaxClients) { + ThrowError("Invalid client index %d", author); + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + char szCMessage[MAX_MESSAGE_LENGTH]; + + SetGlobalTransTarget(client); + + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); + VFormat(szCMessage, sizeof(szCMessage), szBuffer, 4); + + int index = C_Format(szCMessage, sizeof(szCMessage), author); + + if (index == NO_INDEX) { + PrintToChat(client, "%s", szCMessage); + } + else { + C_SayText2(client, author, szCMessage); + } +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags and teamcolor tag. + * + * @param author Author index whos color will be used for teamcolor tag. + * @param szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the author is not connected an error will be thrown. + */ +stock void C_PrintToChatAllEx(int author, const char[] szMessage, any ...) { + if (author < 0 || author > MaxClients) { + ThrowError("Invalid client index %d", author); + } + + if (!IsClientInGame(author)) { + ThrowError("Client %d is not in game", author); + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { + SetGlobalTransTarget(i); + VFormat(szBuffer, sizeof(szBuffer), szMessage, 3); + + C_PrintToChatEx(i, author, "%s", szBuffer); + } + + C_SkipList[i] = false; + } +} + +/** + * Removes color tags from the string. + * + * @param szMessage String. + * @return No return + */ +stock void C_RemoveTags(char[] szMessage, int maxlength) { + for (int i = 0; i < MAX_COLORS; i++) { + ReplaceString(szMessage, maxlength, C_Tag[i], "", false); + } + + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); +} + +/** + * Checks whether a color is allowed or not + * + * @param tag Color Tag. + * @return True when color is supported, otherwise false + */ +stock bool C_ColorAllowed(C_Colors color) { + if (!C_EventIsHooked) { + C_SetupProfile(); + + C_EventIsHooked = true; + } + + return C_Profile_Colors[color]; +} + +/** + * Replace the color with another color + * Handle with care! + * + * @param color color to replace. + * @param newColor color to replace with. + * @noreturn + */ +stock void C_ReplaceColor(C_Colors color, C_Colors newColor) { + if (!C_EventIsHooked) { + C_SetupProfile(); + + C_EventIsHooked = true; + } + + C_Profile_Colors[color] = C_Profile_Colors[newColor]; + C_Profile_TeamIndex[color] = C_Profile_TeamIndex[newColor]; + + C_TagReqSayText2[color] = C_TagReqSayText2[newColor]; + Format(C_TagCode[color], sizeof(C_TagCode[]), C_TagCode[newColor]); +} + +/** + * This function should only be used right in front of + * C_PrintToChatAll or C_PrintToChatAllEx and it tells + * to those funcions to skip specified client when printing + * message to all clients. After message is printed client will + * no more be skipped. + * + * @param client Client index + * @return No return + */ +stock void C_SkipNextClient(int client) { + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + + C_SkipList[client] = true; +} + +/** + * Replaces color tags in a string with color codes + * + * @param szMessage String. + * @param maxlength Maximum length of the string buffer. + * @return Client index that can be used for SayText2 author index + * + * On error/Errors: If there is more then one team color is used an error will be thrown. + */ +stock int C_Format(char[] szMessage, int maxlength, int author = NO_INDEX) { + /* Hook event for auto profile setup on map start */ + if (!C_EventIsHooked) { + C_SetupProfile(); + HookEvent("server_spawn", C_Event_MapStart, EventHookMode_PostNoCopy); + + C_EventIsHooked = true; + } + + int iRandomPlayer = NO_INDEX; + + // On CS:GO set invisible precolor + if (GetEngineVersion() == Engine_CSGO) { + Format(szMessage, maxlength, " %s", szMessage); + } + + /* If author was specified replace {teamcolor} tag */ + if (author != NO_INDEX) { + if (C_Profile_SayText2) { + ReplaceString(szMessage, maxlength, "{teamcolor}", "\x03", false); + + iRandomPlayer = author; + } + /* If saytext2 is not supported by game replace {teamcolor} with green tag */ + else { + ReplaceString(szMessage, maxlength, "{teamcolor}", C_TagCode[Color_Green], false); + } + } + else { + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); + } + + /* For other color tags we need a loop */ + for (int i = 0; i < MAX_COLORS; i++) { + /* If tag not found - skip */ + if (StrContains(szMessage, C_Tag[i], false) == -1) { + continue; + } + + /* If tag is not supported by game replace it with green tag */ + else if (!C_Profile_Colors[i]) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); + } + + /* If tag doesn't need saytext2 simply replace */ + else if (!C_TagReqSayText2[i]) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[i], false); + } + + /* Tag needs saytext2 */ + else { + /* If saytext2 is not supported by game replace tag with green tag */ + if (!C_Profile_SayText2) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); + } + + /* Game supports saytext2 */ + else { + /* If random player for tag wasn't specified replace tag and find player */ + if (iRandomPlayer == NO_INDEX) { + /* Searching for valid client for tag */ + iRandomPlayer = C_FindRandomPlayerByTeam(C_Profile_TeamIndex[i]); + + /* If player not found replace tag with green color tag */ + if (iRandomPlayer == NO_PLAYER) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); + } + + /* If player was found simply replace */ + else { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[i], false); + } + + } + /* If found another team color tag throw error */ + else { + //ReplaceString(szMessage, maxlength, C_Tag[i], ""); + ThrowError("Using two team colors in one message is not allowed"); + } + } + } + } + + return iRandomPlayer; +} + +/** + * Finds a random player with specified team + * + * @param color_team Client team. + * @return Client index or NO_PLAYER if no player found + */ +stock int C_FindRandomPlayerByTeam(int color_team) { + if (color_team == SERVER_INDEX) { + return 0; + } + + int[] players = new int[MaxClients]; + int count; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && GetClientTeam(i) == color_team) { + players[count++] = i; + } + } + + if (count) { + return players[GetRandomInt(0, count-1)]; + } + + return NO_PLAYER; +} + +/** + * Sends a SayText2 usermessage to a client + * + * @param szMessage Client index + * @param maxlength Author index + * @param szMessage Message + * @return No return. + */ +stock void C_SayText2(int client, int author, const char[] szMessage) { + Handle hBuffer = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + + if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { + Protobuf pb = UserMessageToProtobuf(hBuffer); + pb.SetInt("ent_idx", author); + pb.SetBool("chat", true); + pb.SetString("msg_name", szMessage); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + } + else { + BfWrite bf = UserMessageToBfWrite(hBuffer); + bf.WriteByte(author); + bf.WriteByte(true); + bf.WriteString(szMessage); + } + + EndMessage(); +} + +/** + * Creates game color profile + * This function must be edited if you want to add more games support + * + * @return No return. + */ +stock void C_SetupProfile() { + EngineVersion engine = GetEngineVersion(); + + if (engine == Engine_CSS) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_CSGO) { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_Colors[Color_Darkred] = true; + C_Profile_Colors[Color_Lime] = true; + C_Profile_Colors[Color_Lightred] = true; + C_Profile_Colors[Color_Purple] = true; + C_Profile_Colors[Color_Grey] = true; + C_Profile_Colors[Color_Yellow] = true; + C_Profile_Colors[Color_Orange] = true; + C_Profile_Colors[Color_Bluegrey] = true; + C_Profile_Colors[Color_Lightblue] = true; + C_Profile_Colors[Color_Darkblue] = true; + C_Profile_Colors[Color_Grey2] = true; + C_Profile_Colors[Color_Orchid] = true; + C_Profile_Colors[Color_Lightred2] = true; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_TF2) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_Left4Dead || engine == Engine_Left4Dead2) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 3; + C_Profile_TeamIndex[Color_Blue] = 2; + C_Profile_SayText2 = true; + } + else if (engine == Engine_HL2DM) { + /* hl2mp profile is based on mp_teamplay convar */ + if (GetConVarBool(FindConVar("mp_teamplay"))) { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Red] = 3; + C_Profile_TeamIndex[Color_Blue] = 2; + C_Profile_SayText2 = true; + } + else { + C_Profile_SayText2 = false; + C_Profile_Colors[Color_Olive] = true; + } + } + else if (engine == Engine_DODS) { + C_Profile_Colors[Color_Olive] = true; + C_Profile_SayText2 = false; + } + /* Profile for other games */ + else { + if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID) { + C_Profile_SayText2 = false; + } + else { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + } +} + +public void C_Event_MapStart(Event event, const char[] name, bool dontBroadcast) { + C_SetupProfile(); + + for (int i = 1; i <= MaxClients; ++i) { + C_SkipList[i] = false; + } +} + +/** + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. + * + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, + * C_ShowActivity2() is slightly more useful. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivity(int client, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char tag[] = "[SM] "; + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", tag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", tag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Same as C_ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing). + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivityEx(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", szTag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. + * All users receive a message in their chat text, except for the originating client, + * who receives the message based on the current ReplySource. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivity2(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + // ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + /* We don't display directly to the console because the chat text + * simply gets added to the console, so we don't want it to print + * twice. + */ + C_PrintToChatEx(client, client, "%s%s", szTag, szBuffer); + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || i == client) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID + || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} diff --git a/src/scripting/include/multicolors/morecolors.inc b/src/scripting/include/multicolors/morecolors.inc new file mode 100644 index 0000000..b1a6285 --- /dev/null +++ b/src/scripting/include/multicolors/morecolors.inc @@ -0,0 +1,998 @@ +// MOAR COLORS +// By Dr. McKay +// Inspired by: https://forums.alliedmods.net/showthread.php?t=96831 + +#if defined _more_colors_included + #endinput +#endif +#define _more_colors_included + +#pragma newdecls optional +#include + +#define MORE_COLORS_VERSION "2.0.0-MC" +#define MC_MAX_MESSAGE_LENGTH 256 +#define MAX_BUFFER_LENGTH (MC_MAX_MESSAGE_LENGTH * 4) + +#define MCOLOR_RED 0xFF4040 +#define MCOLOR_BLUE 0x99CCFF +#define MCOLOR_GRAY 0xCCCCCC +#define MCOLOR_GREEN 0x3EFF3E + +#define MC_GAME_DODS 0 + +bool MC_SkipList[MAXPLAYERS+1]; +StringMap MC_Trie; +int MC_TeamColors[][] = {{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 + +static ConVar sm_show_activity; + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + * + * On error/Errors: If the client is not connected an error will be thrown. + */ +stock void MC_PrintToChat(int client, const char[] message, any ...) { + MC_CheckTrie(); + + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + + if (!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + + char buffer[MAX_BUFFER_LENGTH]; + char buffer2[MAX_BUFFER_LENGTH]; + + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + + MC_ReplaceColorCodes(buffer2); + MC_SendMessage(client, buffer2); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + */ +stock void MC_PrintToChatAll(const char[] message, any ...) { + MC_CheckTrie(); + + char buffer[MAX_BUFFER_LENGTH], buffer2[MAX_BUFFER_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || MC_SkipList[i]) { + MC_SkipList[i] = false; + continue; + } + + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 2); + + MC_ReplaceColorCodes(buffer2); + MC_SendMessage(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). + * + * On error/Errors: If the client or author are not connected an error will be thrown + */ +stock void MC_PrintToChatEx(int client, int author, const char[] message, any ...) { + MC_CheckTrie(); + + 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 ((author != 0) && !IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + + char buffer[MAX_BUFFER_LENGTH], buffer2[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + MC_ReplaceColorCodes(buffer2, author); + MC_SendMessage(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). + * + * On error/Errors: If the author is not connected an error will be thrown. + */ +stock void MC_PrintToChatAllEx(int author, const char[] message, any ...) { + MC_CheckTrie(); + + if (author < 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + + if ((author != 0) && !IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + + char buffer[MAX_BUFFER_LENGTH]; + char buffer2[MAX_BUFFER_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || MC_SkipList[i]) { + MC_SkipList[i] = false; + continue; + } + + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + + MC_ReplaceColorCodes(buffer2, author); + MC_SendMessage(i, buffer2, author); + } +} + +/** + * Sends a SayText2 usermessage + * + * @param client Client to send usermessage to + * @param message Message to send + */ +stock void MC_SendMessage(int client, const char[] message, int author = 0) { + if (author == 0) { + author = client; + } + + char buffer[MC_MAX_MESSAGE_LENGTH]; + strcopy(buffer, sizeof(buffer), message); + + UserMsg index = GetUserMessageId("SayText2"); + if (index == INVALID_MESSAGE_ID) { + if (GetEngineVersion() == Engine_DODS) { + int team = GetClientTeam(author); + if (team == 0) { + ReplaceString(buffer, sizeof(buffer), "\x03", "\x04", false); // Unassigned gets green + } + else { + char temp[16]; + Format(temp, sizeof(temp), "\x07%06X", MC_TeamColors[MC_GAME_DODS][team - 1]); + ReplaceString(buffer, sizeof(buffer), "\x03", temp, false); + } + } + + PrintToChat(client, "%s", buffer); + return; + } + + Handle buf = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { + Protobuf pb = UserMessageToProtobuf(buf); + pb.SetInt("ent_idx", author); + pb.SetBool("chat", true); + pb.SetString("msg_name", buffer); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + } + else { + BfWrite bf = UserMessageToBfWrite(buf); + bf.WriteByte(author); // Message author + bf.WriteByte(true); // Chat message + bf.WriteString(buffer); // Message text + } + + EndMessage(); +} + +/** + * This function should only be used right in front of + * MC_PrintToChatAll or MC_PrintToChatAllEx. 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 + */ +stock void MC_SkipNextClient(int client) { + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + + MC_SkipList[client] = true; +} + +/** + * Checks if the colors trie is initialized and initializes it if it's not (used internally) + * + * @return No return + */ +stock void MC_CheckTrie() { + if (MC_Trie == null) { + MC_Trie = MC_InitColorTrie(); + } +} + +/** + * Replaces color tags in a string with color codes (used internally by MC_PrintToChat, MC_PrintToChatAll, MC_PrintToChatEx, and MC_PrintToChatAllEx + * + * @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 MC_RemoveTags + * @param maxlen Optional value for max buffer length, used by MC_RemoveTags + * + * On error/Errors: If the client index passed for author is invalid or not in game. + */ +stock void MC_ReplaceColorCodes(char[] buffer, int author = 0, bool removeTags = false, int maxlen = MAX_BUFFER_LENGTH) { + MC_CheckTrie(); + 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); + } + + int cursor = 0; + int value; + char tag[32], buff[32]; + char[] output = new char[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 + + Regex regex = new Regex("{[#a-zA-Z0-9]+}"); + for (int 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 (regex.Match(buffer[cursor]) < 1) { + delete regex; + strcopy(buffer, maxlen, output); + return; + } + + regex.GetSubString(0, tag, sizeof(tag)); + MC_StrToLower(tag); + cursor = StrContains(buffer[cursor], tag, false) + cursor + 1; + strcopy(buff, sizeof(buff), tag); + ReplaceString(buff, sizeof(buff), "{", ""); + ReplaceString(buff, sizeof(buff), "}", ""); + + if (buff[0] == '#') { + if (strlen(buff) == 7) { + Format(buff, sizeof(buff), "\x07%s", buff[1]); + } + else if (strlen(buff) == 9) { + Format(buff, sizeof(buff), "\x08%s", buff[1]); + } + else { + continue; + } + + if (removeTags) { + ReplaceString(output, maxlen, tag, "", false); + } + else { + ReplaceString(output, maxlen, tag, buff, false); + } + } + else if (!MC_Trie.GetValue(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 + */ +stock void CSubString(const char[] input, char[] output, int maxlen, int start, int numChars = 0) { + int 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 + */ +stock void MC_StrToLower(char[] buffer) { + int len = strlen(buffer); + for (int 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 MC_AddColor(const char[] name, int color) { + MC_CheckTrie(); + + int value; + + if (MC_Trie.GetValue(name, value)) { + return false; + } + + char newName[64]; + strcopy(newName, sizeof(newName), name); + + MC_StrToLower(newName); + MC_Trie.SetValue(newName, color); + return true; +} + +/** + * Removes color tags from a message + * + * @param message Message to remove tags from + * @param maxlen Maximum buffer length + */ +stock void MC_RemoveTags(char[] message, int maxlen) { + MC_ReplaceColorCodes(message, 0, true, maxlen); +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param message Message (formatting rules) + */ +stock void MC_ReplyToCommand(int client, const char[] message, any ...) { + char buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + + if (client == 0) { + MC_RemoveTags(buffer, sizeof(buffer)); + PrintToServer("%s", buffer); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + MC_RemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } + else { + MC_PrintToChat(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) + */ +stock void MC_ReplyToCommandEx(int client, int author, const char[] message, any ...) { + char buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + + if (client == 0) { + MC_RemoveTags(buffer, sizeof(buffer)); + PrintToServer("%s", buffer); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + MC_RemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } + else { + MC_PrintToChatEx(client, author, "%s", buffer); + } +} + +/** + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. + * + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, + * MC_ShowActivity2() is slightly more useful. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @error + */ +stock int MC_ShowActivity(int client, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char tag[] = "[SM] "; + + char szBuffer[MC_MAX_MESSAGE_LENGTH]; + //char szCMessage[MC_MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) + ThrowError("Client index %d is invalid", client); + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + MC_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", tag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + MC_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", tag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = id.HasFlag(Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Same as MC_ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing). + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @error + */ +stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MC_MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MC_MAX_MESSAGE_LENGTH]; + //char szCMessage[MC_MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", szTag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = id.HasFlag(Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. + * All users receive a message in their chat text, except for the originating client, + * who receives the message based on the current ReplySource. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @error + */ +stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MC_MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MC_MAX_MESSAGE_LENGTH]; + //char szCMessage[MC_MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + // ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + /* We don't display directly to the console because the chat text + * simply gets added to the console, so we don't want it to print + * twice. + */ + MC_PrintToChatEx(client, client, "%s%s", szTag, szBuffer); + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || i == client) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = id.HasFlag(Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + + if ((value & 8) || ((value & 16) && is_root)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + MC_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * 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 char[] color) { + MC_CheckTrie(); + int temp; + return MC_Trie.GetValue(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 int CGetTeamColor(int client) { + if (client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + + if (!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + + int value; + switch(GetClientTeam(client)) { + case 1: { + value = MCOLOR_GRAY; + } + case 2: { + value = MCOLOR_RED; + } + case 3: { + value = MCOLOR_BLUE; + } + default: { + value = MCOLOR_GREEN; + } + } + + return value; +} + +stock StringMap MC_InitColorTrie() { + StringMap hTrie = new StringMap(); + hTrie.SetValue("aliceblue", 0xF0F8FF); + hTrie.SetValue("allies", 0x4D7942); // same as Allies team in DoD:S + hTrie.SetValue("ancient", 0xEB4B4B); // same as Ancient item rarity in Dota 2 + hTrie.SetValue("antiquewhite", 0xFAEBD7); + hTrie.SetValue("aqua", 0x00FFFF); + hTrie.SetValue("aquamarine", 0x7FFFD4); + hTrie.SetValue("arcana", 0xADE55C); // same as Arcana item rarity in Dota 2 + hTrie.SetValue("axis", 0xFF4040); // same as Axis team in DoD:S + hTrie.SetValue("azure", 0x007FFF); + hTrie.SetValue("beige", 0xF5F5DC); + hTrie.SetValue("bisque", 0xFFE4C4); + hTrie.SetValue("black", 0x000000); + hTrie.SetValue("blanchedalmond", 0xFFEBCD); + hTrie.SetValue("blue", 0x99CCFF); // same as BLU/Counter-Terrorist team color + hTrie.SetValue("blueviolet", 0x8A2BE2); + hTrie.SetValue("brown", 0xA52A2A); + hTrie.SetValue("burlywood", 0xDEB887); + hTrie.SetValue("cadetblue", 0x5F9EA0); + hTrie.SetValue("chartreuse", 0x7FFF00); + hTrie.SetValue("chocolate", 0xD2691E); + hTrie.SetValue("collectors", 0xAA0000); // same as Collector's item quality in TF2 + hTrie.SetValue("common", 0xB0C3D9); // same as Common item rarity in Dota 2 + hTrie.SetValue("community", 0x70B04A); // same as Community item quality in TF2 + hTrie.SetValue("coral", 0xFF7F50); + hTrie.SetValue("cornflowerblue", 0x6495ED); + hTrie.SetValue("cornsilk", 0xFFF8DC); + hTrie.SetValue("corrupted", 0xA32C2E); // same as Corrupted item quality in Dota 2 + hTrie.SetValue("crimson", 0xDC143C); + hTrie.SetValue("cyan", 0x00FFFF); + hTrie.SetValue("darkblue", 0x00008B); + hTrie.SetValue("darkcyan", 0x008B8B); + hTrie.SetValue("darkgoldenrod", 0xB8860B); + hTrie.SetValue("darkgray", 0xA9A9A9); + hTrie.SetValue("darkgrey", 0xA9A9A9); + hTrie.SetValue("darkgreen", 0x006400); + hTrie.SetValue("darkkhaki", 0xBDB76B); + hTrie.SetValue("darkmagenta", 0x8B008B); + hTrie.SetValue("darkolivegreen", 0x556B2F); + hTrie.SetValue("darkorange", 0xFF8C00); + hTrie.SetValue("darkorchid", 0x9932CC); + hTrie.SetValue("darkred", 0x8B0000); + hTrie.SetValue("darksalmon", 0xE9967A); + hTrie.SetValue("darkseagreen", 0x8FBC8F); + hTrie.SetValue("darkslateblue", 0x483D8B); + hTrie.SetValue("darkslategray", 0x2F4F4F); + hTrie.SetValue("darkslategrey", 0x2F4F4F); + hTrie.SetValue("darkturquoise", 0x00CED1); + hTrie.SetValue("darkviolet", 0x9400D3); + hTrie.SetValue("deeppink", 0xFF1493); + hTrie.SetValue("deepskyblue", 0x00BFFF); + hTrie.SetValue("dimgray", 0x696969); + hTrie.SetValue("dimgrey", 0x696969); + hTrie.SetValue("dodgerblue", 0x1E90FF); + hTrie.SetValue("exalted", 0xCCCCCD); // same as Exalted item quality in Dota 2 + hTrie.SetValue("firebrick", 0xB22222); + hTrie.SetValue("floralwhite", 0xFFFAF0); + hTrie.SetValue("forestgreen", 0x228B22); + hTrie.SetValue("frozen", 0x4983B3); // same as Frozen item quality in Dota 2 + hTrie.SetValue("fuchsia", 0xFF00FF); + hTrie.SetValue("fullblue", 0x0000FF); + hTrie.SetValue("fullred", 0xFF0000); + hTrie.SetValue("gainsboro", 0xDCDCDC); + hTrie.SetValue("genuine", 0x4D7455); // same as Genuine item quality in TF2 + hTrie.SetValue("ghostwhite", 0xF8F8FF); + hTrie.SetValue("gold", 0xFFD700); + hTrie.SetValue("goldenrod", 0xDAA520); + hTrie.SetValue("gray", 0xCCCCCC); // same as spectator team color + hTrie.SetValue("grey", 0xCCCCCC); + hTrie.SetValue("green", 0x3EFF3E); + hTrie.SetValue("greenyellow", 0xADFF2F); + hTrie.SetValue("haunted", 0x38F3AB); // same as Haunted item quality in TF2 + hTrie.SetValue("honeydew", 0xF0FFF0); + hTrie.SetValue("hotpink", 0xFF69B4); + hTrie.SetValue("immortal", 0xE4AE33); // same as Immortal item rarity in Dota 2 + hTrie.SetValue("indianred", 0xCD5C5C); + hTrie.SetValue("indigo", 0x4B0082); + hTrie.SetValue("ivory", 0xFFFFF0); + hTrie.SetValue("khaki", 0xF0E68C); + hTrie.SetValue("lavender", 0xE6E6FA); + hTrie.SetValue("lavenderblush", 0xFFF0F5); + hTrie.SetValue("lawngreen", 0x7CFC00); + hTrie.SetValue("legendary", 0xD32CE6); // same as Legendary item rarity in Dota 2 + hTrie.SetValue("lemonchiffon", 0xFFFACD); + hTrie.SetValue("lightblue", 0xADD8E6); + hTrie.SetValue("lightcoral", 0xF08080); + hTrie.SetValue("lightcyan", 0xE0FFFF); + hTrie.SetValue("lightgoldenrodyellow", 0xFAFAD2); + hTrie.SetValue("lightgray", 0xD3D3D3); + hTrie.SetValue("lightgrey", 0xD3D3D3); + hTrie.SetValue("lightgreen", 0x99FF99); + hTrie.SetValue("lightpink", 0xFFB6C1); + hTrie.SetValue("lightsalmon", 0xFFA07A); + hTrie.SetValue("lightseagreen", 0x20B2AA); + hTrie.SetValue("lightskyblue", 0x87CEFA); + hTrie.SetValue("lightslategray", 0x778899); + hTrie.SetValue("lightslategrey", 0x778899); + hTrie.SetValue("lightsteelblue", 0xB0C4DE); + hTrie.SetValue("lightyellow", 0xFFFFE0); + hTrie.SetValue("lime", 0x00FF00); + hTrie.SetValue("limegreen", 0x32CD32); + hTrie.SetValue("linen", 0xFAF0E6); + hTrie.SetValue("magenta", 0xFF00FF); + hTrie.SetValue("maroon", 0x800000); + hTrie.SetValue("mediumaquamarine", 0x66CDAA); + hTrie.SetValue("mediumblue", 0x0000CD); + hTrie.SetValue("mediumorchid", 0xBA55D3); + hTrie.SetValue("mediumpurple", 0x9370D8); + hTrie.SetValue("mediumseagreen", 0x3CB371); + hTrie.SetValue("mediumslateblue", 0x7B68EE); + hTrie.SetValue("mediumspringgreen", 0x00FA9A); + hTrie.SetValue("mediumturquoise", 0x48D1CC); + hTrie.SetValue("mediumvioletred", 0xC71585); + hTrie.SetValue("midnightblue", 0x191970); + hTrie.SetValue("mintcream", 0xF5FFFA); + hTrie.SetValue("mistyrose", 0xFFE4E1); + hTrie.SetValue("moccasin", 0xFFE4B5); + hTrie.SetValue("mythical", 0x8847FF); // same as Mythical item rarity in Dota 2 + hTrie.SetValue("navajowhite", 0xFFDEAD); + hTrie.SetValue("navy", 0x000080); + hTrie.SetValue("normal", 0xB2B2B2); // same as Normal item quality in TF2 + hTrie.SetValue("oldlace", 0xFDF5E6); + hTrie.SetValue("olive", 0x9EC34F); + hTrie.SetValue("olivedrab", 0x6B8E23); + hTrie.SetValue("orange", 0xFFA500); + hTrie.SetValue("orangered", 0xFF4500); + hTrie.SetValue("orchid", 0xDA70D6); + hTrie.SetValue("palegoldenrod", 0xEEE8AA); + hTrie.SetValue("palegreen", 0x98FB98); + hTrie.SetValue("paleturquoise", 0xAFEEEE); + hTrie.SetValue("palevioletred", 0xD87093); + hTrie.SetValue("papayawhip", 0xFFEFD5); + hTrie.SetValue("peachpuff", 0xFFDAB9); + hTrie.SetValue("peru", 0xCD853F); + hTrie.SetValue("pink", 0xFFC0CB); + hTrie.SetValue("plum", 0xDDA0DD); + hTrie.SetValue("powderblue", 0xB0E0E6); + hTrie.SetValue("purple", 0x800080); + hTrie.SetValue("rare", 0x4B69FF); // same as Rare item rarity in Dota 2 + hTrie.SetValue("red", 0xFF4040); // same as RED/Terrorist team color + hTrie.SetValue("rosybrown", 0xBC8F8F); + hTrie.SetValue("royalblue", 0x4169E1); + hTrie.SetValue("saddlebrown", 0x8B4513); + hTrie.SetValue("salmon", 0xFA8072); + hTrie.SetValue("sandybrown", 0xF4A460); + hTrie.SetValue("seagreen", 0x2E8B57); + hTrie.SetValue("seashell", 0xFFF5EE); + hTrie.SetValue("selfmade", 0x70B04A); // same as Self-Made item quality in TF2 + hTrie.SetValue("sienna", 0xA0522D); + hTrie.SetValue("silver", 0xC0C0C0); + hTrie.SetValue("skyblue", 0x87CEEB); + hTrie.SetValue("slateblue", 0x6A5ACD); + hTrie.SetValue("slategray", 0x708090); + hTrie.SetValue("slategrey", 0x708090); + hTrie.SetValue("snow", 0xFFFAFA); + hTrie.SetValue("springgreen", 0x00FF7F); + hTrie.SetValue("steelblue", 0x4682B4); + hTrie.SetValue("strange", 0xCF6A32); // same as Strange item quality in TF2 + hTrie.SetValue("tan", 0xD2B48C); + hTrie.SetValue("teal", 0x008080); + hTrie.SetValue("thistle", 0xD8BFD8); + hTrie.SetValue("tomato", 0xFF6347); + hTrie.SetValue("turquoise", 0x40E0D0); + hTrie.SetValue("uncommon", 0xB0C3D9); // same as Uncommon item rarity in Dota 2 + hTrie.SetValue("unique", 0xFFD700); // same as Unique item quality in TF2 + hTrie.SetValue("unusual", 0x8650AC); // same as Unusual item quality in TF2 + hTrie.SetValue("valve", 0xA50F79); // same as Valve item quality in TF2 + hTrie.SetValue("vintage", 0x476291); // same as Vintage item quality in TF2 + hTrie.SetValue("violet", 0xEE82EE); + hTrie.SetValue("wheat", 0xF5DEB3); + hTrie.SetValue("white", 0xFFFFFF); + hTrie.SetValue("whitesmoke", 0xF5F5F5); + hTrie.SetValue("yellow", 0xFFFF00); + hTrie.SetValue("yellowgreen", 0x9ACD32); + + return hTrie; +} diff --git a/src/scripting/include/no_op.sp b/src/scripting/include/no_op.sp index cd65da6..5920387 100644 --- a/src/scripting/include/no_op.sp +++ b/src/scripting/include/no_op.sp @@ -2,6 +2,7 @@ #include #define NULLPTR view_as
(0) +#define BUFFER_SIZE 128 // The original byte content of the patches StringMap Originals; @@ -85,4 +86,49 @@ stock void RecoverFunction(GameData gamedata, const char[] name, const char[] si int FuncSize = GetKeyInt(gamedata, sizename); RestoreAddress(Func, name, FuncSize); +} + +public Action Command_NoOpStatus(int client, int argc) +{ + ReplyToCommand(client, "[NoOp] Hello!"); + + StringMapSnapshot snapshot = Originals.Snapshot(); + int length = snapshot.Length; + + ReplyToCommand(client, "[NoOp] Found %i patches.", length); + + for (int i = 0; i < length; i++) + { + // Recover key + char key[BUFFER_SIZE]; + snapshot.GetKey(i, key, sizeof(key)); + + // Recover originals + any[] originals = new any[BUFFER_SIZE]; + int original_length = 0; + if (!Originals.GetArray(key, originals, BUFFER_SIZE, original_length)) + { + LogError("Unable to recover originals for no-op '%s'.", key); + ReplyToCommand(client, "[NoOp] '%s': Couldn't find original for patch", key); + continue; + } + + char[] original_hex = new char[original_length * 8]; + for (int j = 0; j < original_length; j++) + { + + Format(original_hex, original_length * 8, "%s %X", original_hex, originals[j]); + } + + ReplyToCommand(client, "[NoOp] '%s': Original [%i] <%s >", key, original_length, original_hex); + + } + + ReplyToCommand(client, "[NoOp] Goodbye!"); + return Plugin_Handled; +} + +stock void NoOpCommand(const char[] name) +{ + RegAdminCmd(name, Command_NoOpStatus, ADMFLAG_RCON, "Displays patch status and information", "no_op"); } \ No newline at end of file diff --git a/src/scripting/include/version.sp b/src/scripting/include/version.sp new file mode 100644 index 0000000..e81e4c5 --- /dev/null +++ b/src/scripting/include/version.sp @@ -0,0 +1,2 @@ + +#define PLUGIN_VERSION "1.1" \ No newline at end of file diff --git a/src/scripting/sourceforks_antilag.sp b/src/scripting/sourceforks_antilag.sp index f32c9f2..701af39 100644 --- a/src/scripting/sourceforks_antilag.sp +++ b/src/scripting/sourceforks_antilag.sp @@ -6,40 +6,65 @@ // https://forums.alliedmods.net/showthread.php?t=321932 // https://forums.alliedmods.net/showthread.php?t=332721 +// Punishment behavior: +// Each player will receive a "heat" upon entering the server. +// This heat is decremented by 1 every time an exploit attempt is detected. +// (This gives players with, say, bad connections a safety buffer) +// When the heat reaches 0, the player is punished. +// However, if 30 seconds have passed and no expoit attempts were detected, +// the player is exonerated of suspicion. + +// I'm not sure it's even possible to trigger this exploit on a vanilla client. +// But I don't want to use the scream test on that. + #include #include #include #include "no_op.sp" +#include "admin_utils.sp" +#include "version.sp" -#define PLUGIN_VERSION "1.1" -#define ARR_MAXPLAYERS MAXPLAYERS+1 - -public Plugin:myinfo = -{ - name = "[SourceForks] [CSGO] Server Exploit Fix [5/28/2021 & 3/7/2020]", - author = "backwards", +public Plugin: myinfo = { + name = "[SourceForks] [CSGO] Server Exploit Fix [5/28/2021 & 3/7/2020]", + author = "backwards", description = "Fixes Several Server Lag Exploits", - version = PLUGIN_VERSION, - url = "http://www.steamcommunity.com/id/mypassword" + version = PLUGIN_VERSION, + url = "http://www.steamcommunity.com/id/mypassword" } -#define DEBUG 0 +#define DEBUG 0 + +#define ARR_MAXPLAYERS MAXPLAYERS + 1 -#define GAMEDATA_FILE "sourceforks_antilag.games" -#define DEFAULT_HEAT (6*60) -#define HEAT_SUSPICIOUS (5 * 60) -#define HEAT_ATTACKER (2 * 60) +#define GAMEDATA_FILE "sourceforks_antilag.games" +#define TICKRATE 128 -GameData Config; -ConVar ConPunishment; -int ClientHeat[ARR_MAXPLAYERS]; +// How many seconds before the state is refreshed and attacking players are banned +#define TIMER_REFRESH 5 +#define TIMER_REFRESH_FLOAT 5.0 +// How many seconds after an attack a player should be exonerated +#define TIMER_CLEAN 15 + +#define DEFAULT_HEAT (TIMER_CLEAN * TICKRATE) +#define HEAT_BUMP_ON_REFRESH (TIMER_REFRESH * TICKRATE) +// It should be impossible for a normal client to reach HEAT_SUSPICIOUS +#define HEAT_SUSPICIOUS ((TIMER_CLEAN - TIMER_REFRESH + 1) * TICKRATE) +// It is impossible for a normal client to reach HEAT_ATTACKER on 128-tick or below servers. +#define HEAT_ATTACKER ((TIMER_CLEAN - TIMER_REFRESH - 1) * TICKRATE) + +// Flag that describes admins to alert +#define ADMINFLAG_ALERT (ADMFLAG_BAN) + +GameData Config; +ConVar ConPunishment; +int ClientHeat[ARR_MAXPLAYERS]; enum Punishment { - Punish_None = 0, + Punish_None = 0, Punish_Alert = 1, - Punish_Kick = 2, - Punish_Ban = 3, + Punish_Kick = 2, + Punish_Ban = 3, }; // ================================================================================== @@ -48,9 +73,9 @@ enum Punishment stock void Blame(const char[] ip) { - #if DEBUG +#if DEBUG == 2 PrintToServer("Blaming '%s'", ip); - #endif +#endif for (int i = 1; i < MAXPLAYERS; i++) { @@ -65,10 +90,10 @@ stock void Blame(const char[] ip) if (!StrEqual(ip, clientIp)) continue; - - #if DEBUG + +#if DEBUG == 2 PrintToServer("Found client %i", i); - #endif +#endif if (ClientHeat[i] > 0) ClientHeat[i]--; @@ -83,9 +108,9 @@ public MRESReturn Mitigate_IPArg(DHookParam Params) char ip[32]; Params.GetString(1, ip, sizeof(ip)) - Blame(ip); + Blame(ip); - //Return.SetString(ip); + // Return.SetString(ip); return MRES_Handled; } @@ -94,7 +119,10 @@ public MRESReturn Mitigate_IPArg(DHookParam Params) // ================================================================================== public Action Timer_CoolDownPlayers(Handle self) { - Punishment punishment = Punishment:ConPunishment.IntValue; +#if DEBUG + PrintToServer("DEFAULT_HEAT = %i; HEAT_SUSPICIOUS = %i, HEAT_ATTACKER = %i, HEAT_BUMP_ON_REFRESH = %i", DEFAULT_HEAT, HEAT_SUSPICIOUS, HEAT_ATTACKER, HEAT_BUMP_ON_REFRESH); +#endif + Punishment punishment = Punishment: ConPunishment.IntValue; for (int i = 1; i < MAXPLAYERS; i++) { if (!IsClientInGame(i)) @@ -103,30 +131,24 @@ public Action Timer_CoolDownPlayers(Handle self) if (IsFakeClient(i)) continue; - #if DEBUG +#if DEBUG PrintToServer("Client %i Heat %i", i, ClientHeat[i]); - #endif +#endif // ================= // Alert adminstrators to suspicious network activity. if (ClientHeat[i] != DEFAULT_HEAT && punishment >= Punish_Alert) { - for (int admin = 1; admin < MAXPLAYERS; admin++) + if (ClientHeat[i] < HEAT_SUSPICIOUS) { - if (!CheckCommandAccess(admin, "", ADMFLAG_GENERIC | ADMFLAG_KICK, true)) - continue; - - if (ClientHeat[i] >= HEAT_SUSPICIOUS) - continue; - if (ClientHeat[i] > HEAT_ATTACKER) - PrintToChat(admin, "[SourceForks] Client '%N' (#%i) has unusual network activity.", i, GetClientUserId(i)); + CPrintToAdmins("{orange}Client '%N' ({default}#%i{orange}) has unusual network activity.", ADMINFLAG_ALERT, i, GetClientUserId(i)); if (ClientHeat[i] <= HEAT_ATTACKER) - PrintToChat(admin, "[SourceForks] Client '%N' (#%i) is attempting to DDOS the server.", i, GetClientUserId(i)); - + CPrintToAdmins("{darkred}Client '%N' ({default}#%i{darkred}) is attempting to DDOS the server.", ADMINFLAG_ALERT, i, GetClientUserId(i)); + if (ClientHeat[i] <= 0 && punishment >= Punish_Kick) - PrintToChat(admin, "[SourceForks] Punishing client %L for attempted DDOS.", i); + CPrintToAdmins("{darkred}Punishing client {default}%L{darkred} for attempted DDOS.", ADMINFLAG_ALERT, i); } } @@ -137,10 +159,35 @@ public Action Timer_CoolDownPlayers(Handle self) if (punishment == Punish_Kick) KickClientEx(i, "[SourceForks] Attempted DDOS"); - if (punishment == Punish_Ban) + // If greater than 3, treat as ban. + if (punishment >= Punish_Ban) BanClient(i, 0, BANFLAG_AUTO, "[SourceForks] Attempted DDOS", "[SourceForks] Attempted DDOS", "Sourceforks", 0); } + } + // Exonerate logic does not depend on users being in/game non-fake, etc. + // So it runs here. + for (int i = 1; i < MAXPLAYERS; i++) + { +#if DEBUG + int before = ClientHeat[i]; +#endif + // ================ + // Bump player heat to exonerate + ClientHeat[i] = ClientHeat[i] + HEAT_BUMP_ON_REFRESH; + // Don't go to infinity! + if (ClientHeat[i] > DEFAULT_HEAT) + ClientHeat[i] = DEFAULT_HEAT; + +#if DEBUG + if (!IsClientInGame(i)) + continue; + + if (IsFakeClient(i)) + continue; + + PrintToServer("Client %i Heat %i + %i = %i", i, before, HEAT_BUMP_ON_REFRESH, ClientHeat[i]); +#endif } return Plugin_Continue; @@ -148,9 +195,9 @@ public Action Timer_CoolDownPlayers(Handle self) public Action Timer_WarmUpPlayers(Handle self) { - #if DEBUG +#if DEBUG PrintToServer("Warming up"); - #endif +#endif for (int i = 1; i < MAXPLAYERS; i++) { @@ -168,12 +215,23 @@ DynamicDetour Detour_InvalidReliableState; public OnPluginStart() { + + // Reset existing client's heat + for (int i = 1; i < MAXPLAYERS; i++) + { + ClientHeat[i] = DEFAULT_HEAT; + } + NoOpInit(); if (GetEngineVersion() != Engine_CSGO) SetFailState("This plugin is only compatible with CS:GO."); - ConPunishment = CreateConVar("sourceforks_antilag_punishment", "3", "Sets punishment for players attempting to DDOS the server. 0 = None, 1 = Alert Admins (Default), 2 = Kick, 3 = Permanent Ban") + ConPunishment = CreateConVar("sourceforks_antilag_punishment", + "3", + "0 = None, 1 = Alert Admins, 2 = Kick, 3 = Permanent Ban (Default)", + FCVAR_PROTECTED | FCVAR_HIDDEN); + Config = LoadGameConfigFile(GAMEDATA_FILE); // Ratelimit spam @@ -185,10 +243,12 @@ public OnPluginStart() // Invalid reliable stats spam NoOpFunction(Config, "InvalidReliableState", "InvalidReliableStateSize"); + NoOpCommand("noop_antilag"); + // Now, mitigations: // InvalidReliableState { - Address invalid = Config.GetAddress("InvalidReliableState"); + Address invalid = Config.GetAddress("InvalidReliableState"); Detour_InvalidReliableState = DHookCreateDetour(invalid, CallConv_CDECL, ReturnType_Void, ThisPointer_Ignore); // EAX contains IP @@ -196,13 +256,11 @@ public OnPluginStart() Detour_InvalidReliableState.Enable(Hook_Pre, Mitigate_IPArg); } - for (int i = 1; i < MAXPLAYERS; i++) - { - ClientHeat[i] = DEFAULT_HEAT; - } + // Load configuration + AutoExecConfig(true, "sourceforks_antilag"); - CreateTimer(5.0, Timer_CoolDownPlayers, 0, TIMER_REPEAT); - CreateTimer(30.0, Timer_WarmUpPlayers, 0, TIMER_REPEAT) + CreateTimer(TIMER_REFRESH_FLOAT, Timer_CoolDownPlayers, 0, TIMER_REPEAT); + // CreateTimer(30.0, Timer_WarmUpPlayers, 0, TIMER_REPEAT) } diff --git a/src/scripting/sourceforks_movementunlocker.sp b/src/scripting/sourceforks_movementunlocker.sp index 1f8751c..7d55a2b 100644 --- a/src/scripting/sourceforks_movementunlocker.sp +++ b/src/scripting/sourceforks_movementunlocker.sp @@ -1,30 +1,29 @@ #include #include "no_op.sp" +#include "version.sp" -#define PLUGIN_VERSION "1.1" - -public Plugin:myinfo = -{ - name = "[SourceForks] [CSGO] Movement Unlocker", - author = "Peace-Maker", +public Plugin: myinfo = { + name = "[SourceForks] [CSGO] Movement Unlocker", + author = "Peace-Maker", description = "Removes max speed limitation from players on the ground. Feels like CS:S.", - version = PLUGIN_VERSION, - url = "http://www.wcfan.de/" + version = PLUGIN_VERSION, + url = "http://www.wcfan.de/" } #define GAMEDATA_FILE "sourceforks_movementunlocker.games" -GameData Config; + GameData Config; public OnPluginStart() { NoOpInit(); Config = LoadGameConfigFile(GAMEDATA_FILE); - + if (GetEngineVersion() != Engine_CSGO) SetFailState("This plugin is only compatible with CS:GO."); - + + NoOpCommand("noop_movementunlocker"); NoOpFunction(Config, "WalkMoveMaxSpeed", "WalkMoveMaxSpeedSize"); }