#pragma semicolon 1 #include #tryinclude #define PLUGIN_VERSION "1.1" #define PLUGIN_DESCRIPTION "Redux of Web Shortcuts and Dynamic MOTD Functionality" public Plugin:myinfo = { name = "Web Shortcuts", /* https://www.youtube.com/watch?v=h6k5jwllFfA&hd=1 */ author = "Kyle Sanderson, Nicholas Hastings", description = PLUGIN_DESCRIPTION, version = PLUGIN_VERSION, url = "http://SourceMod.net" }; enum _:States { Game_TF2 = (1<<0), Game_L4D = (1<<1), Big_MOTD = (1<<8) }; new g_iGameMode; enum _:FieldCheckFlags { Flag_Steam_ID = (1<<0), Flag_User_ID = (1<<1), Flag_Friend_ID = (1<<2), Flag_Name = (1<<3), Flag_IP = (1<<4), Flag_Language = (1<<5), Flag_Rate = (1<<6), Flag_Server_IP = (1<<7), Flag_Server_Port = (1<<8), Flag_Server_Name = (1<<9), Flag_Server_Custom = (1<<10), Flag_L4D_GameMode = (1<<11), Flag_Current_Map = (1<<12), Flag_Next_Map = (1<<13), Flag_GameDir = (1<<14), Flag_CurPlayers = (1<<15), #if defined _steamtools_included Flag_MaxPlayers = (1<<16), Flag_VACStatus = (1<<17), Flag_Server_Pub_IP = (1<<18), Flag_Steam_ConnStatus = (1<<19) #else Flag_MaxPlayers = (1<<16) #endif /* _steamtools_included */ }; #define IsTeamFortress2() (g_iGameMode & Game_TF2) #define IsLeftForDead() (g_iGameMode & Game_L4D) #define GoLargeOrGoHome() (IsTeamFortress2() && (g_iGameMode & Big_MOTD)) /*#include "Duck"*/ new Handle:g_hIndexArray = INVALID_HANDLE; new Handle:g_hFastLookupTrie = INVALID_HANDLE; new Handle:g_hCurrentTrie = INVALID_HANDLE; new String:g_sCurrentSection[128]; public OnPluginStart() { g_hIndexArray = CreateArray(); /* We'll only use this for cleanup to prevent handle leaks and what not. Our friend below doesn't have iteration, so we have to do this... */ g_hFastLookupTrie = CreateTrie(); AddCommandListener(Client_Say, "say"); AddCommandListener(Client_Say, "say_team"); /* From Psychonic */ Duck_OnPluginStart(); new Handle:cvarVersion = CreateConVar("webshortcutsredux_version", PLUGIN_VERSION, PLUGIN_DESCRIPTION, FCVAR_PLUGIN|FCVAR_NOTIFY); /* On a reload, this will be set to the old version. Let's update it. */ SetConVarString(cvarVersion, PLUGIN_VERSION); } public Action:Client_Say(iClient, const String:sCommand[], argc) { if (argc < 1 || !IsValidClient(iClient)) { return Plugin_Continue; /* Well. While we can probably have blank hooks, I doubt anyone wants this. Lets not waste cycles. Let the game deal with this. */ } decl String:sFirstArg[64]; /* If this is too small, let someone know. */ GetCmdArg(1, sFirstArg, sizeof(sFirstArg)); TrimString(sFirstArg); new Handle:hStoredTrie = INVALID_HANDLE; if (!GetTrieValue(g_hFastLookupTrie, sFirstArg, hStoredTrie) || hStoredTrie == INVALID_HANDLE) /* L -> R. Strings are R -> L, but that can change. */ { return Plugin_Continue; /* Didn't find anything. Bug out! */ } if (DealWithOurTrie(iClient, sFirstArg, hStoredTrie)) { return Plugin_Handled; /* We want other hooks to be called, I guess. We just don't want it to go to the game. */ } return Plugin_Continue; /* Well this is embarasing. We didn't actually hook this. Or atleast didn't intend to. */ } public bool:DealWithOurTrie(iClient, const String:sHookedString[], Handle:hStoredTrie) { decl String:sUrl[256]; if (!GetTrieString(hStoredTrie, "Url", sUrl, sizeof(sUrl))) { LogError("Unable to find a Url for: \"%s\".", sHookedString); return false; } new iUrlBits; if (!GetTrieValue(hStoredTrie, "UrlBits", iUrlBits)) { iUrlBits = 0; /* That's fine, there are no replacements! Less work for us. */ } decl String:sTitle[256]; new iTitleBits; if (!GetTrieString(hStoredTrie, "Title", sTitle, sizeof(sTitle))) { sTitle[0] = '\0'; /* We don't really need a title. Don't worry, it's cool. */ iTitleBits = 0; } else { if (!GetTrieValue(hStoredTrie, "TitleBits", iTitleBits)) { iTitleBits = 0; /* That's fine, there are no replacements! Less work for us. */ } } Duck_DoReplacements(iClient, sUrl, iUrlBits, sTitle, iTitleBits); /* Arrays are passed by reference. Variables are copied. */ new bool:bBig; new bool:bNotSilent = true; GetTrieValue(hStoredTrie, "Silent", bNotSilent); if (GoLargeOrGoHome()) { GetTrieValue(hStoredTrie, "Big", bBig); } decl String:sMessage[256]; if (GetTrieString(hStoredTrie, "Msg", sMessage, sizeof(sMessage))) { new iMsgBits; GetTrieValue(hStoredTrie, "MsgBits", iMsgBits); if (iMsgBits != 0) { Duck_DoReplacements(iClient, sMessage, iMsgBits, sMessage, 0); /* Lame Hack for now */ } PrintToChatAll("%s", sMessage); } DisplayMOTDWithOptions(iClient, sTitle, sUrl, bBig, bNotSilent, MOTDPANEL_TYPE_URL); return true; } public ClearExistingData() { new Handle:hHandle = INVALID_HANDLE; for (new i = (GetArraySize(g_hIndexArray) - 1); i >= 0; i--) { hHandle = GetArrayCell(g_hIndexArray, i); if (hHandle == INVALID_HANDLE) { continue; } delete hHandle; } ClearArray(g_hIndexArray); ClearTrie(g_hFastLookupTrie); } public OnConfigsExecuted() { ClearExistingData(); decl String:sPath[256]; BuildPath(Path_SM, sPath, sizeof(sPath), "configs/Webshortcuts.txt"); if (!FileExists(sPath)) { return; } ProcessFile(sPath); } public ProcessFile(const String:sPathToFile[]) { new Handle:hSMC = SMC_CreateParser(); SMC_SetReaders(hSMC, SMCNewSection, SMCReadKeyValues, SMCEndSection); new iLine; new SMCError:ReturnedError = SMC_ParseFile(hSMC, sPathToFile, iLine); /* Calls the below functions, then execution continues. */ if (ReturnedError != SMCError_Okay) { decl String:sError[256]; SMC_GetErrorString(ReturnedError, sError, sizeof(sError)); if (iLine > 0) { LogError("Could not parse file (Line: %d, File \"%s\"): %s.", iLine, sPathToFile, sError); delete hSMC; /* Sneaky Handles. */ return; } LogError("Parser encountered error (File: \"%s\"): %s.", sPathToFile, sError); } delete hSMC; } public SMCResult:SMCNewSection(Handle:smc, const String:name[], bool:opt_quotes) { if (!opt_quotes) { LogError("Invalid Quoting used with Section: %s.", name); } strcopy(g_sCurrentSection, sizeof(g_sCurrentSection), name); if (GetTrieValue(g_hFastLookupTrie, name, g_hCurrentTrie)) { return SMCParse_Continue; } else /* That's cool. Sounds like an initial insertion. Just wanted to make sure! */ { g_hCurrentTrie = CreateTrie(); PushArrayCell(g_hIndexArray, g_hCurrentTrie); /* Don't be leakin */ SetTrieValue(g_hFastLookupTrie, name, g_hCurrentTrie); SetTrieString(g_hCurrentTrie, "Name", name); } return SMCParse_Continue; } public SMCResult:SMCReadKeyValues(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes) { if (!key_quotes) { LogError("Invalid Quoting used with Key: \"%s\".", key); } else if (!value_quotes) { LogError("Invalid Quoting used with Key: \"%s\" Value: \"%s\".", key, value); } else if (g_hCurrentTrie == INVALID_HANDLE) { return SMCParse_Continue; } switch (key[0]) { case 'p','P': { if (!StrEqual(key, "Pointer", false)) { return SMCParse_Continue; } new iFindValue; iFindValue = FindValueInArray(g_hIndexArray, g_hCurrentTrie); if (iFindValue > -1) { RemoveFromArray(g_hIndexArray, iFindValue); } if (g_sCurrentSection[0] != '\0') { RemoveFromTrie(g_hFastLookupTrie, g_sCurrentSection); } delete g_hCurrentTrie; /* We're about to invalidate below */ if (GetTrieValue(g_hFastLookupTrie, value, g_hCurrentTrie)) { SetTrieValue(g_hFastLookupTrie, g_sCurrentSection, g_hCurrentTrie, true); return SMCParse_Continue; } g_hCurrentTrie = CreateTrie(); /* Ruhro, the thing this points to doesn't actually exist. Should we error or what? Nah, lets try and recover. */ PushArrayCell(g_hIndexArray, g_hCurrentTrie); /* Don't be losin handles */ SetTrieValue(g_hFastLookupTrie, g_sCurrentSection, g_hCurrentTrie, true); SetTrieString(g_hCurrentTrie, "Name", g_sCurrentSection, true); } case 'u','U': { if (!StrEqual(key, "Url", false)) { return SMCParse_Continue; } SetTrieString(g_hCurrentTrie, "Url", value, true); new iBits; Duck_CalcBits(value, iBits); /* Passed by Ref */ SetTrieValue(g_hCurrentTrie, "UrlBits", iBits, true); } case 'T','t': { if (!StrEqual(key, "Title", false)) { return SMCParse_Continue; } SetTrieString(g_hCurrentTrie, "Title", value, true); new iBits; Duck_CalcBits(value, iBits); /* Passed by Ref */ SetTrieValue(g_hCurrentTrie, "TitleBits", iBits, true); } case 'b','B': { if (!GoLargeOrGoHome() || !StrEqual(key, "Big", false)) /* Maybe they don't know they can't use it? Oh well. Protect the silly. */ { return SMCParse_Continue; } SetTrieValue(g_hCurrentTrie, "Big", TranslateToBool(value), true); } case 'h','H': { if (!StrEqual(key, "Hook", false)) { return SMCParse_Continue; } SetTrieValue(g_hFastLookupTrie, value, g_hCurrentTrie, true); } case 's', 'S': { if (!StrEqual(key, "Silent", false)) { return SMCParse_Continue; } SetTrieValue(g_hCurrentTrie, "Silent", !TranslateToBool(value), true); } case 'M', 'm': { if (!StrEqual(key, "Msg", false)) { return SMCParse_Continue; } SetTrieString(g_hCurrentTrie, "Msg", value, true); new iBits; Duck_CalcBits(value, iBits); /* Passed by Ref */ SetTrieValue(g_hCurrentTrie, "MsgBits", iBits, true); } } return SMCParse_Continue; } public SMCResult:SMCEndSection(Handle:smc) { g_hCurrentTrie = INVALID_HANDLE; g_sCurrentSection[0] = '\0'; } public bool:TranslateToBool(const String:sSource[]) { switch(sSource[0]) { case '0', 'n', 'N', 'f', 'F': { return false; } case '1', 'y', 'Y', 't', 'T', 's', 'S': { return true; } } return false; /* Assume False */ } public DisplayMOTDWithOptions(iClient, const String:sTitle[], const String:sUrl[], bool:bBig, bool:bNotSilent, iType) { new Handle:hKv = CreateKeyValues("motd"); if (bBig) { KvSetNum(hKv, "customsvr", 1); } KvSetNum(hKv, "type", iType); if (sTitle[0] != '\0') { KvSetString(hKv, "title", sTitle); } if (sUrl[0] != '\0') { KvSetString(hKv, "msg", sUrl); } ShowVGUIPanel(iClient, "info", hKv, bNotSilent); delete hKv; } static stock bool:IsValidClient(iClient) { return (0 < iClient <= MaxClients && IsClientInGame(iClient)); } /* Psychonics Realm */ #define FIELD_CHECK(%1,%2);\ if (StrContains(source, %1) != -1) { field |= %2; } #define TOKEN_STEAM_ID "{STEAM_ID}" #define TOKEN_USER_ID "{USER_ID}" #define TOKEN_FRIEND_ID "{FRIEND_ID}" #define TOKEN_NAME "{NAME}" #define TOKEN_IP "{IP}" #define TOKEN_LANGUAGE "{LANGUAGE}" #define TOKEN_RATE "{RATE}" #define TOKEN_SERVER_IP "{SERVER_IP}" #define TOKEN_SERVER_PORT "{SERVER_PORT}" #define TOKEN_SERVER_NAME "{SERVER_NAME}" #define TOKEN_SERVER_CUSTOM "{SERVER_CUSTOM}" #define TOKEN_L4D_GAMEMODE "{L4D_GAMEMODE}" #define TOKEN_CURRENT_MAP "{CURRENT_MAP}" #define TOKEN_NEXT_MAP "{NEXT_MAP}" #define TOKEN_GAMEDIR "{GAMEDIR}" #define TOKEN_CURPLAYERS "{CURPLAYERS}" #define TOKEN_MAXPLAYERS "{MAXPLAYERS}" #if defined _steamtools_included #define TOKEN_VACSTATUS "{VAC_STATUS}" #define TOKEN_SERVER_PUB_IP "{SERVER_PUB_IP}" #define TOKEN_STEAM_CONNSTATUS "{STEAM_CONNSTATUS}" new g_bSteamTools; #endif /* _steamtools_included */ /* Cached values */ new String:g_szServerIp[16]; new String:g_szServerPort[6]; /* These can all be larger but whole buffer holds < 128 */ new String:g_szServerName[128]; new String:g_szServerCustom[128]; new String:g_szL4DGameMode[128]; new String:g_szCurrentMap[128]; new String:g_szGameDir[64]; /*new Handle:g_hCmdQueue[MAXPLAYERS+1];*/ #if defined _steamtools_included public Steam_FullyLoaded() { g_bSteamTools = true; } public OnLibraryRemoved(const String:sLibrary[]) { if (!StrEqual(sLibrary, "SteamTools", false)) { return; } g_bSteamTools = false; } #endif public Duck_OnPluginStart() { decl String:sGameDir[64]; GetGameFolderName(sGameDir, sizeof(sGameDir)); if (!strncmp(sGameDir, "tf", 2, false) || !strncmp(sGameDir, "tf_beta", 7, false)) { g_iGameMode |= Game_TF2; g_iGameMode |= Big_MOTD; } /* On a reload, these will already be registered and could be set to non-default */ if (IsTeamFortress2()) { /* AddCommandListener(Duck_TF2OnClose, "closed_htmlpage"); */ } LongIPToString(GetConVarInt(FindConVar("hostip")), g_szServerIp); GetConVarString(FindConVar("hostport"), g_szServerPort, sizeof(g_szServerPort)); new Handle:hostname = FindConVar("hostname"); decl String:szHostname[256]; GetConVarString(hostname, szHostname, sizeof(szHostname)); Duck_UrlEncodeString(g_szServerName, sizeof(g_szServerName), szHostname); HookConVarChange(hostname, OnCvarHostnameChange); decl String:szCustom[256]; new Handle:hCVARCustom = CreateConVar("WebShortcuts_Custom", "", "Custom String for this server."); GetConVarString(hCVARCustom, szCustom, sizeof(szCustom)); Duck_UrlEncodeString(g_szServerCustom, sizeof(g_szServerCustom), szCustom); HookConVarChange(hCVARCustom, OnCvarCustomChange); new iSDKVersion = GuessSDKVersion(); if (iSDKVersion == SOURCE_SDK_LEFT4DEAD || iSDKVersion == SOURCE_SDK_LEFT4DEAD2) { g_iGameMode |= Game_L4D; new Handle:hGameMode = FindConVar("mp_gamemode"); decl String:szGamemode[256]; GetConVarString(hGameMode, szGamemode, sizeof(szGamemode)); Duck_UrlEncodeString(g_szL4DGameMode, sizeof(g_szL4DGameMode), szGamemode); HookConVarChange(hGameMode, OnCvarGamemodeChange); } Duck_UrlEncodeString(g_szGameDir, sizeof(g_szGameDir), sGameDir); } public OnMapStart() { decl String:sTempMap[sizeof(g_szCurrentMap)]; GetCurrentMap(sTempMap, sizeof(sTempMap)); Duck_UrlEncodeString(g_szCurrentMap, sizeof(g_szCurrentMap), sTempMap); } stock Duck_DoReplacements(iClient, String:sUrl[256], iUrlBits, String:sTitle[256], iTitleBits) /* Huge thanks to Psychonic */ { if (iUrlBits & Flag_Steam_ID || iTitleBits & Flag_Steam_ID) { decl String:sSteamId[64]; if (GetClientAuthString(iClient, sSteamId, sizeof(sSteamId))) { ReplaceString(sSteamId, sizeof(sSteamId), ":", "%3a"); if (iTitleBits & Flag_Steam_ID) ReplaceString(sTitle, sizeof(sTitle), TOKEN_STEAM_ID, sSteamId); if (iUrlBits & Flag_Steam_ID) ReplaceString(sUrl, sizeof(sUrl), TOKEN_STEAM_ID, sSteamId); } else { if (iTitleBits & Flag_Steam_ID) ReplaceString(sTitle, sizeof(sTitle), TOKEN_STEAM_ID, ""); if (iUrlBits & Flag_Steam_ID) ReplaceString(sUrl, sizeof(sUrl), TOKEN_STEAM_ID, ""); } } if (iUrlBits & Flag_User_ID || iTitleBits & Flag_User_ID) { decl String:sUserId[16]; IntToString(GetClientUserId(iClient), sUserId, sizeof(sUserId)); if (iTitleBits & Flag_User_ID) ReplaceString(sTitle, sizeof(sTitle), TOKEN_USER_ID, sUserId); if (iUrlBits & Flag_User_ID) ReplaceString(sUrl, sizeof(sUrl), TOKEN_USER_ID, sUserId); } if (iUrlBits & Flag_Friend_ID || iTitleBits & Flag_Friend_ID) { decl String:sFriendId[64]; if (GetClientFriendID(iClient, sFriendId, sizeof(sFriendId))) { if (iTitleBits & Flag_Friend_ID) ReplaceString(sTitle, sizeof(sTitle), TOKEN_FRIEND_ID, sFriendId); if (iUrlBits & Flag_Friend_ID) ReplaceString(sUrl, sizeof(sUrl), TOKEN_FRIEND_ID, sFriendId); } else { if (iTitleBits & Flag_Friend_ID) ReplaceString(sTitle, sizeof(sTitle), TOKEN_FRIEND_ID, ""); if (iUrlBits & Flag_Friend_ID) ReplaceString(sUrl, sizeof(sUrl), TOKEN_FRIEND_ID, ""); } } if (iUrlBits & Flag_Name || iTitleBits & Flag_Name) { decl String:sName[MAX_NAME_LENGTH]; if (GetClientName(iClient, sName, sizeof(sName))) { decl String:sEncName[sizeof(sName)*3]; Duck_UrlEncodeString(sEncName, sizeof(sEncName), sName); if (iTitleBits & Flag_Name) ReplaceString(sTitle, sizeof(sTitle), TOKEN_NAME, sEncName); if (iUrlBits & Flag_Name) ReplaceString(sUrl, sizeof(sUrl), TOKEN_NAME, sEncName); } else { if (iTitleBits & Flag_Name) ReplaceString(sTitle, sizeof(sTitle), TOKEN_NAME, ""); if (iUrlBits & Flag_Name) ReplaceString(sUrl, sizeof(sUrl), TOKEN_NAME, ""); } } if (iUrlBits & Flag_IP || iTitleBits & Flag_IP) { decl String:sClientIp[32]; if (GetClientIP(iClient, sClientIp, sizeof(sClientIp))) { if (iTitleBits & Flag_IP) ReplaceString(sTitle, sizeof(sTitle), TOKEN_IP, sClientIp); if (iUrlBits & Flag_IP) ReplaceString(sUrl, sizeof(sUrl), TOKEN_IP, sClientIp); } else { if (iTitleBits & Flag_IP) ReplaceString(sTitle, sizeof(sTitle), TOKEN_IP, ""); if (iUrlBits & Flag_IP) ReplaceString(sUrl, sizeof(sUrl), TOKEN_IP, ""); } } if (iUrlBits & Flag_Language || iTitleBits & Flag_Language) { decl String:sLanguage[32]; if (GetClientInfo(iClient, "cl_language", sLanguage, sizeof(sLanguage))) { decl String:sEncLanguage[sizeof(sLanguage)*3]; Duck_UrlEncodeString(sEncLanguage, sizeof(sEncLanguage), sLanguage); if (iTitleBits & Flag_Language) ReplaceString(sTitle, sizeof(sTitle), TOKEN_LANGUAGE, sEncLanguage); if (iUrlBits & Flag_Language) ReplaceString(sUrl, sizeof(sUrl), TOKEN_LANGUAGE, sEncLanguage); } else { if (iTitleBits & Flag_Language) ReplaceString(sTitle, sizeof(sTitle), TOKEN_LANGUAGE, ""); if (iUrlBits & Flag_Language) ReplaceString(sUrl, sizeof(sUrl), TOKEN_LANGUAGE, ""); } } if (iUrlBits & Flag_Rate || iTitleBits & Flag_Rate) { decl String:sRate[16]; if (GetClientInfo(iClient, "rate", sRate, sizeof(sRate))) { /* due to iClient's rate being silly, this won't necessarily be all digits */ decl String:sEncRate[sizeof(sRate)*3]; Duck_UrlEncodeString(sEncRate, sizeof(sEncRate), sRate); if (iTitleBits & Flag_Rate) ReplaceString(sTitle, sizeof(sTitle), TOKEN_RATE, sEncRate); if (iUrlBits & Flag_Rate) ReplaceString(sUrl, sizeof(sUrl), TOKEN_RATE, sEncRate); } else { if (iTitleBits & Flag_Rate) ReplaceString(sTitle, sizeof(sTitle), TOKEN_RATE, ""); if (iUrlBits & Flag_Rate) ReplaceString(sUrl, sizeof(sUrl), TOKEN_RATE, ""); } } if (iTitleBits & Flag_Server_IP) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_IP, g_szServerIp); if (iUrlBits & Flag_Server_IP) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_IP, g_szServerIp); if (iTitleBits & Flag_Server_Port) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_PORT, g_szServerPort); if (iUrlBits & Flag_Server_Port) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_PORT, g_szServerPort); if (iTitleBits & Flag_Server_Name) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_NAME, g_szServerName); if (iUrlBits & Flag_Server_Name) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_NAME, g_szServerName); if (iTitleBits & Flag_Server_Custom) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_CUSTOM, g_szServerCustom); if (iUrlBits & Flag_Server_Custom) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_CUSTOM, g_szServerCustom); if (IsLeftForDead() && ((iUrlBits & Flag_L4D_GameMode) || (iTitleBits & Flag_L4D_GameMode))) { if (iTitleBits & Flag_L4D_GameMode) ReplaceString(sTitle, sizeof(sTitle), TOKEN_L4D_GAMEMODE, g_szL4DGameMode); if (iUrlBits & Flag_L4D_GameMode) ReplaceString(sUrl, sizeof(sUrl), TOKEN_L4D_GAMEMODE, g_szL4DGameMode); } if (iTitleBits & Flag_Current_Map) ReplaceString(sTitle, sizeof(sTitle), TOKEN_CURRENT_MAP, g_szCurrentMap); if (iUrlBits & Flag_Current_Map) ReplaceString(sUrl, sizeof(sUrl), TOKEN_CURRENT_MAP, g_szCurrentMap); if (iUrlBits & Flag_Next_Map || iTitleBits & Flag_Next_Map) { decl String:szNextMap[PLATFORM_MAX_PATH]; if (GetNextMap(szNextMap, sizeof(szNextMap))) { if (iTitleBits & Flag_Next_Map) ReplaceString(sTitle, sizeof(sTitle), TOKEN_NEXT_MAP, szNextMap); if (iUrlBits & Flag_Next_Map) ReplaceString(sUrl, sizeof(sUrl), TOKEN_NEXT_MAP, szNextMap); } else { if (iTitleBits & Flag_Next_Map) ReplaceString(sTitle, sizeof(sTitle), TOKEN_NEXT_MAP, ""); if (iUrlBits & Flag_Next_Map) ReplaceString(sUrl, sizeof(sUrl), TOKEN_NEXT_MAP, ""); } } if (iTitleBits & Flag_GameDir) ReplaceString(sTitle, sizeof(sTitle), TOKEN_GAMEDIR, g_szGameDir); if (iUrlBits & Flag_GameDir) ReplaceString(sUrl, sizeof(sUrl), TOKEN_GAMEDIR, g_szGameDir); if (iUrlBits & Flag_CurPlayers || iTitleBits & Flag_CurPlayers) { decl String:sCurPlayers[10]; IntToString(GetClientCount(false), sCurPlayers, sizeof(sCurPlayers)); if (iTitleBits & Flag_CurPlayers) ReplaceString(sTitle, sizeof(sTitle), TOKEN_CURPLAYERS, sCurPlayers); if (iUrlBits & Flag_CurPlayers) ReplaceString(sUrl, sizeof(sUrl), TOKEN_CURPLAYERS, sCurPlayers); } if (iUrlBits & Flag_MaxPlayers || iTitleBits & Flag_MaxPlayers) { decl String:maxplayers[10]; IntToString(MaxClients, maxplayers, sizeof(maxplayers)); if (iTitleBits & Flag_MaxPlayers) ReplaceString(sTitle, sizeof(sTitle), TOKEN_MAXPLAYERS, maxplayers); if (iUrlBits & Flag_MaxPlayers) ReplaceString(sUrl, sizeof(sUrl), TOKEN_MAXPLAYERS, maxplayers); } #if defined _steamtools_included if (iUrlBits & Flag_VACStatus || iTitleBits & Flag_VACStatus) { if (g_bSteamTools && Steam_IsVACEnabled()) { if (iTitleBits & Flag_VACStatus) ReplaceString(sTitle, sizeof(sTitle), TOKEN_VACSTATUS, "1"); if (iUrlBits & Flag_VACStatus) ReplaceString(sUrl, sizeof(sUrl), TOKEN_VACSTATUS, "1"); } else { if (iTitleBits & Flag_VACStatus) ReplaceString(sTitle, sizeof(sTitle), TOKEN_VACSTATUS, "0"); if (iUrlBits & Flag_VACStatus) ReplaceString(sUrl, sizeof(sUrl), TOKEN_VACSTATUS, "0"); } } if (iUrlBits & Flag_Server_Pub_IP || iTitleBits & Flag_Server_Pub_IP) { if (g_bSteamTools) { decl ip[4]; decl String:sIPString[16]; Steam_GetPublicIP(ip); FormatEx(sIPString, sizeof(sIPString), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); if (iTitleBits & Flag_Server_Pub_IP) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_PUB_IP, sIPString); if (iUrlBits & Flag_Server_Pub_IP) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_PUB_IP, sIPString); } else { if (iTitleBits & Flag_Server_Pub_IP) ReplaceString(sTitle, sizeof(sTitle), TOKEN_SERVER_PUB_IP, ""); if (iUrlBits & Flag_Server_Pub_IP) ReplaceString(sUrl, sizeof(sUrl), TOKEN_SERVER_PUB_IP, ""); } } if (iUrlBits & Flag_Steam_ConnStatus || iTitleBits & Flag_Steam_ConnStatus) { if (g_bSteamTools && Steam_IsConnected()) { if (iTitleBits & Flag_Steam_ConnStatus) ReplaceString(sTitle, sizeof(sTitle), TOKEN_STEAM_CONNSTATUS, "1"); if (iUrlBits & Flag_Steam_ConnStatus) ReplaceString(sUrl, sizeof(sUrl), TOKEN_STEAM_CONNSTATUS, "1"); } else { if (iTitleBits & Flag_Steam_ConnStatus) ReplaceString(sTitle, sizeof(sTitle), TOKEN_STEAM_CONNSTATUS, "0"); if (iUrlBits & Flag_Steam_ConnStatus) ReplaceString(sUrl, sizeof(sUrl), TOKEN_STEAM_CONNSTATUS, "0"); } } #endif /* _steamtools_included */ } stock bool:GetClientFriendID(client, String:sFriendID[], size) { #if defined _steamtools_included Steam_GetCSteamIDForClient(client, sFriendID, size); #else decl String:sSteamID[64]; if (!GetClientAuthString(client, sSteamID, sizeof(sSteamID))) { sFriendID[0] = '\0'; /* Sanitize incase the return isn't checked. */ return false; } TrimString(sSteamID); /* Just incase... */ if (StrEqual(sSteamID, "STEAM_ID_LAN", false)) { sFriendID[0] = '\0'; return false; } decl String:toks[3][16]; ExplodeString(sSteamID, ":", toks, sizeof(toks), sizeof(toks[])); new iServer = StringToInt(toks[1]); new iAuthID = StringToInt(toks[2]); new iFriendID = (iAuthID*2) + 60265728 + iServer; if (iFriendID >= 100000000) { decl String:temp[12], String:carry[12]; FormatEx(temp, sizeof(temp), "%d", iFriendID); FormatEx(carry, 2, "%s", temp); new icarry = StringToInt(carry[0]); new upper = 765611979 + icarry; FormatEx(temp, sizeof(temp), "%d", iFriendID); FormatEx(sFriendID, size, "%d%s", upper, temp[1]); } else { Format(sFriendID, size, "765611979%d", iFriendID); } #endif return true; } Duck_CalcBits(const String:source[], &field) { field = 0; FIELD_CHECK(TOKEN_STEAM_ID, Flag_Steam_ID); FIELD_CHECK(TOKEN_USER_ID, Flag_User_ID); FIELD_CHECK(TOKEN_FRIEND_ID, Flag_Friend_ID); FIELD_CHECK(TOKEN_NAME, Flag_Name); FIELD_CHECK(TOKEN_IP, Flag_IP); FIELD_CHECK(TOKEN_LANGUAGE, Flag_Language); FIELD_CHECK(TOKEN_RATE, Flag_Rate); FIELD_CHECK(TOKEN_SERVER_IP, Flag_Server_IP); FIELD_CHECK(TOKEN_SERVER_PORT, Flag_Server_Port); FIELD_CHECK(TOKEN_SERVER_NAME, Flag_Server_Name); FIELD_CHECK(TOKEN_SERVER_CUSTOM, Flag_Server_Custom); if (IsLeftForDead()) { FIELD_CHECK(TOKEN_L4D_GAMEMODE, Flag_L4D_GameMode); } FIELD_CHECK(TOKEN_CURRENT_MAP, Flag_Current_Map); FIELD_CHECK(TOKEN_NEXT_MAP, Flag_Next_Map); FIELD_CHECK(TOKEN_GAMEDIR, Flag_GameDir); FIELD_CHECK(TOKEN_CURPLAYERS, Flag_CurPlayers); FIELD_CHECK(TOKEN_MAXPLAYERS, Flag_MaxPlayers); #if defined _steamtools_included FIELD_CHECK(TOKEN_VACSTATUS, Flag_VACStatus); FIELD_CHECK(TOKEN_SERVER_PUB_IP, Flag_Server_Pub_IP); FIELD_CHECK(TOKEN_STEAM_CONNSTATUS, Flag_Steam_ConnStatus); #endif } /* Courtesy of Mr. Asher Baker */ stock LongIPToString(ip, String:szBuffer[16]) { FormatEx(szBuffer, sizeof(szBuffer), "%i.%i.%i.%i", (((ip & 0xFF000000) >> 24) & 0xFF), (((ip & 0x00FF0000) >> 16) & 0xFF), (((ip & 0x0000FF00) >> 8) & 0xFF), (((ip & 0x000000FF) >> 0) & 0xFF)); } /* loosely based off of PHP's urlencode */ stock Duck_UrlEncodeString(String:output[], size, const String:input[]) { new icnt = 0; new ocnt = 0; for(;;) { if (ocnt == size) { output[ocnt-1] = '\0'; return; } new c = input[icnt]; if (c == '\0') { output[ocnt] = '\0'; return; } // Use '+' instead of '%20'. // Still follows spec and takes up less of our limited buffer. if (c == ' ') { output[ocnt++] = '+'; } else if ((c < '0' && c != '-' && c != '.') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a' && c != '_') || (c > 'z' && c != '~')) { output[ocnt++] = '%'; Format(output[ocnt], size-strlen(output[ocnt]), "%x", c); ocnt += 2; } else { output[ocnt++] = c; } icnt++; } } public OnCvarHostnameChange(Handle:convar, const String:oldValue[], const String:newValue[]) { Duck_UrlEncodeString(g_szServerName, sizeof(g_szServerName), newValue); } public OnCvarGamemodeChange(Handle:convar, const String:oldValue[], const String:newValue[]) { Duck_UrlEncodeString(g_szL4DGameMode, sizeof(g_szL4DGameMode), newValue); } public OnCvarCustomChange(Handle:convar, const String:oldValue[], const String:newValue[]) { Duck_UrlEncodeString(g_szServerCustom, sizeof(g_szServerCustom), newValue); }