From 5a821bf3f458f5cd83933ef3b398887a1608c1ea Mon Sep 17 00:00:00 2001 From: Stephen Elliott Date: Mon, 28 Jan 2013 13:16:56 -0500 Subject: [PATCH 1/4] removed G message --- .../sourcemod/scripting/include/umc_utils.inc | 2672 ++++++++--------- 1 file changed, 1336 insertions(+), 1336 deletions(-) diff --git a/addons/sourcemod/scripting/include/umc_utils.inc b/addons/sourcemod/scripting/include/umc_utils.inc index b697f18..ecd44e0 100644 --- a/addons/sourcemod/scripting/include/umc_utils.inc +++ b/addons/sourcemod/scripting/include/umc_utils.inc @@ -1,1337 +1,1337 @@ -#if defined _umc_utils_included - #endinput -#endif -#define _umc_utils_included - -#pragma semicolon 1 - -#include - -#include - -#include -#include -#include -#include - -//************************************************************************************************// -// GENERAL UTILITIES // -//************************************************************************************************// - -#if UMC_DEBUG -//Prints a debug message. -stock DebugMessage(const String:message[], any:...) -{ - new size = strlen(message) + 255; - decl String:fMessage[size]; - VFormat(fMessage, size, message, 2); - - LogUMCMessage("DEBUG: %s", fMessage); -} -#define DEBUG_MESSAGE(%0) DebugMessage(%0); - -#else - -#define DEBUG_MESSAGE(%0) - -#endif - - -#define MIN(%0, %1) ((%0) < (%1)) ? (%0) : (%1) - - -stock LogUMCMessage(const String:message[], any:...) -{ - new size = strlen(message) + 255; - decl String:fMessage[size]; - VFormat(fMessage, size, message, 2); - - decl String:fileName[PLATFORM_MAX_PATH]; - decl String:timeStamp[64]; - FormatTime(timeStamp, sizeof(timeStamp), "%Y%m%d", GetTime()); - BuildPath(Path_SM, fileName, sizeof(fileName), "logs/UMC_%s.log", timeStamp); - - LogToFile(fileName, fMessage); -} - - -stock StringToUpper(String:str[]) -{ - new i = 0; - while (str[i] != 0) - { - str[i] = CharToUpper(str[i]); - } -} - - -//Utility function to build a map trie. -stock Handle:CreateMapTrie(const String:map[], const String:group[]) -{ - new Handle:trie = CreateTrie(); - SetTrieString(trie, MAP_TRIE_MAP_KEY, map); - SetTrieString(trie, MAP_TRIE_GROUP_KEY, group); - return trie; -} - - -//Utility function to get a KeyValues Handle from a filename, with the specified root key. -stock Handle:GetKvFromFile(const String:filename[], const String:rootKey[], bool:checkNorm=true) -{ - new Handle:kv = CreateKeyValues(rootKey); - - //Log an error and return empty handle if... - // ...the kv file fails to parse. - if (!(checkNorm && ConvertNormalMapcycle(kv, filename)) && !FileToKeyValues(kv, filename)) - { - LogError("KV ERROR: Unable to load KV file: %s", filename); - CloseHandle(kv); - return INVALID_HANDLE; - } - -#if UMC_DEBUG - LogKv(kv); -#endif - - return kv; -} - - -// -stock bool:ConvertNormalMapcycle(Handle:kv, const String:filename[]) -{ - DEBUG_MESSAGE("Opening Mapcycle File %s", filename) - new Handle:file = OpenFile(filename, "r"); - - if (file == INVALID_HANDLE) - return false; - - DEBUG_MESSAGE("Fetching first line.") - new String:firstLine[256]; - new bool:foundDef; - while (ReadFileLine(file, firstLine, sizeof(firstLine))) - { - TrimString(firstLine); - if (strlen(firstLine) > 0) - { - foundDef = true; - break; - } - } - - if (!foundDef) - { - DEBUG_MESSAGE("Couldn't find first line") - CloseHandle(file); - return false; - } - - DEBUG_MESSAGE("Checking first line for UMC header: \"%s\"", firstLine) - - static Handle:re = INVALID_HANDLE; - if (re == INVALID_HANDLE) - re = CompileRegex("^//!UMC\\s+([0-9]+)\\s*$"); - - decl String:buffer[5]; - - if (MatchRegex(re, firstLine) > 1) - GetRegexSubString(re, 1, buffer, sizeof(buffer)); - else - { - DEBUG_MESSAGE("Header was not found. Aborting.") - CloseHandle(file); - return false; - } - - DEBUG_MESSAGE("Making a map group.") - - static Handle:re2 = INVALID_HANDLE; - if (re2 == INVALID_HANDLE) - re2 = CompileRegex("^\\s*([^/\\\\:*?'\"<>|\\s]+)\\s*(?://.*)?$"); - - KvJumpToKey(kv, "Mapcycle", true); - KvSetNum(kv, "maps_invote", StringToInt(buffer)); - - DEBUG_MESSAGE("Parsing maps.") - - decl String:map[MAP_LENGTH]; - decl String:line[256]; - while (ReadFileLine(file, line, sizeof(line))) - { - TrimString(line); - if (MatchRegex(re2, line) > 1) - { - GetRegexSubString(re2, 1, map, sizeof(map)); - DEBUG_MESSAGE("Adding map: %s", map) - KvJumpToKey(kv, map, true); - KvGoBack(kv); - } - } - - CloseHandle(file); - - KvGoBack(kv); - - return true; -} - - -//Utility function to jump to a specific map in a mapcycle. -// kv: Mapcycle Keyvalues that must be at the root value. -stock bool:KvJumpToMap(Handle:kv, const String:map[]) -{ - if (!KvGotoFirstSubKey(kv)) - return false; - - decl String:mapName[MAP_LENGTH]; - - do - { - if (!KvGotoFirstSubKey(kv)) - continue; - do - { - KvGetSectionName(kv, mapName, sizeof(mapName)); - if (StrEqual(mapName, map)) - return true; - } while (KvGotoNextKey(kv)); - - KvGoBack(kv); - } while (KvGotoNextKey(kv)); - - KvGoBack(kv); - return false; -} - - -//Utility function to search for a group that contains the given map. -// kv: Mapcycle -// map: Map whose group we're looking for. -// buffer: Buffer to store the found group name. -// maxlen: Maximum length of the buffer. -stock bool:KvFindGroupOfMap(Handle:kv, const String:map[], String:buffer[], maxlen) -{ - if (!KvGotoFirstSubKey(kv)) - return false; - - decl String:mapName[MAP_LENGTH], String:groupName[MAP_LENGTH]; - do - { - KvGetSectionName(kv, groupName, sizeof(groupName)); - - if (!KvGotoFirstSubKey(kv)) - continue; - - do - { - KvGetSectionName(kv, mapName, sizeof(mapName)); - if (StrEqual(mapName, map, false)) - { - KvGoBack(kv); - KvGoBack(kv); - strcopy(buffer, maxlen, groupName); - return true; - } - } - while (KvGotoNextKey(kv)); - - KvGoBack(kv); - } - while (KvGotoNextKey(kv)); - - KvGoBack(kv); - - return false; -} - - -enum CustomHudFallbackType { - HudFallback_Chat, - HudFallback_Hint, - HudFallback_Center, - HudFallback_None -} - -//Color Arrays for colors in warning messages. -static g_iSColors[7] = {1, 3, 3, 4, 4, 5, 6}; -static String:g_sSColors[7][13] = {"{DEFAULT}", "{LIGHTGREEN}", "{TEAM}", "{GREEN}", "{RED}", - "{DARKGREEN}", "{YELLOW}"}; -static g_iTColors[13][3] = {{255, 255, 255}, {255, 0, 0}, { 0, 255, 0}, - { 0, 0, 255}, {255, 255, 0}, {255, 0, 255}, - { 0, 255, 255}, {255, 128, 0}, {255, 0, 128}, - {128, 255, 0}, { 0, 255, 128}, {128, 0, 255}, - { 0, 128, 255}}; -static String:g_sTColors[13][12] = {"{WHITE}", "{RED}", "{GREEN}", "{BLUE}", "{YELLOW}", "{PURPLE}", - "{CYAN}", "{ORANGE}", "{PINK}", "{OLIVE}", "{LIME}", "{VIOLET}", - "{LIGHTBLUE}"}; - -//Handle to the Center Message timer. For Vote Warnings. -static Handle:center_message_timer = INVALID_HANDLE; -static bool:center_warning_active = false; - -//Handle to the TF2 Game Message entity timer. For Vote Warnings. -//static Handle:game_message_timer = INVALID_HANDLE; -//static bool:game_message_active = false; - -//Displays a message to the server -stock DisplayServerMessage(const String:msg[], const String:type[]) -{ - //Kill timers for previous messages. - //if (game_message_active) - //{ - // TriggerTimer(game_message_timer); - //} - if (center_warning_active) - { - center_warning_active = false; - TriggerTimer(center_message_timer); - } - - if (strlen(msg) == 0) - return; - - decl String:message[255]; - strcopy(message, sizeof(message), msg); - - //Display a chat message ("S") if... - // ...the user specifies. - if (StrContains(type, "S") != -1) - { - new String:sColor[4]; - - Format(message, sizeof(message), "%c%s", 1, message); - - for (new c = 0; c < sizeof(g_iSColors); c++) - { - if (StrContains(message, g_sSColors[c])) - { - FormatEx(sColor, sizeof(sColor), "%c", g_iSColors[c]); - ReplaceString(message, sizeof(message), g_sSColors[c], sColor); - } - } - - PrintToChatAll(message); - } - - //Buffer to hold message in order to manipulate it. - decl String:sTextTmp[255]; - - //Display a top message ("T") if... - // ...the user specifies. - if (StrContains(type, "T") != -1) - { - strcopy(sTextTmp, sizeof(sTextTmp), message); - decl String:sColor[16]; - new iColor = -1, iPos = BreakString(sTextTmp, sColor, sizeof(sColor)); - - for (new i = 0; i < sizeof(g_sTColors); i++) - { - if (StrEqual(sColor, g_sTColors[i])) - iColor = i; - } - - if (iColor == -1) - { - iPos = 0; - iColor = 0; - } - - new Handle:hKv = CreateKeyValues("Stuff", "title", sTextTmp[iPos]); - KvSetColor(hKv, "color", g_iTColors[iColor][0], g_iTColors[iColor][1], - g_iTColors[iColor][2], 255); - KvSetNum(hKv, "level", 1); - KvSetNum(hKv, "time", 10); - - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i)) - CreateDialog(i, hKv, DialogType_Msg); - } - - CloseHandle(hKv); - } - - // Remove colors, because C,H,M methods do not support colors. - - //Remove a color from the message string for... - // ...each color in the Say color array. - for (new c = 0; c < sizeof(g_iSColors); c++) - { - if (StrContains(message, g_sSColors[c]) != -1) - ReplaceString(message, sizeof(message), g_sSColors[c], ""); - } - - //Remove a color from the message string for... - // ...each color in the Top color array. - for (new c = 0; c < sizeof(g_iTColors); c++) - { - if (StrContains(message, g_sTColors[c]) != -1) - ReplaceString(message, sizeof(message), g_sTColors[c], ""); - } - - //Display a center message ("C") if... - // ...the user specifies. - if (StrContains(type, "C") != -1) - { - PrintCenterTextAll(message); - - //Setup timer to keep the center message visible. - new Handle:hCenterAd; - center_message_timer = CreateDataTimer(1.0, Timer_CenterAd, hCenterAd, TIMER_REPEAT); - WritePackString(hCenterAd, message); - - center_warning_active = center_message_timer != INVALID_HANDLE; - if (!center_warning_active) - CloseHandle(hCenterAd); - } - - //Display a hint message ("H") if... - // ...the user specifies. - if (StrContains(type, "H") != -1) - PrintHintTextToAll(message); - - //Display a TF2 Game Event message ("G") if... - // ...the user specifies. - if (StrContains(type, "G") != -1) - TF2_SendCustomHudMessageAll("ico_time_10", -1, HudFallback_Hint, message); -} - - -//Called with each tick of the timer for center messages. Used to keep the message visible for an -//extended period. -public Action:Timer_CenterAd(Handle:timer, Handle:pack) -{ - decl String:sText[256]; - static iCount = 0; - - ResetPack(pack); - ReadPackString(pack, sText, sizeof(sText)); - - if (center_warning_active && ++iCount < 5) - { - PrintCenterTextAll(sText); - return Plugin_Continue; - } - else - { - iCount = 0; - center_message_timer = INVALID_HANDLE; - center_warning_active = false; - return Plugin_Stop; - } -} - - -// -stock TF2_SendCustomHudMesssage(client, const String:icon[], team=-1, CustomHudFallbackType:fallback, - const String:format[], any:...) -{ - decl String:message[256]; - VFormat(message, sizeof(message), format, 6); - - new Handle:pack = CreateDataPack(); - WritePackCell(pack, GetClientUserId(client)); - WritePackString(pack, message); - WritePackString(pack, icon); - WritePackCell(pack, team); - WritePackCell(pack, fallback); - QueryClientConVar(client, "cl_hud_minmode", MinModeCheckFinished, pack); -} - - -// -stock TF2_SendCustomHudMessageAll(const String:icon[], team=-1, CustomHudFallbackType:fallback, - const String:format[], any:...) -{ - decl String:message[256]; - VFormat(message, sizeof(message), format, 5); - - for (new i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || IsFakeClient(i)) - continue; - - new Handle:pack = CreateDataPack(); - WritePackCell(pack, GetClientUserId(i)); - WritePackString(pack, message); - WritePackString(pack, icon); - WritePackCell(pack, team); - WritePackCell(pack, _:fallback); - QueryClientConVar(i, "cl_hud_minmode", MinModeCheckFinished, pack); - } -} - - -// -public MinModeCheckFinished(QueryCookie:cookie, uid, ConVarQueryResult:result, const String:cvarName[], - const String:cvarValue[], any:pack) -{ - ResetPack(pack); - new client = GetClientOfUserId(ReadPackCell(pack)); - if (client == 0) - { - CloseHandle(pack); - return; - } - - decl String:message[256], String:icon[256]; - ReadPackString(pack, message, sizeof(message)); - ReadPackString(pack, icon, sizeof(icon)); - - new team = ReadPackCell(pack); - - if (result != ConVarQuery_Okay || StringToInt(cvarValue) == 0) - { - switch (CustomHudFallbackType:ReadPackCell(pack)) - { - case HudFallback_Chat: - PrintToChat(client, "%s", message); - case HudFallback_Hint: - PrintHintText(client, "%s", message); - case HudFallback_Center: - PrintCenterText(client, "%s", message); - } - } - else - { - new Handle:msg = StartMessageOne("HudNotifyCustom", client); - BfWriteString(msg, message); - BfWriteString(msg, icon); - BfWriteByte(msg, ((team == -1) ? GetClientTeam(client) : team)); - EndMessage(); - } - - CloseHandle(pack); -} - - -//Sets all elements of an array of booleans to false. -stock ResetArray(bool:arr[], size) -{ - for (new i = 0; i < size; i++) - arr[i] = false; -} - - -//Utility function to cache a sound. -stock CacheSound(const String:sound[]) -{ - //Handle the sound if... - // ...it is defined. - if (strlen(sound) > 0) - { - //Filepath buffer - decl String:filePath[PLATFORM_MAX_PATH]; - - //Format sound to the correct directory. - FormatEx(filePath, sizeof(filePath), "sound/%s", sound); - - //Log an error and don't cache the sound if... - // ...the sound file does not exist - if (!FileExists(filePath)) - LogError("SOUND ERROR: Sound file '%s' does not exist.", filePath); - //Otherwise, cache the sound. - else - { - //Make sure clients download the sound if they don't have it. - AddFileToDownloadsTable(filePath); - - //Cache it. - PrecacheSound(sound, true); - - //Log an error if... - // ...the sound failed to be cached. - if (!IsSoundPrecached(filePath)) - LogError("SOUND ERROR: Failed to precache sound file '%s'", sound); - } - } -} - - -//Fetch the next index of the menu. -// size: the size of the menu -// scramble: whether or not a random index should be picked. -stock GetNextMenuIndex(size, bool:scramble) -{ - return scramble ? GetRandomInt(0, size) : size; -} - - -//Inserts given string into given array at given index. -stock InsertArrayString(Handle:arr, index, const String:value[]) -{ - if (GetArraySize(arr) > index) - { - ShiftArrayUp(arr, index); - SetArrayString(arr, index, value); - } - else - PushArrayString(arr, value); -} - - -//Inserts given cell into given adt_array at given index, -stock InsertArrayCell(Handle:arr, index, any:cell) -{ - if (GetArraySize(arr) > index) - { - ShiftArrayUp(arr, index); - SetArrayCell(arr, index, cell); - } - else - PushArrayCell(arr, cell); -} - - -//Deletes values off the end of an array until it is down to the given size. -stock TrimArray(Handle:arr, size) -{ - //Remove elements from the start of an array while... - // ...the size of the array is greater than the required size. - new asize = GetArraySize(arr); - while (asize > size) - RemoveFromArray(arr, --asize); -} - - -//Adds the given map to the given memory array. -// mapName: the name of the map -// arr: the memory array we're adding to -// size: the maximum size of the memory array -stock AddToMemoryArray(const String:mapName[], Handle:arr, size) -{ - //Add the new map to the beginning of the array. - InsertArrayString(arr, 0, mapName); - - //Trim the array down to size. - TrimArray(arr, size); -} - - -//Adds entire array to the given menu. -stock AddArrayToMenu(Handle:menu, Handle:arr, Handle:dispArr=INVALID_HANDLE) -{ - decl String:map[MAP_LENGTH], String:disp[MAP_LENGTH]; - new arrSize = GetArraySize(arr); - new dispSize = (dispArr != INVALID_HANDLE) ? GetArraySize(dispArr) : 0; - for (new i = 0; i < arrSize; i++) - { - GetArrayString(arr, i, map, sizeof(map)); - if (i >= dispSize) - disp = map; - else - GetArrayString(dispArr, i, disp, sizeof(disp)); - AddMenuItem(menu, map, disp); - } -} - - -//Changes the map in 5 seconds. -stock ForceChangeInFive(const String:map[], const String:reason[]="") -{ - //Notify the server. - PrintToChatAll("\x03[UMC]\x01 %t", "Map Change in 5"); - - //Setup the change. - ForceChangeMap(map, 5.0, reason); -} - - -//Changes the map after the specified time period. -stock ForceChangeMap(const String:map[], Float:time, const String:reason[]="") -{ - LogUMCMessage("%s: Changing map to '%s' in %.f seconds.", reason, map, time); - - //Setup the timer. - new Handle:pack; - CreateDataTimer( - time, - Handle_MapChangeTimer, - pack, - TIMER_FLAG_NO_MAPCHANGE - ); - WritePackString(pack, map); - WritePackString(pack, reason); -} - - -//Called after the mapchange timer is completed. -public Action:Handle_MapChangeTimer(Handle:timer, Handle:pack) -{ - //Get map from the timer's pack. - decl String:map[MAP_LENGTH], String:reason[255]; - ResetPack(pack); - ReadPackString(pack, map, sizeof(map)); - ReadPackString(pack, reason, sizeof(reason)); - - DEBUG_MESSAGE("Changing map to %s: %s", map, reason) - - //Change the map. - ForceChangeLevel(map, reason); -} - - -//Determines if the current server time is between the given min and max. -stock bool:IsTimeBetween(min, max) -{ - //Get the current server time. - decl String:time[5]; - FormatTime(time, sizeof(time), "%H%M"); - new theTime = StringToInt(time); - - //Handle wrap-around case if... - // ...max time is less than min time. - if (max <= min) - { - max += 2400; - if (theTime <= min) - { - theTime += 2400; - } - } - return min <= theTime && theTime <= max; -} - - -//Determines if the current server player count is between the given min and max. -stock bool:IsPlayerCountBetween(min, max) -{ - //Get the current number of players. - new numplayers = GetRealClientCount(); - return min <= numplayers && numplayers <= max; -} - - -//Converts an adt_array to a standard array. -stock ConvertAdtArray(Handle:arr, any:newArr[], size) -{ - new arraySize = GetArraySize(arr); - new min = size < arraySize ? size : arraySize; - for (new i = 0; i < min; i++) - newArr[i] = GetArrayCell(arr, i); -} - - -// -stock ConvertArray(const any:arr[], amt, Handle:newArr) -{ - for (new i = 0; i < amt; i++) - PushArrayCell(newArr, arr[i]); -} - - -//Selects one random name from the given name array, using the weights in the supplies weight array. -//Stores the result in buffer. -stock bool:GetWeightedRandomSubKey(String:buffer[], size, Handle:weightArr, Handle:nameArr, &index=0) -{ - //Calc total number of maps we're choosing. - new total = GetArraySize(weightArr); - - DEBUG_MESSAGE("Getting number of items in the pool - %i", total) - - //Return an answer immediately if... - // ...there's only one map to choose from. - if (total == 1) - { - DEBUG_MESSAGE("Only 1 item in pool, setting it as the winner.") - //WE HAVE A WINNER! - GetArrayString(nameArr, 0, buffer, size); - return true; - } - //Otherwise, we immediately do nothing and return, if... - // ...there are no maps to choose from. - else if (total == 0) - { - DEBUG_MESSAGE("No items in the pool. Returning false.") - return false; - } - - DEBUG_MESSAGE("Setting up array of weights.") - //Convert the adt_array of weights to a normal array. - new Float:weights[total]; - ConvertAdtArray(weightArr, weights, total); - - DEBUG_MESSAGE("Picking a random number.") - //We select a random number here by getting a random Float in the - //range [0, 1), and then multiply it by the sum of the weights, to - //make the effective range [0, totalweight). - new Float:rand = GetURandomFloat() * ArraySum(weights, total); - new Float:runningTotal = 0.0; //keeps track of total so far - - DEBUG_MESSAGE("Find the winner in the pool.") - //Determine if a map is the winner for... - // ...each map in the arrays. - for (new i = 0; i < total; i++) - { - DEBUG_MESSAGE("Update running total of weights.") - //add weight onto the total - runningTotal += weights[i]; - - DEBUG_MESSAGE("Check if we're at the right item.") - //We have found an answer if... - // ...the running total has reached the random number. - if (runningTotal > rand) - { - DEBUG_MESSAGE("Item found.") - GetArrayString(nameArr, i, buffer, size); - index = i; - return true; - } - } - - DEBUG_MESSAGE("ERROR WITH THE RANDOMIZATION ALGORITHM!") - //This shouldn't ever happen, but alas the compiler complains. - index = -1; - return false; -} - - -//Utility function to sum up an array of floats. -stock Float:ArraySum(const Float:floats[], size) -{ - new Float:result = 0.0; - for (new i = 0; i < size; i++) - result += floats[i]; - return result; -} - - -//Utility function to clear an array of Handles and close each Handle. -stock ClearHandleArray(Handle:arr) -{ - new arraySize = GetArraySize(arr); - for (new i = 0; i < arraySize; i++) - CloseHandle(GetArrayCell(arr, i)); - ClearArray(arr); -} - - -//Utility function to get the true count of active clients on the server. -stock GetRealClientCount(bool:inGameOnly=true) -{ - new clients = 0; - for (new i = 1; i <= MaxClients; i++) - { - if ((inGameOnly ? IsClientInGame(i) : IsClientConnected(i)) && !IsFakeClient(i)) - clients++; - } - return clients; -} - - -//Utiliy function to append arrays -stock ArrayAppend(Handle:arr1, Handle:arr2) -{ - new arraySize = GetArraySize(arr2); - for (new i = 0; i < arraySize; i++) - PushArrayCell(arr1, GetArrayCell(arr2, i)); -} - - -//Builds an adt_array of numbers from 0 to max-1. -stock Handle:BuildNumArray(max) -{ - new size = 2 + max / 10; - new Handle:result = CreateArray(ByteCountToCells(size)); - decl String:buffer[size]; - for (new i = 0; i < max; i++) - { - //IntToString(i, buffer, size); - FormatEx(buffer, size, "%i", i); - PushArrayString(result, buffer); - } - return result; -} - - -//Determines the correct time to paginate a menu. Menu passed to this argument should have -//pagination enabled. -stock SetCorrectMenuPagination(Handle:menu, numSlots) -{ - if (GetMenuStyleHandle(MenuStyle_Valve) != GetMenuStyleHandle(MenuStyle_Radio) && numSlots < 10) - SetMenuPagination(menu, MENU_NO_PAGINATION); -} - - -//Finds a string in an array starting at a specific index. -stock FindStringInArrayEx(Handle:arr, const String:value[], start=0) -{ - new size = GetArraySize(arr); - decl String:buffer[255]; - for (new i = start; i < size; i++) - { - GetArrayString(arr, i, buffer, sizeof(buffer)); - if (StrEqual(value, buffer)) - return i; - } - return -1; -} - - -//Closes a handle and sets the variable pointer to INVALID_HANDLE -stock CloseHandleEx(&Handle:handle) -{ - CloseHandle(handle); - handle = INVALID_HANDLE; -} - - -//Creates a copy of an adt_array. -stock Handle:CopyStringArray(Handle:arr, blocksize=1) -{ - new size = GetArraySize(arr); - new Handle:result = CreateArray(blocksize); - new len = 4 * blocksize; - decl String:buffer[len]; - for (new i = 0; i < size; i++) - { - GetArrayString(arr, i, buffer, len); - PushArrayString(result, buffer); - } - return result; -} - - -//Makes the timer to retry running a vote every second. -stock MakeRetryVoteTimer(Function:callback) -{ - CreateTimer(1.0, Handle_RetryVoteTimer, callback, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - - -//Handles the retry timer for votes that were attempted to be started. -public Action:Handle_RetryVoteTimer(Handle:timer, any:data) -{ - if (IsVoteInProgress()) - return Plugin_Continue; - - new Function:callback = Function:data; - - Call_StartFunction(INVALID_HANDLE, callback); - Call_Finish(); - - return Plugin_Stop; -} - - -//Comparison function for map tries . Used for sorting. -public CompareMapTries(index1, index2, Handle:array, Handle:hndl) -{ - decl String:map1[MAP_LENGTH], String:map2[MAP_LENGTH], - String:group1[MAP_LENGTH], String:group2[MAP_LENGTH]; - new Handle:map = INVALID_HANDLE; - map = GetArrayCell(array, index1); - GetTrieString(map, MAP_TRIE_MAP_KEY, map1, sizeof(map1)); - GetTrieString(map, MAP_TRIE_GROUP_KEY, group1, sizeof(group1)); - map = GetArrayCell(array, index2); - GetTrieString(map, MAP_TRIE_MAP_KEY, map2, sizeof(map2)); - GetTrieString(map, MAP_TRIE_GROUP_KEY, group2, sizeof(group2)); - - new result = strcmp(map1, map2); - if (result == 0) - result = strcmp(group1, group2); - return result; -} - - -//Sorts an array of map tries -stock SortMapTrieArray(Handle:array) -{ - SortADTArrayCustom(array, CompareMapTries); -} - - -//Prints the sections of a kv to the log. -stock PrintKvToConsole(Handle:kv, client, depth=0) -{ - decl String:section[64]; - KvGetSectionName(kv, section, sizeof(section)); - - new whitespace = depth*2+1; - decl String:space[whitespace]; - FillWhiteSpace(space, whitespace); - - PrintToConsole(client, "%s\"%s\"", space, section); - - if (!KvGotoFirstSubKey(kv)) - return; - - do - { - PrintKvToConsole(kv, client, depth + 1); - } - while (KvGotoNextKey(kv)); - - KvGoBack(kv); -} - - -//Prints the sections of a kv to the log. -stock LogKv(Handle:kv, depth=0) -{ - decl String:section[64]; - KvGetSectionName(kv, section, sizeof(section)); - - new whitespace = depth*2+1; - decl String:space[whitespace]; - FillWhiteSpace(space, whitespace); - - LogUMCMessage("%i: %s\"%s\"", depth+1, space, section); - - if (!KvGotoFirstSubKey(kv)) - return; - - do - { - LogKv(kv, depth + 1); - } - while (KvGotoNextKey(kv)); - - KvGoBack(kv); -} - - -//Fills a string with whitespace. -stock FillWhiteSpace(String:buffer[], maxlen) -{ - new limit = maxlen - 1; - for (new i = 0; i < limit; i++) - { - buffer[i] = ' '; - } - buffer[limit] = 0; -} - - -// -stock PrintNominationArray(Handle:array) -{ - new Handle:nom; - decl String:map[MAP_LENGTH], String:group[MAP_LENGTH]; - new size = GetArraySize(array); - for (new i = 0; i < size; i++) - { - nom = GetArrayCell(array, i); - GetTrieString(nom, MAP_TRIE_MAP_KEY, map, sizeof(map)); - GetTrieString(nom, MAP_TRIE_GROUP_KEY, group, sizeof(group)); - LogUMCMessage("%20s | %20s", map, group); - } -} - - -// -stock bool:VoteMenuToAllWithFlags(Handle:menu, time, const String:flagstring[]="") -{ - if (strlen(flagstring) > 0) - { - new flags = ReadFlagString(flagstring); - decl clients[MAXPLAYERS+1]; - new count = 0; - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && (flags & GetUserFlagBits(i))) - { - clients[count++] = i; - } - } - return VoteMenu(menu, clients, count, time); - } - else - { - return bool:VoteMenuToAll(menu, time); - } -} - - -// -stock GetClientsWithFlags(const String:flagstring[], clients[], maxlen, &amt) -{ - new bool:checkFlags = strlen(flagstring) > 0; - new flags = ReadFlagString(flagstring); - new limit = maxlen < MaxClients ? maxlen : MaxClients; - new count = 0; - for (new i = 1; i <= limit; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i) && (!checkFlags || (flags & GetUserFlagBits(i)))) - { - DEBUG_MESSAGE("Client has correct flags: %L (%i) [F: %s]", i, i, flagstring) - clients[count++] = i; - } - } - amt = count; -} - - -// -stock GetClientWithFlagsCount(const String:flagstring[]) -{ - new bool:checkFlags = strlen(flagstring) > 0; - new flags = ReadFlagString(flagstring); - new count = 0; - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i) && (!checkFlags || (flags & GetUserFlagBits(i)))) - { - count++; - } - } - return count; -} - - -// -stock bool:ClientHasAdminFlags(client, const String:flagString[]) -{ - return strlen(flagString) == 0 || (ReadFlagString(flagString) & GetUserFlagBits(client)); -} - - -//Filters a mapcycle with all invalid entries filtered out. -stock FilterMapcycleFromArrays(Handle:kv, Handle:exMaps, Handle:exGroups, numExGroups, - bool:deleteEmpty=false) -{ - new size = GetArraySize(exGroups); - new len = size < numExGroups ? size : numExGroups; - decl String:group[MAP_LENGTH]; - for (new i = 0; i < len; i++) - { - GetArrayString(exGroups, i, group, sizeof(group)); - if (!deleteEmpty) - { - KvJumpToKey(kv, group); - KvDeleteAllSubKeys(kv); - KvGoBack(kv); - } - else - { - KvDeleteSubKey(kv, group); - } - } - - if (!KvGotoFirstSubKey(kv)) - return; - - for ( ; ; ) - { - FilterMapGroupFromArrays(kv, exMaps, exGroups); - - //Delete the group if there are no valid maps in it. - if (deleteEmpty) - { - if (!KvGotoFirstSubKey(kv)) - { - DEBUG_MESSAGE("Removing empty group \"%s\".", group) - if (KvDeleteThis(kv) == -1) - { - DEBUG_MESSAGE("Mapcycle filtering completed.") - return; - } - else - continue; - } - - KvGoBack(kv); - } - if (!KvGotoNextKey(kv)) - break; - } - - KvGoBack(kv); - - DEBUG_MESSAGE("Mapcycle filtering completed.") -} - - -//Filters the kv at the level of the map group. -stock FilterMapGroupFromArrays(Handle:kv, Handle:exMaps, Handle:exGroups) -{ - decl String:group[MAP_LENGTH]; - KvGetSectionName(kv, group, sizeof(group)); - - if (!KvGotoFirstSubKey(kv)) - return; - - DEBUG_MESSAGE("Starting filtering of map group \"%s\".", group) - - decl String:mapName[MAP_LENGTH]; - for ( ; ; ) - { - KvGetSectionName(kv, mapName, sizeof(mapName)); - if (IsMapInArrays(mapName, group, exMaps, exGroups)) - { - DEBUG_MESSAGE("Removing invalid map \"%s\" from group \"%s\".", mapName, group) - if (KvDeleteThis(kv) == -1) - { - DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group) - return; - } - } - else - { - if (!KvGotoNextKey(kv)) - break; - } - } - - KvGoBack(kv); - - DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group) -} - - -// -stock bool:IsMapInArrays(const String:map[], const String:group[], Handle:exMaps, Handle:exGroups) -{ - DEBUG_MESSAGE("Starting exclusion traversal...") - new index = -1; - new bool:isExcluded = false; - decl String:exGroup[MAP_LENGTH]; - if (exMaps != INVALID_HANDLE && exGroups != INVALID_HANDLE) - { - new gSize = GetArraySize(exGroups); - do - { - index = FindStringInArrayEx(exMaps, map, index+1); - - if (index >= 0 && index < gSize) - { - DEBUG_MESSAGE("Map found at %i", index) - GetArrayString(exGroups, index, exGroup, sizeof(exGroup)); - isExcluded = StrEqual(exGroup, group, false); - DEBUG_MESSAGE("Map Excluded? %i (%s | %s)", _:isExcluded, exGroup, group) - } - } - while (!isExcluded && index != -1); - } - - return isExcluded; -} - - -// -stock bool:GroupExcludedPreviouslyPlayed(const String:group[], Handle:exGroups, numExGroups) -{ - if (numExGroups <= 0) return false; - - new i = FindStringInArray(exGroups, group); - return i != -1 && i < numExGroups; -} - - -// -stock bool:MapExcludedPreviouslyPlayed(const String:map[], const String:group[], Handle:exMaps, - Handle:exGroups, numExGroups) -{ - return GroupExcludedPreviouslyPlayed(group, exGroups, numExGroups) || - IsMapInArrays(map, group, exMaps, exGroups); -} - - -// -stock bool:KvDeleteSubKey(Handle:kv, const String:name[]) -{ - return KvJumpToKey(kv, name) && (KvDeleteThis(kv) == -1 || KvGoBack(kv)); -} - - -// -stock KvDeleteAllSubKeys(Handle:kv) -{ - if (!KvGotoFirstSubKey(kv)) - return; - - for ( ; ; ) - { - if (KvDeleteThis(kv) == -1) - return; - } -} - - -// -stock Handle:CloseAndClone(Handle:hndl, Handle:newOwner) -{ - new Handle:result = CloneHandle(hndl, newOwner); - CloseHandle(hndl); - return result; -} - - -// -stock ConvertClientsToUserIDs(const clients[], userIds[], amt) -{ - for (new i = 0; i < amt; i++) - { - userIds[i] = GetClientUserId(clients[i]); - } -} - - -// -stock ConvertUserIDsToClients(const userIds[], clients[], amt) -{ - for (new i = 0; i < amt; i++) - { - clients[i] = GetClientOfUserId(userIds[i]); - } -} - - -// -stock bool:GetTrieArray2(Handle:trie, const String:key[], any:arr[], max_size, &size=0) -{ - new bool:result = GetTrieArray(trie, key, arr, max_size, size); - if (!result) - { - result = GetTrieValue(trie, key, arr[0]); - if (result) - { - size = 1; - } - } - return result; -} - - -// -stock ArrayMin(const arr[], maxlen, &idx=0) -{ - if (maxlen <= 0) - return 0; - - new minIdx = 0; - new min = arr[0]; - new tmp; - for (new i = 1; i < maxlen; i++) - { - tmp = arr[i]; - if (tmp < min) - { - min = tmp; - minIdx = i; - } - } - return min; -} - - -// -stock ArrayMax(const arr[], maxlen, &idx=0) -{ - if (maxlen <= 0) - return 0; - - new maxIdx = 0; - new max = arr[0]; - new tmp; - for (new i = 1; i < maxlen; i++) - { - tmp = arr[i]; - if (tmp > max) - { - max = tmp; - maxIdx = i; - } - } - return max; +#if defined _umc_utils_included + #endinput +#endif +#define _umc_utils_included + +#pragma semicolon 1 + +#include + +#include + +#include +#include +#include +#include + +//************************************************************************************************// +// GENERAL UTILITIES // +//************************************************************************************************// + +#if UMC_DEBUG +//Prints a debug message. +stock DebugMessage(const String:message[], any:...) +{ + new size = strlen(message) + 255; + decl String:fMessage[size]; + VFormat(fMessage, size, message, 2); + + LogUMCMessage("DEBUG: %s", fMessage); +} +#define DEBUG_MESSAGE(%0) DebugMessage(%0); + +#else + +#define DEBUG_MESSAGE(%0) + +#endif + + +#define MIN(%0, %1) ((%0) < (%1)) ? (%0) : (%1) + + +stock LogUMCMessage(const String:message[], any:...) +{ + new size = strlen(message) + 255; + decl String:fMessage[size]; + VFormat(fMessage, size, message, 2); + + decl String:fileName[PLATFORM_MAX_PATH]; + decl String:timeStamp[64]; + FormatTime(timeStamp, sizeof(timeStamp), "%Y%m%d", GetTime()); + BuildPath(Path_SM, fileName, sizeof(fileName), "logs/UMC_%s.log", timeStamp); + + LogToFile(fileName, fMessage); +} + + +stock StringToUpper(String:str[]) +{ + new i = 0; + while (str[i] != 0) + { + str[i] = CharToUpper(str[i]); + } +} + + +//Utility function to build a map trie. +stock Handle:CreateMapTrie(const String:map[], const String:group[]) +{ + new Handle:trie = CreateTrie(); + SetTrieString(trie, MAP_TRIE_MAP_KEY, map); + SetTrieString(trie, MAP_TRIE_GROUP_KEY, group); + return trie; +} + + +//Utility function to get a KeyValues Handle from a filename, with the specified root key. +stock Handle:GetKvFromFile(const String:filename[], const String:rootKey[], bool:checkNorm=true) +{ + new Handle:kv = CreateKeyValues(rootKey); + + //Log an error and return empty handle if... + // ...the kv file fails to parse. + if (!(checkNorm && ConvertNormalMapcycle(kv, filename)) && !FileToKeyValues(kv, filename)) + { + LogError("KV ERROR: Unable to load KV file: %s", filename); + CloseHandle(kv); + return INVALID_HANDLE; + } + +#if UMC_DEBUG + LogKv(kv); +#endif + + return kv; +} + + +// +stock bool:ConvertNormalMapcycle(Handle:kv, const String:filename[]) +{ + DEBUG_MESSAGE("Opening Mapcycle File %s", filename) + new Handle:file = OpenFile(filename, "r"); + + if (file == INVALID_HANDLE) + return false; + + DEBUG_MESSAGE("Fetching first line.") + new String:firstLine[256]; + new bool:foundDef; + while (ReadFileLine(file, firstLine, sizeof(firstLine))) + { + TrimString(firstLine); + if (strlen(firstLine) > 0) + { + foundDef = true; + break; + } + } + + if (!foundDef) + { + DEBUG_MESSAGE("Couldn't find first line") + CloseHandle(file); + return false; + } + + DEBUG_MESSAGE("Checking first line for UMC header: \"%s\"", firstLine) + + static Handle:re = INVALID_HANDLE; + if (re == INVALID_HANDLE) + re = CompileRegex("^//!UMC\\s+([0-9]+)\\s*$"); + + decl String:buffer[5]; + + if (MatchRegex(re, firstLine) > 1) + GetRegexSubString(re, 1, buffer, sizeof(buffer)); + else + { + DEBUG_MESSAGE("Header was not found. Aborting.") + CloseHandle(file); + return false; + } + + DEBUG_MESSAGE("Making a map group.") + + static Handle:re2 = INVALID_HANDLE; + if (re2 == INVALID_HANDLE) + re2 = CompileRegex("^\\s*([^/\\\\:*?'\"<>|\\s]+)\\s*(?://.*)?$"); + + KvJumpToKey(kv, "Mapcycle", true); + KvSetNum(kv, "maps_invote", StringToInt(buffer)); + + DEBUG_MESSAGE("Parsing maps.") + + decl String:map[MAP_LENGTH]; + decl String:line[256]; + while (ReadFileLine(file, line, sizeof(line))) + { + TrimString(line); + if (MatchRegex(re2, line) > 1) + { + GetRegexSubString(re2, 1, map, sizeof(map)); + DEBUG_MESSAGE("Adding map: %s", map) + KvJumpToKey(kv, map, true); + KvGoBack(kv); + } + } + + CloseHandle(file); + + KvGoBack(kv); + + return true; +} + + +//Utility function to jump to a specific map in a mapcycle. +// kv: Mapcycle Keyvalues that must be at the root value. +stock bool:KvJumpToMap(Handle:kv, const String:map[]) +{ + if (!KvGotoFirstSubKey(kv)) + return false; + + decl String:mapName[MAP_LENGTH]; + + do + { + if (!KvGotoFirstSubKey(kv)) + continue; + do + { + KvGetSectionName(kv, mapName, sizeof(mapName)); + if (StrEqual(mapName, map)) + return true; + } while (KvGotoNextKey(kv)); + + KvGoBack(kv); + } while (KvGotoNextKey(kv)); + + KvGoBack(kv); + return false; +} + + +//Utility function to search for a group that contains the given map. +// kv: Mapcycle +// map: Map whose group we're looking for. +// buffer: Buffer to store the found group name. +// maxlen: Maximum length of the buffer. +stock bool:KvFindGroupOfMap(Handle:kv, const String:map[], String:buffer[], maxlen) +{ + if (!KvGotoFirstSubKey(kv)) + return false; + + decl String:mapName[MAP_LENGTH], String:groupName[MAP_LENGTH]; + do + { + KvGetSectionName(kv, groupName, sizeof(groupName)); + + if (!KvGotoFirstSubKey(kv)) + continue; + + do + { + KvGetSectionName(kv, mapName, sizeof(mapName)); + if (StrEqual(mapName, map, false)) + { + KvGoBack(kv); + KvGoBack(kv); + strcopy(buffer, maxlen, groupName); + return true; + } + } + while (KvGotoNextKey(kv)); + + KvGoBack(kv); + } + while (KvGotoNextKey(kv)); + + KvGoBack(kv); + + return false; +} + + +enum CustomHudFallbackType { + HudFallback_Chat, + HudFallback_Hint, + HudFallback_Center, + HudFallback_None +} + +//Color Arrays for colors in warning messages. +static g_iSColors[7] = {1, 3, 3, 4, 4, 5, 6}; +static String:g_sSColors[7][13] = {"{DEFAULT}", "{LIGHTGREEN}", "{TEAM}", "{GREEN}", "{RED}", + "{DARKGREEN}", "{YELLOW}"}; +static g_iTColors[13][3] = {{255, 255, 255}, {255, 0, 0}, { 0, 255, 0}, + { 0, 0, 255}, {255, 255, 0}, {255, 0, 255}, + { 0, 255, 255}, {255, 128, 0}, {255, 0, 128}, + {128, 255, 0}, { 0, 255, 128}, {128, 0, 255}, + { 0, 128, 255}}; +static String:g_sTColors[13][12] = {"{WHITE}", "{RED}", "{GREEN}", "{BLUE}", "{YELLOW}", "{PURPLE}", + "{CYAN}", "{ORANGE}", "{PINK}", "{OLIVE}", "{LIME}", "{VIOLET}", + "{LIGHTBLUE}"}; + +//Handle to the Center Message timer. For Vote Warnings. +static Handle:center_message_timer = INVALID_HANDLE; +static bool:center_warning_active = false; + +//Handle to the TF2 Game Message entity timer. For Vote Warnings. +//static Handle:game_message_timer = INVALID_HANDLE; +//static bool:game_message_active = false; + +//Displays a message to the server +stock DisplayServerMessage(const String:msg[], const String:type[]) +{ + //Kill timers for previous messages. + //if (game_message_active) + //{ + // TriggerTimer(game_message_timer); + //} + if (center_warning_active) + { + center_warning_active = false; + TriggerTimer(center_message_timer); + } + + if (strlen(msg) == 0) + return; + + decl String:message[255]; + strcopy(message, sizeof(message), msg); + + //Display a chat message ("S") if... + // ...the user specifies. + if (StrContains(type, "S") != -1) + { + new String:sColor[4]; + + Format(message, sizeof(message), "%c%s", 1, message); + + for (new c = 0; c < sizeof(g_iSColors); c++) + { + if (StrContains(message, g_sSColors[c])) + { + FormatEx(sColor, sizeof(sColor), "%c", g_iSColors[c]); + ReplaceString(message, sizeof(message), g_sSColors[c], sColor); + } + } + + PrintToChatAll(message); + } + + //Buffer to hold message in order to manipulate it. + decl String:sTextTmp[255]; + + //Display a top message ("T") if... + // ...the user specifies. + if (StrContains(type, "T") != -1) + { + strcopy(sTextTmp, sizeof(sTextTmp), message); + decl String:sColor[16]; + new iColor = -1, iPos = BreakString(sTextTmp, sColor, sizeof(sColor)); + + for (new i = 0; i < sizeof(g_sTColors); i++) + { + if (StrEqual(sColor, g_sTColors[i])) + iColor = i; + } + + if (iColor == -1) + { + iPos = 0; + iColor = 0; + } + + new Handle:hKv = CreateKeyValues("Stuff", "title", sTextTmp[iPos]); + KvSetColor(hKv, "color", g_iTColors[iColor][0], g_iTColors[iColor][1], + g_iTColors[iColor][2], 255); + KvSetNum(hKv, "level", 1); + KvSetNum(hKv, "time", 10); + + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i)) + CreateDialog(i, hKv, DialogType_Msg); + } + + CloseHandle(hKv); + } + + // Remove colors, because C,H,M methods do not support colors. + + //Remove a color from the message string for... + // ...each color in the Say color array. + for (new c = 0; c < sizeof(g_iSColors); c++) + { + if (StrContains(message, g_sSColors[c]) != -1) + ReplaceString(message, sizeof(message), g_sSColors[c], ""); + } + + //Remove a color from the message string for... + // ...each color in the Top color array. + for (new c = 0; c < sizeof(g_iTColors); c++) + { + if (StrContains(message, g_sTColors[c]) != -1) + ReplaceString(message, sizeof(message), g_sTColors[c], ""); + } + + //Display a center message ("C") if... + // ...the user specifies. + if (StrContains(type, "C") != -1) + { + PrintCenterTextAll(message); + + //Setup timer to keep the center message visible. + new Handle:hCenterAd; + center_message_timer = CreateDataTimer(1.0, Timer_CenterAd, hCenterAd, TIMER_REPEAT); + WritePackString(hCenterAd, message); + + center_warning_active = center_message_timer != INVALID_HANDLE; + if (!center_warning_active) + CloseHandle(hCenterAd); + } + + //Display a hint message ("H") if... + // ...the user specifies. + if (StrContains(type, "H") != -1) + PrintHintTextToAll(message); + + //Display a TF2 Game Event message ("G") if... + // ...the user specifies. + // if (StrContains(type, "G") != -1) + // TF2_SendCustomHudMessageAll("ico_time_10", -1, HudFallback_Hint, message); +} + + +//Called with each tick of the timer for center messages. Used to keep the message visible for an +//extended period. +public Action:Timer_CenterAd(Handle:timer, Handle:pack) +{ + decl String:sText[256]; + static iCount = 0; + + ResetPack(pack); + ReadPackString(pack, sText, sizeof(sText)); + + if (center_warning_active && ++iCount < 5) + { + PrintCenterTextAll(sText); + return Plugin_Continue; + } + else + { + iCount = 0; + center_message_timer = INVALID_HANDLE; + center_warning_active = false; + return Plugin_Stop; + } +} + + +// +// stock TF2_SendCustomHudMessage(client, const String:icon[], team=-1, CustomHudFallbackType:fallback, +// const String:format[], any:...) +// { +// decl String:message[256]; +// VFormat(message, sizeof(message), format, 6); + +// new Handle:pack = CreateDataPack(); +// WritePackCell(pack, GetClientUserId(client)); +// WritePackString(pack, message); +// WritePackString(pack, icon); +// WritePackCell(pack, team); +// WritePackCell(pack, fallback); +// QueryClientConVar(client, "cl_hud_minmode", MinModeCheckFinished, pack); +// } + + +// // +// stock TF2_SendCustomHudMessageAll(const String:icon[], team=-1, CustomHudFallbackType:fallback, +// const String:format[], any:...) +// { +// decl String:message[256]; +// VFormat(message, sizeof(message), format, 5); + +// for (new i = 1; i <= MaxClients; i++) +// { +// if (!IsClientInGame(i) || IsFakeClient(i)) +// continue; + +// new Handle:pack = CreateDataPack(); +// WritePackCell(pack, GetClientUserId(i)); +// WritePackString(pack, message); +// WritePackString(pack, icon); +// WritePackCell(pack, team); +// WritePackCell(pack, _:fallback); +// QueryClientConVar(i, "cl_hud_minmode", MinModeCheckFinished, pack); +// } +// } + + +// // +// public MinModeCheckFinished(QueryCookie:cookie, uid, ConVarQueryResult:result, const String:cvarName[], +// const String:cvarValue[], any:pack) +// { +// ResetPack(pack); +// new client = GetClientOfUserId(ReadPackCell(pack)); +// if (client == 0) +// { +// CloseHandle(pack); +// return; +// } + +// decl String:message[256], String:icon[256]; +// ReadPackString(pack, message, sizeof(message)); +// ReadPackString(pack, icon, sizeof(icon)); + +// new team = ReadPackCell(pack); + +// if (result != ConVarQuery_Okay || StringToInt(cvarValue) == 0) +// { +// switch (CustomHudFallbackType:ReadPackCell(pack)) +// { +// case HudFallback_Chat: +// PrintToChat(client, "%s", message); +// case HudFallback_Hint: +// PrintHintText(client, "%s", message); +// case HudFallback_Center: +// PrintCenterText(client, "%s", message); +// } +// } +// else +// { +// new Handle:msg = StartMessageOne("HudNotifyCustom", client); +// BfWriteString(msg, message); +// BfWriteString(msg, icon); +// BfWriteByte(msg, ((team == -1) ? GetClientTeam(client) : team)); +// EndMessage(); +// } + +// CloseHandle(pack); +// } + + +//Sets all elements of an array of booleans to false. +stock ResetArray(bool:arr[], size) +{ + for (new i = 0; i < size; i++) + arr[i] = false; +} + + +//Utility function to cache a sound. +stock CacheSound(const String:sound[]) +{ + //Handle the sound if... + // ...it is defined. + if (strlen(sound) > 0) + { + //Filepath buffer + decl String:filePath[PLATFORM_MAX_PATH]; + + //Format sound to the correct directory. + FormatEx(filePath, sizeof(filePath), "sound/%s", sound); + + //Log an error and don't cache the sound if... + // ...the sound file does not exist + if (!FileExists(filePath)) + LogError("SOUND ERROR: Sound file '%s' does not exist.", filePath); + //Otherwise, cache the sound. + else + { + //Make sure clients download the sound if they don't have it. + AddFileToDownloadsTable(filePath); + + //Cache it. + PrecacheSound(sound, true); + + //Log an error if... + // ...the sound failed to be cached. + if (!IsSoundPrecached(filePath)) + LogError("SOUND ERROR: Failed to precache sound file '%s'", sound); + } + } +} + + +//Fetch the next index of the menu. +// size: the size of the menu +// scramble: whether or not a random index should be picked. +stock GetNextMenuIndex(size, bool:scramble) +{ + return scramble ? GetRandomInt(0, size) : size; +} + + +//Inserts given string into given array at given index. +stock InsertArrayString(Handle:arr, index, const String:value[]) +{ + if (GetArraySize(arr) > index) + { + ShiftArrayUp(arr, index); + SetArrayString(arr, index, value); + } + else + PushArrayString(arr, value); +} + + +//Inserts given cell into given adt_array at given index, +stock InsertArrayCell(Handle:arr, index, any:cell) +{ + if (GetArraySize(arr) > index) + { + ShiftArrayUp(arr, index); + SetArrayCell(arr, index, cell); + } + else + PushArrayCell(arr, cell); +} + + +//Deletes values off the end of an array until it is down to the given size. +stock TrimArray(Handle:arr, size) +{ + //Remove elements from the start of an array while... + // ...the size of the array is greater than the required size. + new asize = GetArraySize(arr); + while (asize > size) + RemoveFromArray(arr, --asize); +} + + +//Adds the given map to the given memory array. +// mapName: the name of the map +// arr: the memory array we're adding to +// size: the maximum size of the memory array +stock AddToMemoryArray(const String:mapName[], Handle:arr, size) +{ + //Add the new map to the beginning of the array. + InsertArrayString(arr, 0, mapName); + + //Trim the array down to size. + TrimArray(arr, size); +} + + +//Adds entire array to the given menu. +stock AddArrayToMenu(Handle:menu, Handle:arr, Handle:dispArr=INVALID_HANDLE) +{ + decl String:map[MAP_LENGTH], String:disp[MAP_LENGTH]; + new arrSize = GetArraySize(arr); + new dispSize = (dispArr != INVALID_HANDLE) ? GetArraySize(dispArr) : 0; + for (new i = 0; i < arrSize; i++) + { + GetArrayString(arr, i, map, sizeof(map)); + if (i >= dispSize) + disp = map; + else + GetArrayString(dispArr, i, disp, sizeof(disp)); + AddMenuItem(menu, map, disp); + } +} + + +//Changes the map in 5 seconds. +stock ForceChangeInFive(const String:map[], const String:reason[]="") +{ + //Notify the server. + PrintToChatAll("\x03[UMC]\x01 %t", "Map Change in 5"); + + //Setup the change. + ForceChangeMap(map, 5.0, reason); +} + + +//Changes the map after the specified time period. +stock ForceChangeMap(const String:map[], Float:time, const String:reason[]="") +{ + LogUMCMessage("%s: Changing map to '%s' in %.f seconds.", reason, map, time); + + //Setup the timer. + new Handle:pack; + CreateDataTimer( + time, + Handle_MapChangeTimer, + pack, + TIMER_FLAG_NO_MAPCHANGE + ); + WritePackString(pack, map); + WritePackString(pack, reason); +} + + +//Called after the mapchange timer is completed. +public Action:Handle_MapChangeTimer(Handle:timer, Handle:pack) +{ + //Get map from the timer's pack. + decl String:map[MAP_LENGTH], String:reason[255]; + ResetPack(pack); + ReadPackString(pack, map, sizeof(map)); + ReadPackString(pack, reason, sizeof(reason)); + + DEBUG_MESSAGE("Changing map to %s: %s", map, reason) + + //Change the map. + ForceChangeLevel(map, reason); +} + + +//Determines if the current server time is between the given min and max. +stock bool:IsTimeBetween(min, max) +{ + //Get the current server time. + decl String:time[5]; + FormatTime(time, sizeof(time), "%H%M"); + new theTime = StringToInt(time); + + //Handle wrap-around case if... + // ...max time is less than min time. + if (max <= min) + { + max += 2400; + if (theTime <= min) + { + theTime += 2400; + } + } + return min <= theTime && theTime <= max; +} + + +//Determines if the current server player count is between the given min and max. +stock bool:IsPlayerCountBetween(min, max) +{ + //Get the current number of players. + new numplayers = GetRealClientCount(); + return min <= numplayers && numplayers <= max; +} + + +//Converts an adt_array to a standard array. +stock ConvertAdtArray(Handle:arr, any:newArr[], size) +{ + new arraySize = GetArraySize(arr); + new min = size < arraySize ? size : arraySize; + for (new i = 0; i < min; i++) + newArr[i] = GetArrayCell(arr, i); +} + + +// +stock ConvertArray(const any:arr[], amt, Handle:newArr) +{ + for (new i = 0; i < amt; i++) + PushArrayCell(newArr, arr[i]); +} + + +//Selects one random name from the given name array, using the weights in the supplies weight array. +//Stores the result in buffer. +stock bool:GetWeightedRandomSubKey(String:buffer[], size, Handle:weightArr, Handle:nameArr, &index=0) +{ + //Calc total number of maps we're choosing. + new total = GetArraySize(weightArr); + + DEBUG_MESSAGE("Getting number of items in the pool - %i", total) + + //Return an answer immediately if... + // ...there's only one map to choose from. + if (total == 1) + { + DEBUG_MESSAGE("Only 1 item in pool, setting it as the winner.") + //WE HAVE A WINNER! + GetArrayString(nameArr, 0, buffer, size); + return true; + } + //Otherwise, we immediately do nothing and return, if... + // ...there are no maps to choose from. + else if (total == 0) + { + DEBUG_MESSAGE("No items in the pool. Returning false.") + return false; + } + + DEBUG_MESSAGE("Setting up array of weights.") + //Convert the adt_array of weights to a normal array. + new Float:weights[total]; + ConvertAdtArray(weightArr, weights, total); + + DEBUG_MESSAGE("Picking a random number.") + //We select a random number here by getting a random Float in the + //range [0, 1), and then multiply it by the sum of the weights, to + //make the effective range [0, totalweight). + new Float:rand = GetURandomFloat() * ArraySum(weights, total); + new Float:runningTotal = 0.0; //keeps track of total so far + + DEBUG_MESSAGE("Find the winner in the pool.") + //Determine if a map is the winner for... + // ...each map in the arrays. + for (new i = 0; i < total; i++) + { + DEBUG_MESSAGE("Update running total of weights.") + //add weight onto the total + runningTotal += weights[i]; + + DEBUG_MESSAGE("Check if we're at the right item.") + //We have found an answer if... + // ...the running total has reached the random number. + if (runningTotal > rand) + { + DEBUG_MESSAGE("Item found.") + GetArrayString(nameArr, i, buffer, size); + index = i; + return true; + } + } + + DEBUG_MESSAGE("ERROR WITH THE RANDOMIZATION ALGORITHM!") + //This shouldn't ever happen, but alas the compiler complains. + index = -1; + return false; +} + + +//Utility function to sum up an array of floats. +stock Float:ArraySum(const Float:floats[], size) +{ + new Float:result = 0.0; + for (new i = 0; i < size; i++) + result += floats[i]; + return result; +} + + +//Utility function to clear an array of Handles and close each Handle. +stock ClearHandleArray(Handle:arr) +{ + new arraySize = GetArraySize(arr); + for (new i = 0; i < arraySize; i++) + CloseHandle(GetArrayCell(arr, i)); + ClearArray(arr); +} + + +//Utility function to get the true count of active clients on the server. +stock GetRealClientCount(bool:inGameOnly=true) +{ + new clients = 0; + for (new i = 1; i <= MaxClients; i++) + { + if ((inGameOnly ? IsClientInGame(i) : IsClientConnected(i)) && !IsFakeClient(i)) + clients++; + } + return clients; +} + + +//Utiliy function to append arrays +stock ArrayAppend(Handle:arr1, Handle:arr2) +{ + new arraySize = GetArraySize(arr2); + for (new i = 0; i < arraySize; i++) + PushArrayCell(arr1, GetArrayCell(arr2, i)); +} + + +//Builds an adt_array of numbers from 0 to max-1. +stock Handle:BuildNumArray(max) +{ + new size = 2 + max / 10; + new Handle:result = CreateArray(ByteCountToCells(size)); + decl String:buffer[size]; + for (new i = 0; i < max; i++) + { + //IntToString(i, buffer, size); + FormatEx(buffer, size, "%i", i); + PushArrayString(result, buffer); + } + return result; +} + + +//Determines the correct time to paginate a menu. Menu passed to this argument should have +//pagination enabled. +stock SetCorrectMenuPagination(Handle:menu, numSlots) +{ + if (GetMenuStyleHandle(MenuStyle_Valve) != GetMenuStyleHandle(MenuStyle_Radio) && numSlots < 10) + SetMenuPagination(menu, MENU_NO_PAGINATION); +} + + +//Finds a string in an array starting at a specific index. +stock FindStringInArrayEx(Handle:arr, const String:value[], start=0) +{ + new size = GetArraySize(arr); + decl String:buffer[255]; + for (new i = start; i < size; i++) + { + GetArrayString(arr, i, buffer, sizeof(buffer)); + if (StrEqual(value, buffer)) + return i; + } + return -1; +} + + +//Closes a handle and sets the variable pointer to INVALID_HANDLE +stock CloseHandleEx(&Handle:handle) +{ + CloseHandle(handle); + handle = INVALID_HANDLE; +} + + +//Creates a copy of an adt_array. +stock Handle:CopyStringArray(Handle:arr, blocksize=1) +{ + new size = GetArraySize(arr); + new Handle:result = CreateArray(blocksize); + new len = 4 * blocksize; + decl String:buffer[len]; + for (new i = 0; i < size; i++) + { + GetArrayString(arr, i, buffer, len); + PushArrayString(result, buffer); + } + return result; +} + + +//Makes the timer to retry running a vote every second. +stock MakeRetryVoteTimer(Function:callback) +{ + CreateTimer(1.0, Handle_RetryVoteTimer, callback, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + + +//Handles the retry timer for votes that were attempted to be started. +public Action:Handle_RetryVoteTimer(Handle:timer, any:data) +{ + if (IsVoteInProgress()) + return Plugin_Continue; + + new Function:callback = Function:data; + + Call_StartFunction(INVALID_HANDLE, callback); + Call_Finish(); + + return Plugin_Stop; +} + + +//Comparison function for map tries . Used for sorting. +public CompareMapTries(index1, index2, Handle:array, Handle:hndl) +{ + decl String:map1[MAP_LENGTH], String:map2[MAP_LENGTH], + String:group1[MAP_LENGTH], String:group2[MAP_LENGTH]; + new Handle:map = INVALID_HANDLE; + map = GetArrayCell(array, index1); + GetTrieString(map, MAP_TRIE_MAP_KEY, map1, sizeof(map1)); + GetTrieString(map, MAP_TRIE_GROUP_KEY, group1, sizeof(group1)); + map = GetArrayCell(array, index2); + GetTrieString(map, MAP_TRIE_MAP_KEY, map2, sizeof(map2)); + GetTrieString(map, MAP_TRIE_GROUP_KEY, group2, sizeof(group2)); + + new result = strcmp(map1, map2); + if (result == 0) + result = strcmp(group1, group2); + return result; +} + + +//Sorts an array of map tries +stock SortMapTrieArray(Handle:array) +{ + SortADTArrayCustom(array, CompareMapTries); +} + + +//Prints the sections of a kv to the log. +stock PrintKvToConsole(Handle:kv, client, depth=0) +{ + decl String:section[64]; + KvGetSectionName(kv, section, sizeof(section)); + + new whitespace = depth*2+1; + decl String:space[whitespace]; + FillWhiteSpace(space, whitespace); + + PrintToConsole(client, "%s\"%s\"", space, section); + + if (!KvGotoFirstSubKey(kv)) + return; + + do + { + PrintKvToConsole(kv, client, depth + 1); + } + while (KvGotoNextKey(kv)); + + KvGoBack(kv); +} + + +//Prints the sections of a kv to the log. +stock LogKv(Handle:kv, depth=0) +{ + decl String:section[64]; + KvGetSectionName(kv, section, sizeof(section)); + + new whitespace = depth*2+1; + decl String:space[whitespace]; + FillWhiteSpace(space, whitespace); + + LogUMCMessage("%i: %s\"%s\"", depth+1, space, section); + + if (!KvGotoFirstSubKey(kv)) + return; + + do + { + LogKv(kv, depth + 1); + } + while (KvGotoNextKey(kv)); + + KvGoBack(kv); +} + + +//Fills a string with whitespace. +stock FillWhiteSpace(String:buffer[], maxlen) +{ + new limit = maxlen - 1; + for (new i = 0; i < limit; i++) + { + buffer[i] = ' '; + } + buffer[limit] = 0; +} + + +// +stock PrintNominationArray(Handle:array) +{ + new Handle:nom; + decl String:map[MAP_LENGTH], String:group[MAP_LENGTH]; + new size = GetArraySize(array); + for (new i = 0; i < size; i++) + { + nom = GetArrayCell(array, i); + GetTrieString(nom, MAP_TRIE_MAP_KEY, map, sizeof(map)); + GetTrieString(nom, MAP_TRIE_GROUP_KEY, group, sizeof(group)); + LogUMCMessage("%20s | %20s", map, group); + } +} + + +// +stock bool:VoteMenuToAllWithFlags(Handle:menu, time, const String:flagstring[]="") +{ + if (strlen(flagstring) > 0) + { + new flags = ReadFlagString(flagstring); + decl clients[MAXPLAYERS+1]; + new count = 0; + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && (flags & GetUserFlagBits(i))) + { + clients[count++] = i; + } + } + return VoteMenu(menu, clients, count, time); + } + else + { + return bool:VoteMenuToAll(menu, time); + } +} + + +// +stock GetClientsWithFlags(const String:flagstring[], clients[], maxlen, &amt) +{ + new bool:checkFlags = strlen(flagstring) > 0; + new flags = ReadFlagString(flagstring); + new limit = maxlen < MaxClients ? maxlen : MaxClients; + new count = 0; + for (new i = 1; i <= limit; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i) && (!checkFlags || (flags & GetUserFlagBits(i)))) + { + DEBUG_MESSAGE("Client has correct flags: %L (%i) [F: %s]", i, i, flagstring) + clients[count++] = i; + } + } + amt = count; +} + + +// +stock GetClientWithFlagsCount(const String:flagstring[]) +{ + new bool:checkFlags = strlen(flagstring) > 0; + new flags = ReadFlagString(flagstring); + new count = 0; + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i) && (!checkFlags || (flags & GetUserFlagBits(i)))) + { + count++; + } + } + return count; +} + + +// +stock bool:ClientHasAdminFlags(client, const String:flagString[]) +{ + return strlen(flagString) == 0 || (ReadFlagString(flagString) & GetUserFlagBits(client)); +} + + +//Filters a mapcycle with all invalid entries filtered out. +stock FilterMapcycleFromArrays(Handle:kv, Handle:exMaps, Handle:exGroups, numExGroups, + bool:deleteEmpty=false) +{ + new size = GetArraySize(exGroups); + new len = size < numExGroups ? size : numExGroups; + decl String:group[MAP_LENGTH]; + for (new i = 0; i < len; i++) + { + GetArrayString(exGroups, i, group, sizeof(group)); + if (!deleteEmpty) + { + KvJumpToKey(kv, group); + KvDeleteAllSubKeys(kv); + KvGoBack(kv); + } + else + { + KvDeleteSubKey(kv, group); + } + } + + if (!KvGotoFirstSubKey(kv)) + return; + + for ( ; ; ) + { + FilterMapGroupFromArrays(kv, exMaps, exGroups); + + //Delete the group if there are no valid maps in it. + if (deleteEmpty) + { + if (!KvGotoFirstSubKey(kv)) + { + DEBUG_MESSAGE("Removing empty group \"%s\".", group) + if (KvDeleteThis(kv) == -1) + { + DEBUG_MESSAGE("Mapcycle filtering completed.") + return; + } + else + continue; + } + + KvGoBack(kv); + } + if (!KvGotoNextKey(kv)) + break; + } + + KvGoBack(kv); + + DEBUG_MESSAGE("Mapcycle filtering completed.") +} + + +//Filters the kv at the level of the map group. +stock FilterMapGroupFromArrays(Handle:kv, Handle:exMaps, Handle:exGroups) +{ + decl String:group[MAP_LENGTH]; + KvGetSectionName(kv, group, sizeof(group)); + + if (!KvGotoFirstSubKey(kv)) + return; + + DEBUG_MESSAGE("Starting filtering of map group \"%s\".", group) + + decl String:mapName[MAP_LENGTH]; + for ( ; ; ) + { + KvGetSectionName(kv, mapName, sizeof(mapName)); + if (IsMapInArrays(mapName, group, exMaps, exGroups)) + { + DEBUG_MESSAGE("Removing invalid map \"%s\" from group \"%s\".", mapName, group) + if (KvDeleteThis(kv) == -1) + { + DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group) + return; + } + } + else + { + if (!KvGotoNextKey(kv)) + break; + } + } + + KvGoBack(kv); + + DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group) +} + + +// +stock bool:IsMapInArrays(const String:map[], const String:group[], Handle:exMaps, Handle:exGroups) +{ + DEBUG_MESSAGE("Starting exclusion traversal...") + new index = -1; + new bool:isExcluded = false; + decl String:exGroup[MAP_LENGTH]; + if (exMaps != INVALID_HANDLE && exGroups != INVALID_HANDLE) + { + new gSize = GetArraySize(exGroups); + do + { + index = FindStringInArrayEx(exMaps, map, index+1); + + if (index >= 0 && index < gSize) + { + DEBUG_MESSAGE("Map found at %i", index) + GetArrayString(exGroups, index, exGroup, sizeof(exGroup)); + isExcluded = StrEqual(exGroup, group, false); + DEBUG_MESSAGE("Map Excluded? %i (%s | %s)", _:isExcluded, exGroup, group) + } + } + while (!isExcluded && index != -1); + } + + return isExcluded; +} + + +// +stock bool:GroupExcludedPreviouslyPlayed(const String:group[], Handle:exGroups, numExGroups) +{ + if (numExGroups <= 0) return false; + + new i = FindStringInArray(exGroups, group); + return i != -1 && i < numExGroups; +} + + +// +stock bool:MapExcludedPreviouslyPlayed(const String:map[], const String:group[], Handle:exMaps, + Handle:exGroups, numExGroups) +{ + return GroupExcludedPreviouslyPlayed(group, exGroups, numExGroups) || + IsMapInArrays(map, group, exMaps, exGroups); +} + + +// +stock bool:KvDeleteSubKey(Handle:kv, const String:name[]) +{ + return KvJumpToKey(kv, name) && (KvDeleteThis(kv) == -1 || KvGoBack(kv)); +} + + +// +stock KvDeleteAllSubKeys(Handle:kv) +{ + if (!KvGotoFirstSubKey(kv)) + return; + + for ( ; ; ) + { + if (KvDeleteThis(kv) == -1) + return; + } +} + + +// +stock Handle:CloseAndClone(Handle:hndl, Handle:newOwner) +{ + new Handle:result = CloneHandle(hndl, newOwner); + CloseHandle(hndl); + return result; +} + + +// +stock ConvertClientsToUserIDs(const clients[], userIds[], amt) +{ + for (new i = 0; i < amt; i++) + { + userIds[i] = GetClientUserId(clients[i]); + } +} + + +// +stock ConvertUserIDsToClients(const userIds[], clients[], amt) +{ + for (new i = 0; i < amt; i++) + { + clients[i] = GetClientOfUserId(userIds[i]); + } +} + + +// +stock bool:GetTrieArray2(Handle:trie, const String:key[], any:arr[], max_size, &size=0) +{ + new bool:result = GetTrieArray(trie, key, arr, max_size, size); + if (!result) + { + result = GetTrieValue(trie, key, arr[0]); + if (result) + { + size = 1; + } + } + return result; +} + + +// +stock ArrayMin(const arr[], maxlen, &idx=0) +{ + if (maxlen <= 0) + return 0; + + new minIdx = 0; + new min = arr[0]; + new tmp; + for (new i = 1; i < maxlen; i++) + { + tmp = arr[i]; + if (tmp < min) + { + min = tmp; + minIdx = i; + } + } + return min; +} + + +// +stock ArrayMax(const arr[], maxlen, &idx=0) +{ + if (maxlen <= 0) + return 0; + + new maxIdx = 0; + new max = arr[0]; + new tmp; + for (new i = 1; i < maxlen; i++) + { + tmp = arr[i]; + if (tmp > max) + { + max = tmp; + maxIdx = i; + } + } + return max; } \ No newline at end of file From 1a1d70bbd357600aa610b4bf02e14321a975c44b Mon Sep 17 00:00:00 2001 From: Stephen Elliott Date: Mon, 28 Jan 2013 15:11:25 -0500 Subject: [PATCH 2/4] intermission uses protobuffs when applicable --- addons/sourcemod/scripting/umc-randomcycle.sp | 881 +++++++++--------- 1 file changed, 446 insertions(+), 435 deletions(-) diff --git a/addons/sourcemod/scripting/umc-randomcycle.sp b/addons/sourcemod/scripting/umc-randomcycle.sp index 00f5352..07b2e04 100644 --- a/addons/sourcemod/scripting/umc-randomcycle.sp +++ b/addons/sourcemod/scripting/umc-randomcycle.sp @@ -1,435 +1,446 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Ultimate Mapchooser - Random Cycle * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#pragma semicolon 1 - -#include -#include -#include - -#undef REQUIRE_PLUGIN - -//Auto update -#include -#if AUTOUPDATE_DEV - #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/dev/updateinfo-umc-randomcycle.txt" -#else - #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/updateinfo-umc-randomcycle.txt" -#endif - -#define NEXT_MAPGROUP_KEY "next_mapgroup" - -//Plugin Information -public Plugin:myinfo = -{ - name = "[UMC] Random Cycle", - author = "Steell", - description = "Extends Ultimate Mapchooser to provide random selecting of the next map.", - version = PL_VERSION, - url = "http://forums.alliedmods.net/showthread.php?t=134190" -}; - -//Changelog: -/* -3.3.2 (3/4/2012) -Added ability for Random Mapcycle to select the next map at the start of the game. --New cvar "sm_umc_randcycle_start" to control this ability -Updated UMC Logging functionality -Added ability to view the current mapcycle of all modules - -3.3.1 (12/13/11) -Updated sm_umc_rtv_postvoteaction cvar to allow for normal RTV votes after a vote has taken place. - -*/ - - ////----CONVARS-----///// -new Handle:cvar_filename = INVALID_HANDLE; -new Handle:cvar_randnext = INVALID_HANDLE; -new Handle:cvar_randnext_mem = INVALID_HANDLE; -new Handle:cvar_randnext_catmem = INVALID_HANDLE; -new Handle:cvar_start = INVALID_HANDLE; - ////----/CONVARS-----///// - -//Mapcycle KV -new Handle:map_kv = INVALID_HANDLE; -new Handle:umc_mapcycle = INVALID_HANDLE; - -//Memory queues -new Handle:randnext_mem_arr = INVALID_HANDLE; -new Handle:randnext_catmem_arr = INVALID_HANDLE; - -//Stores the next category to randomly select a map from. -new String:next_rand_cat[MAP_LENGTH]; - -//Used to trigger the selection if the mode doesn't support the "game_end" event -new UserMsg:VGuiMenu; -new bool:intermission_called; - -//Flag -new bool:setting_map; //Are we setting the nextmap at the end of this map? - - -//************************************************************************************************// -// SOURCEMOD EVENTS // -//************************************************************************************************// - -//Called when the plugin is finished loading. -public OnPluginStart() -{ - cvar_start = CreateConVar( - "sm_umc_randcycle_start", - "1", - "Specifies when to select the next map.\n 0 - Map Start,\n 1 - Map End", - 0, true, 0.0, true, 1.0 - ); - - cvar_randnext_catmem = CreateConVar( - "sm_umc_randcycle_groupexclude", - "0", - "Specifies how many past map groups to exclude when picking a random map.", - 0, true, 0.0 - ); - - cvar_randnext = CreateConVar( - "sm_umc_randcycle_enabled", - "1", - "Enables random selection of the next map at the end of each map if a vote hasn't taken place.", - 0, true, 0.0, true, 1.0 - ); - - cvar_randnext_mem = CreateConVar( - "sm_umc_randcycle_mapexclude", - "4", - "Specifies how many past maps to exclude when picking a random map. 1 = Current Map Only", - 0, true, 0.0 - ); - - cvar_filename = CreateConVar( - "sm_umc_randcycle_cyclefile", - "umc_mapcycle.txt", - "File to use for Ultimate Mapchooser's map rotation." - ); - - //Create the config if it doesn't exist, and then execute it. - AutoExecConfig(true, "umc-randomcycle"); - - //Admin commmand to pick a random nextmap. - RegAdminCmd( - "sm_umc_randcycle_picknextmapnow", - Command_Random, - ADMFLAG_CHANGEMAP, - "Makes Ultimate Mapchooser pick a random nextmap." - ); - - //Hook end of game. - HookEventEx("dod_game_over", Event_GameEnd); //DoD - HookEventEx("teamplay_game_over", Event_GameEnd); //TF2 - HookEventEx("tf_game_over", Event_GameEnd); //TF2 (mp_winlimit) - HookEventEx("game_newmap", Event_GameEnd); //Insurgency - HookEventEx("cs_intermission", Event_GameEnd); //CS:GO - - //Hook intermission - new String:game[20]; - GetGameFolderName(game, sizeof(game)); - if (!StrEqual(game, "tf", false) && - !StrEqual(game, "dod", false) && - !StrEqual(game, "insurgency", false)) - { - LogUMCMessage("SETUP: Hooking intermission..."); - VGuiMenu = GetUserMessageId("VGUIMenu"); - HookUserMessage(VGuiMenu, _VGuiMenu); - } - - //Hook cvar change - HookConVarChange(cvar_randnext_mem, Handle_RandNextMemoryChange); - - //Initialize our memory arrays - new numCells = ByteCountToCells(MAP_LENGTH); - randnext_mem_arr = CreateArray(numCells); - randnext_catmem_arr = CreateArray(numCells); - -#if AUTOUPDATE_ENABLE - if (LibraryExists("updater")) - { - Updater_AddPlugin(UPDATE_URL); - } -#endif -} - - -#if AUTOUPDATE_ENABLE -//Called when a new API library is loaded. Used to register UMC auto-updating. -public OnLibraryAdded(const String:name[]) -{ - if (StrEqual(name, "updater")) - { - Updater_AddPlugin(UPDATE_URL); - } -} -#endif - - -//************************************************************************************************// -// GAME EVENTS // -//************************************************************************************************// - -//Called after all config files were executed. -public OnConfigsExecuted() -{ - DEBUG_MESSAGE("Executing RandomCycle OnConfigsExecuted") - - intermission_called = false; - setting_map = ReloadMapcycle(); - - //Grab the name of the current map. - decl String:mapName[MAP_LENGTH]; - GetCurrentMap(mapName, sizeof(mapName)); - - decl String:groupName[MAP_LENGTH]; - UMC_GetCurrentMapGroup(groupName, sizeof(groupName)); - - if (setting_map && StrEqual(groupName, INVALID_GROUP, false)) - { - KvFindGroupOfMap(umc_mapcycle, mapName, groupName, sizeof(groupName)); - } - - DEBUG_MESSAGE("Current Map: %s -- %s", mapName, groupName) - - SetupNextRandGroup(mapName, groupName); - - //Add the map to all the memory queues. - new mapmem = GetConVarInt(cvar_randnext_mem); - new catmem = GetConVarInt(cvar_randnext_catmem); - AddToMemoryArray(mapName, randnext_mem_arr, mapmem); - AddToMemoryArray(groupName, randnext_catmem_arr, (mapmem > catmem) ? mapmem : catmem); - - if (setting_map) - RemovePreviousMapsFromCycle(); - - if (!GetConVarBool(cvar_start)) - { - LogUMCMessage("Selecting random next map due to map starting."); - DoRandomNextMap(); - } -} - - -//Called when intermission window is active. Necessary for mods without "game_end" event. -public Action:_VGuiMenu(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, - bool:init) -{ - //Do nothing if we have already seen the intermission. - if (intermission_called) - return; - - new String:type[10]; - BfReadString(bf, type, sizeof(type)); - - if (strcmp(type, "scores", false) == 0) - { - if (BfReadByte(bf) == 1 && BfReadByte(bf) == 0) - { - intermission_called = true; - Event_GameEnd(INVALID_HANDLE, "", false); - } - } -} - - -//Called when the game ends. Used to trigger random selection of the next map. -public Event_GameEnd(Handle:evnt, const String:name[], bool:dontBroadcast) -{ - //Select and change to a random map if... - // ...the cvar to do so is enabled AND - // ...we haven't completed an end-of-map vote AND - // ...we haven't completed an RTV. - if (GetConVarBool(cvar_start) && GetConVarBool(cvar_randnext) && setting_map) - { - LogUMCMessage("Selecting random next map due to map ending."); - DoRandomNextMap(); - } -} - - -//************************************************************************************************// -// SETUP // -//************************************************************************************************// - -//Fetches the set next group for the given map and group in the mapcycle. -SetupNextRandGroup(const String:map[], const String:group[]) -{ - decl String:gNextGroup[MAP_LENGTH]; - - if (umc_mapcycle == INVALID_HANDLE || StrEqual(group, INVALID_GROUP, false)) - { - strcopy(next_rand_cat, sizeof(next_rand_cat), INVALID_GROUP); - return; - } - - KvRewind(umc_mapcycle); - if (KvJumpToKey(umc_mapcycle, group)) - { - KvGetString(umc_mapcycle, NEXT_MAPGROUP_KEY, gNextGroup, sizeof(gNextGroup), - INVALID_GROUP); - if (KvJumpToKey(umc_mapcycle, map)) - { - KvGetString(umc_mapcycle, NEXT_MAPGROUP_KEY, next_rand_cat, sizeof(next_rand_cat), - gNextGroup); - KvGoBack(umc_mapcycle); - } - KvGoBack(umc_mapcycle); - } - - DEBUG_MESSAGE("Next Random Mapgroup: %s", next_rand_cat) -} - - -//Parses the mapcycle file and returns a KV handle representing the mapcycle. -Handle:GetMapcycle() -{ - //Grab the file name from the cvar. - decl String:filename[PLATFORM_MAX_PATH]; - GetConVarString(cvar_filename, filename, sizeof(filename)); - - //Get the kv handle from the file. - new Handle:result = GetKvFromFile(filename, "umc_rotation"); - - //Log an error and return empty handle if... - // ...the mapcycle file failed to parse. - if (result == INVALID_HANDLE) - { - LogError("SETUP: Mapcycle failed to load!"); - return INVALID_HANDLE; - } - - //Success! - return result; -} - - -//Reloads the mapcycle. Returns true on success, false on failure. -bool:ReloadMapcycle() -{ - if (umc_mapcycle != INVALID_HANDLE) - { - CloseHandle(umc_mapcycle); - umc_mapcycle = INVALID_HANDLE; - } - if (map_kv != INVALID_HANDLE) - { - CloseHandle(map_kv); - map_kv = INVALID_HANDLE; - } - umc_mapcycle = GetMapcycle(); - - return umc_mapcycle != INVALID_HANDLE; -} - - -// -RemovePreviousMapsFromCycle() -{ - map_kv = CreateKeyValues("umc_rotation"); - KvCopySubkeys(umc_mapcycle, map_kv); - FilterMapcycleFromArrays(map_kv, randnext_mem_arr, randnext_catmem_arr, - GetConVarInt(cvar_randnext_catmem)); -} - - -//************************************************************************************************// -// CVAR CHANGES // -//************************************************************************************************// - -//Called when the number of excluded previous maps from random selection of the next map has -//changed. -public Handle_RandNextMemoryChange(Handle:convar, const String:oldValue[], const String:newValue[]) -{ - //Trim the memory array for random selection of the next map. - //We pass 1 extra to the argument in order to account for the current map, which should - //always be excluded. - TrimArray(randnext_mem_arr, StringToInt(newValue)); -} - - -//************************************************************************************************// -// COMMANDS // -//************************************************************************************************// - -//Called when the command to pick a random nextmap is called -public Action:Command_Random(client, args) -{ - if (setting_map || map_kv != INVALID_HANDLE) - { - LogUMCMessage("User %L requested a random map be selected now.", client); - DoRandomNextMap(); - } - else - ReplyToCommand(client, "\x03[UMC]\x01 Mapcycle is invalid, cannot pick a map."); - - return Plugin_Handled; -} - - -//************************************************************************************************// -// RANDOM NEXTMAP // -//************************************************************************************************// - -//Sets a random next map. Returns true on success. -DoRandomNextMap() -{ - DEBUG_MESSAGE("next_rand_cat: %s", next_rand_cat) - decl String:nextMap[MAP_LENGTH], String:nextGroup[MAP_LENGTH]; - if (UMC_GetRandomMap(map_kv, umc_mapcycle, next_rand_cat, nextMap, sizeof(nextMap), nextGroup, - sizeof(nextGroup), false, true)) - { - DEBUG_MESSAGE("Random map: %s %s", nextMap, nextGroup) - UMC_SetNextMap(map_kv, nextMap, nextGroup, ChangeMapTime_MapEnd); - } - else - { - LogUMCMessage("Failed to find a suitable random map."); - } -} - - -//************************************************************************************************// -// ULTIMATE MAPCHOOSER EVENTS // -//************************************************************************************************// - -//Called when UMC has set a next map. -public UMC_OnNextmapSet(Handle:kv, const String:map[], const String:group[], const String:display[]) -{ - LogUMCMessage("Disabling random nextmap selection."); - setting_map = false; -} - - -//Called when UMC requests that the mapcycle should be reloaded. -public UMC_RequestReloadMapcycle() -{ - new bool:reloaded = ReloadMapcycle(); - if (reloaded) - RemovePreviousMapsFromCycle(); - setting_map = reloaded && setting_map; -} - - -//Called when UMC requests that the mapcycle is printed to the console. -public UMC_DisplayMapCycle(client, bool:filtered) -{ - PrintToConsole(client, "Module: Random Mapcycle"); - if (filtered) - { - new Handle:filteredMapcycle = UMC_FilterMapcycle( - map_kv, umc_mapcycle, false, true - ); - PrintKvToConsole(filteredMapcycle, client); - CloseHandle(filteredMapcycle); - } - else - { - PrintKvToConsole(umc_mapcycle, client); - } -} - +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Ultimate Mapchooser - Random Cycle * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#pragma semicolon 1 + +#include +#include +#include + +#undef REQUIRE_PLUGIN + +//Auto update +#include +#if AUTOUPDATE_DEV + #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/dev/updateinfo-umc-randomcycle.txt" +#else + #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/updateinfo-umc-randomcycle.txt" +#endif + +#define NEXT_MAPGROUP_KEY "next_mapgroup" + +//Plugin Information +public Plugin:myinfo = +{ + name = "[UMC] Random Cycle", + author = "Steell", + description = "Extends Ultimate Mapchooser to provide random selecting of the next map.", + version = PL_VERSION, + url = "http://forums.alliedmods.net/showthread.php?t=134190" +}; + +//Changelog: +/* +3.3.2 (3/4/2012) +Added ability for Random Mapcycle to select the next map at the start of the game. +-New cvar "sm_umc_randcycle_start" to control this ability +Updated UMC Logging functionality +Added ability to view the current mapcycle of all modules + +3.3.1 (12/13/11) +Updated sm_umc_rtv_postvoteaction cvar to allow for normal RTV votes after a vote has taken place. + +*/ + + ////----CONVARS-----///// +new Handle:cvar_filename = INVALID_HANDLE; +new Handle:cvar_randnext = INVALID_HANDLE; +new Handle:cvar_randnext_mem = INVALID_HANDLE; +new Handle:cvar_randnext_catmem = INVALID_HANDLE; +new Handle:cvar_start = INVALID_HANDLE; + ////----/CONVARS-----///// + +//Mapcycle KV +new Handle:map_kv = INVALID_HANDLE; +new Handle:umc_mapcycle = INVALID_HANDLE; + +//Memory queues +new Handle:randnext_mem_arr = INVALID_HANDLE; +new Handle:randnext_catmem_arr = INVALID_HANDLE; + +//Stores the next category to randomly select a map from. +new String:next_rand_cat[MAP_LENGTH]; + +//Used to trigger the selection if the mode doesn't support the "game_end" event +new UserMsg:VGuiMenu; +new bool:intermission_called; + +//Flag +new bool:setting_map; //Are we setting the nextmap at the end of this map? + + +//************************************************************************************************// +// SOURCEMOD EVENTS // +//************************************************************************************************// + +//Called when the plugin is finished loading. +public OnPluginStart() +{ + cvar_start = CreateConVar( + "sm_umc_randcycle_start", + "1", + "Specifies when to select the next map.\n 0 - Map Start,\n 1 - Map End", + 0, true, 0.0, true, 1.0 + ); + + cvar_randnext_catmem = CreateConVar( + "sm_umc_randcycle_groupexclude", + "0", + "Specifies how many past map groups to exclude when picking a random map.", + 0, true, 0.0 + ); + + cvar_randnext = CreateConVar( + "sm_umc_randcycle_enabled", + "1", + "Enables random selection of the next map at the end of each map if a vote hasn't taken place.", + 0, true, 0.0, true, 1.0 + ); + + cvar_randnext_mem = CreateConVar( + "sm_umc_randcycle_mapexclude", + "4", + "Specifies how many past maps to exclude when picking a random map. 1 = Current Map Only", + 0, true, 0.0 + ); + + cvar_filename = CreateConVar( + "sm_umc_randcycle_cyclefile", + "umc_mapcycle.txt", + "File to use for Ultimate Mapchooser's map rotation." + ); + + //Create the config if it doesn't exist, and then execute it. + AutoExecConfig(true, "umc-randomcycle"); + + //Admin commmand to pick a random nextmap. + RegAdminCmd( + "sm_umc_randcycle_picknextmapnow", + Command_Random, + ADMFLAG_CHANGEMAP, + "Makes Ultimate Mapchooser pick a random nextmap." + ); + + //Hook end of game. + HookEventEx("dod_game_over", Event_GameEnd); //DoD + HookEventEx("teamplay_game_over", Event_GameEnd); //TF2 + HookEventEx("tf_game_over", Event_GameEnd); //TF2 (mp_winlimit) + HookEventEx("game_newmap", Event_GameEnd); //Insurgency + HookEventEx("cs_intermission", Event_GameEnd); //CS:GO + + //Hook intermission + new String:game[20]; + GetGameFolderName(game, sizeof(game)); + if (!StrEqual(game, "tf", false) && + !StrEqual(game, "dod", false) && + !StrEqual(game, "insurgency", false)) + { + LogUMCMessage("SETUP: Hooking intermission..."); + VGuiMenu = GetUserMessageId("VGUIMenu"); + HookUserMessage(VGuiMenu, _VGuiMenu); + } + + //Hook cvar change + HookConVarChange(cvar_randnext_mem, Handle_RandNextMemoryChange); + + //Initialize our memory arrays + new numCells = ByteCountToCells(MAP_LENGTH); + randnext_mem_arr = CreateArray(numCells); + randnext_catmem_arr = CreateArray(numCells); + +#if AUTOUPDATE_ENABLE + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +#endif +} + + +#if AUTOUPDATE_ENABLE +//Called when a new API library is loaded. Used to register UMC auto-updating. +public OnLibraryAdded(const String:name[]) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +} +#endif + + +//************************************************************************************************// +// GAME EVENTS // +//************************************************************************************************// + +//Called after all config files were executed. +public OnConfigsExecuted() +{ + DEBUG_MESSAGE("Executing RandomCycle OnConfigsExecuted") + + intermission_called = false; + setting_map = ReloadMapcycle(); + + //Grab the name of the current map. + decl String:mapName[MAP_LENGTH]; + GetCurrentMap(mapName, sizeof(mapName)); + + decl String:groupName[MAP_LENGTH]; + UMC_GetCurrentMapGroup(groupName, sizeof(groupName)); + + if (setting_map && StrEqual(groupName, INVALID_GROUP, false)) + { + KvFindGroupOfMap(umc_mapcycle, mapName, groupName, sizeof(groupName)); + } + + DEBUG_MESSAGE("Current Map: %s -- %s", mapName, groupName) + + SetupNextRandGroup(mapName, groupName); + + //Add the map to all the memory queues. + new mapmem = GetConVarInt(cvar_randnext_mem); + new catmem = GetConVarInt(cvar_randnext_catmem); + AddToMemoryArray(mapName, randnext_mem_arr, mapmem); + AddToMemoryArray(groupName, randnext_catmem_arr, (mapmem > catmem) ? mapmem : catmem); + + if (setting_map) + RemovePreviousMapsFromCycle(); + + if (!GetConVarBool(cvar_start)) + { + LogUMCMessage("Selecting random next map due to map starting."); + DoRandomNextMap(); + } +} + + +//Called when intermission window is active. Necessary for mods without "game_end" event. +public Action:_VGuiMenu(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, + bool:init) +{ + //Do nothing if we have already seen the intermission. + if (intermission_called) + return; + + new String:type[10]; + BfReadString(bf, type, sizeof(type)); + + if (strcmp(type, "scores", false) == 0) + { + if (GetUserMessageType() == UM_Protobuf) + { + if (PbReadBool(bf, "show") && PbGetRepeatedFieldCount(bf, "subkeys") == 0) + { + intermission_called = true; + Event_GameEnd(INVALID_HANDLE, "", false); + } + } + else + { + if (BfReadByte(bf) == 1 && BfReadByte(bf) == 0) + { + intermission_called = true; + Event_GameEnd(INVALID_HANDLE, "", false); + } + } + } +} + + +//Called when the game ends. Used to trigger random selection of the next map. +public Event_GameEnd(Handle:evnt, const String:name[], bool:dontBroadcast) +{ + //Select and change to a random map if... + // ...the cvar to do so is enabled AND + // ...we haven't completed an end-of-map vote AND + // ...we haven't completed an RTV. + if (GetConVarBool(cvar_start) && GetConVarBool(cvar_randnext) && setting_map) + { + LogUMCMessage("Selecting random next map due to map ending."); + DoRandomNextMap(); + } +} + + +//************************************************************************************************// +// SETUP // +//************************************************************************************************// + +//Fetches the set next group for the given map and group in the mapcycle. +SetupNextRandGroup(const String:map[], const String:group[]) +{ + decl String:gNextGroup[MAP_LENGTH]; + + if (umc_mapcycle == INVALID_HANDLE || StrEqual(group, INVALID_GROUP, false)) + { + strcopy(next_rand_cat, sizeof(next_rand_cat), INVALID_GROUP); + return; + } + + KvRewind(umc_mapcycle); + if (KvJumpToKey(umc_mapcycle, group)) + { + KvGetString(umc_mapcycle, NEXT_MAPGROUP_KEY, gNextGroup, sizeof(gNextGroup), + INVALID_GROUP); + if (KvJumpToKey(umc_mapcycle, map)) + { + KvGetString(umc_mapcycle, NEXT_MAPGROUP_KEY, next_rand_cat, sizeof(next_rand_cat), + gNextGroup); + KvGoBack(umc_mapcycle); + } + KvGoBack(umc_mapcycle); + } + + DEBUG_MESSAGE("Next Random Mapgroup: %s", next_rand_cat) +} + + +//Parses the mapcycle file and returns a KV handle representing the mapcycle. +Handle:GetMapcycle() +{ + //Grab the file name from the cvar. + decl String:filename[PLATFORM_MAX_PATH]; + GetConVarString(cvar_filename, filename, sizeof(filename)); + + //Get the kv handle from the file. + new Handle:result = GetKvFromFile(filename, "umc_rotation"); + + //Log an error and return empty handle if... + // ...the mapcycle file failed to parse. + if (result == INVALID_HANDLE) + { + LogError("SETUP: Mapcycle failed to load!"); + return INVALID_HANDLE; + } + + //Success! + return result; +} + + +//Reloads the mapcycle. Returns true on success, false on failure. +bool:ReloadMapcycle() +{ + if (umc_mapcycle != INVALID_HANDLE) + { + CloseHandle(umc_mapcycle); + umc_mapcycle = INVALID_HANDLE; + } + if (map_kv != INVALID_HANDLE) + { + CloseHandle(map_kv); + map_kv = INVALID_HANDLE; + } + umc_mapcycle = GetMapcycle(); + + return umc_mapcycle != INVALID_HANDLE; +} + + +// +RemovePreviousMapsFromCycle() +{ + map_kv = CreateKeyValues("umc_rotation"); + KvCopySubkeys(umc_mapcycle, map_kv); + FilterMapcycleFromArrays(map_kv, randnext_mem_arr, randnext_catmem_arr, + GetConVarInt(cvar_randnext_catmem)); +} + + +//************************************************************************************************// +// CVAR CHANGES // +//************************************************************************************************// + +//Called when the number of excluded previous maps from random selection of the next map has +//changed. +public Handle_RandNextMemoryChange(Handle:convar, const String:oldValue[], const String:newValue[]) +{ + //Trim the memory array for random selection of the next map. + //We pass 1 extra to the argument in order to account for the current map, which should + //always be excluded. + TrimArray(randnext_mem_arr, StringToInt(newValue)); +} + + +//************************************************************************************************// +// COMMANDS // +//************************************************************************************************// + +//Called when the command to pick a random nextmap is called +public Action:Command_Random(client, args) +{ + if (setting_map || map_kv != INVALID_HANDLE) + { + LogUMCMessage("User %L requested a random map be selected now.", client); + DoRandomNextMap(); + } + else + ReplyToCommand(client, "\x03[UMC]\x01 Mapcycle is invalid, cannot pick a map."); + + return Plugin_Handled; +} + + +//************************************************************************************************// +// RANDOM NEXTMAP // +//************************************************************************************************// + +//Sets a random next map. Returns true on success. +DoRandomNextMap() +{ + DEBUG_MESSAGE("next_rand_cat: %s", next_rand_cat) + decl String:nextMap[MAP_LENGTH], String:nextGroup[MAP_LENGTH]; + if (UMC_GetRandomMap(map_kv, umc_mapcycle, next_rand_cat, nextMap, sizeof(nextMap), nextGroup, + sizeof(nextGroup), false, true)) + { + DEBUG_MESSAGE("Random map: %s %s", nextMap, nextGroup) + UMC_SetNextMap(map_kv, nextMap, nextGroup, ChangeMapTime_MapEnd); + } + else + { + LogUMCMessage("Failed to find a suitable random map."); + } +} + + +//************************************************************************************************// +// ULTIMATE MAPCHOOSER EVENTS // +//************************************************************************************************// + +//Called when UMC has set a next map. +public UMC_OnNextmapSet(Handle:kv, const String:map[], const String:group[], const String:display[]) +{ + LogUMCMessage("Disabling random nextmap selection."); + setting_map = false; +} + + +//Called when UMC requests that the mapcycle should be reloaded. +public UMC_RequestReloadMapcycle() +{ + new bool:reloaded = ReloadMapcycle(); + if (reloaded) + RemovePreviousMapsFromCycle(); + setting_map = reloaded && setting_map; +} + + +//Called when UMC requests that the mapcycle is printed to the console. +public UMC_DisplayMapCycle(client, bool:filtered) +{ + PrintToConsole(client, "Module: Random Mapcycle"); + if (filtered) + { + new Handle:filteredMapcycle = UMC_FilterMapcycle( + map_kv, umc_mapcycle, false, true + ); + PrintKvToConsole(filteredMapcycle, client); + CloseHandle(filteredMapcycle); + } + else + { + PrintKvToConsole(umc_mapcycle, client); + } +} + From 3c764e1ed35723408a7c108f916f412d804d574b Mon Sep 17 00:00:00 2001 From: Stephen Elliott Date: Tue, 29 Jan 2013 02:27:45 -0500 Subject: [PATCH 3/4] another protobuff fix --- addons/sourcemod/scripting/umc-randomcycle.sp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/umc-randomcycle.sp b/addons/sourcemod/scripting/umc-randomcycle.sp index 07b2e04..1aac935 100644 --- a/addons/sourcemod/scripting/umc-randomcycle.sp +++ b/addons/sourcemod/scripting/umc-randomcycle.sp @@ -224,8 +224,15 @@ public Action:_VGuiMenu(UserMsg:msg_id, Handle:bf, const players[], playersNum, return; new String:type[10]; - BfReadString(bf, type, sizeof(type)); - + if (GetUserMessageType() == UM_Protobuf) + { + PbReadString(bf, "name", type, sizeof(type)); + } + else + { + BfReadString(bf, type, sizeof(type)); + } + if (strcmp(type, "scores", false) == 0) { if (GetUserMessageType() == UM_Protobuf) From e83d93e274aba7debb1e0c421b865ce56859da75 Mon Sep 17 00:00:00 2001 From: Stephen Elliott Date: Thu, 7 Mar 2013 18:56:20 -0500 Subject: [PATCH 4/4] fixed dem build errors --- .../sourcemod/scripting/include/umc-core.inc | 20 ++++++++-------- addons/sourcemod/scripting/umc-core.sp | 24 ++++++++++++------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/addons/sourcemod/scripting/include/umc-core.inc b/addons/sourcemod/scripting/include/umc-core.inc index a5bae8f..dbbd279 100644 --- a/addons/sourcemod/scripting/include/umc-core.inc +++ b/addons/sourcemod/scripting/include/umc-core.inc @@ -60,35 +60,35 @@ enum UMC_VoteType { VoteType_Map = 0, VoteType_Group, - VoteType_Tier -} + VoteType_Tier, +}; enum UMC_ChangeMapTime { ChangeMapTime_Now = 0, ChangeMapTime_RoundEnd, - ChangeMapTime_MapEnd -} + ChangeMapTime_MapEnd, +}; enum UMC_VoteFailAction { VoteFailAction_Nothing = 0, - VoteFailAction_Runoff -} + VoteFailAction_Runoff, +}; enum UMC_RunoffFailAction { RunoffFailAction_Nothing = 0, - RunoffFailAction_Accept -} + RunoffFailAction_Accept, +}; enum UMC_VoteResponse { VoteResponse_Success = 0, VoteResponse_Runoff, VoteResponse_Tiered, - VoteResponse_Fail -} + VoteResponse_Fail, +}; /** diff --git a/addons/sourcemod/scripting/umc-core.sp b/addons/sourcemod/scripting/umc-core.sp index 669003a..68d46b7 100644 --- a/addons/sourcemod/scripting/umc-core.sp +++ b/addons/sourcemod/scripting/umc-core.sp @@ -2393,7 +2393,7 @@ enum UMC_BuildOptionsError //Build and returns a new vote menu. -Handle:BuildVoteItems(Handle:vM, Handle:kv, Handle:mapcycle, UMC_VoteType:&type, bool:scramble, +Handle:BuildVoteItems(Handle:vM, Handle:kv, Handle:mapcycle, &UMC_VoteType:type, bool:scramble, bool:allowDupes, bool:strictNoms, bool:exclude, bool:extend, bool:dontChange) { new Handle:result = CreateArray(); @@ -2436,10 +2436,11 @@ Handle:BuildVoteItems(Handle:vM, Handle:kv, Handle:mapcycle, UMC_VoteType:&type, //Builds and returns a menu for a map vote. -UMC_BuildOptionsError:BuildMapVoteItems(Handle:voteManager, Handle:okv, Handle:mapcycle, - bool:scramble, bool:extend, bool:dontChange, - bool:ignoreDupes=false, bool:strictNoms=false, - bool:ignoreInvoteSetting=false, bool:exclude=true) +UMC_BuildOptionsError:BuildMapVoteItems(Handle:voteManager, Handle:result, Handle:okv, + Handle:mapcycle, bool:scramble, bool:extend, + bool:dontChange, bool:ignoreDupes=false, + bool:strictNoms=false, bool:ignoreInvoteSetting=false, + bool:exclude=true) { DEBUG_MESSAGE("MAPVOTE - Building map vote menu.") //Throw an error and return nothing if... @@ -4615,12 +4616,17 @@ public Action:Handle_TieredVoteTimer(Handle:timer, Handle:pack) GetTrieValue(vM, "stored_ignoredupes", stored_ignoredupes); GetTrieValue(vM, "stored_strictnoms", stored_strictnoms); GetTrieValue(vM, "stored_exclude", stored_exclude); - + //Initialize the menu. - new Handle:options = BuildMapVoteItems(vM, tieredKV, stored_mapcycle, stored_scramble, false, - false, stored_ignoredupes, stored_strictnoms, true, stored_exclude); + new Handle:options = CreateArray(); + + new UMC_BuildOptionsError:error = BuildMapVoteItems( + vM, options, stored_mapcycle, + tieredKV, stored_scramble, false, + false, stored_ignoredupes, + stored_strictnoms, true, stored_exclude); - if (options != INVALID_HANDLE) + if (error == BuildOptionsError_Success) { //Play the vote start sound if... // ...the vote start sound is defined.