typo fix

typo fix

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <nextmap>
#include <smutils>
#include <mapchooser_redux>

// options
#undef REQUIRE_PLUGIN
#include <store>
#include <shop>
#include <fys.pupd>

Handle g_tVote;
Handle g_tRetry;
Handle g_tWarning;

Menu g_hVoteMenu;

ArrayList g_aMapList;
ArrayList g_aNextMapList;
ArrayList g_aNominations;

int g_iExtends;
int g_iMapFileSerial = -1;
int g_iNominateCount;
int g_iRunoffCount;
bool g_bPartyblock;
bool g_bAllowCountdown;
bool g_bHasVoteStarted;
bool g_bWaitingForVote;
bool g_bMapVoteCompleted;
bool g_bChangeMapInProgress;
bool g_bChangeMapAtRoundEnd;
bool g_bWarningInProgress;
bool g_bBlockedSlots;

bool g_pStore;
bool g_pShop;

enum TimerLocation
{
    TimerLocation_Hint = 0,
    TimerLocation_Text,
    TimerLocation_Chat,
    TimerLocation_HUD
}

enum WarningType
{
    WarningType_Vote,
    WarningType_Revote
}

enum struct Convars
{
    ConVar TimeLoc;
    ConVar NameTag;
    ConVar DescTag;
    ConVar MaxExts;
    ConVar Recents;
    ConVar LtpMtpl;
    ConVar BCState;
    ConVar Shuffle;
    ConVar Refunds;
    ConVar Require;
}

// cvars
Convars g_ConVars;

MapChange g_MapChange;

#include "mapchooser/cmds.sp"
#include "mapchooser/cvars.sp"
#include "mapchooser/data.sp"
#include "mapchooser/events.sp"
#include "mapchooser/natives.sp"
#include "mapchooser/stocks.sp"

public Plugin myinfo =
{
    name        = "MapChooser Redux",
    author      = "Kyle",
    description = "Automated Map Voting with Extensions",
    version     = MCR_VERSION,
    url         = "https://kxnrl.com"
};

public void OnPluginStart()
{
    SMUtils_SetChatPrefix("[\x02M\x04C\x0CR\x01]");
    SMUtils_SetChatSpaces("   ");
    SMUtils_SetChatConSnd(false);
    SMUtils_SetTextDest(HUD_PRINTCENTER);

    Cmds_OnPluginStart();
    Cvars_OnPluginStart();
    Data_OnPluginStart();
    Events_OnPluginStart();
    Natives_OnPluginStart();

    LoadTranslations("com.kxnrl.mcr.translations");

    g_aMapList     = new ArrayList(ByteCountToCells(128));
    g_aNextMapList = new ArrayList(ByteCountToCells(128));
    g_aNominations = new ArrayList(sizeof(Nominations));
}

public void OnAllPluginsLoaded()
{
    g_pStore = LibraryExists("store");
    g_pShop = LibraryExists("shop-core");

    Data_OnAllPluginsLoaded();
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public void OnConfigsExecuted()
{
    if (ReadMapList(g_aMapList, g_iMapFileSerial, "mapchooser", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) != null)
        if (g_iMapFileSerial == -1)
            SetFailState("Unable to create a valid map list.");

    g_iExtends = 0;
    g_bPartyblock = false;
    g_bAllowCountdown = false;
    g_bMapVoteCompleted = false;
    g_bChangeMapAtRoundEnd = false;
    g_iNominateCount = 0;

    g_aNominations.Clear();

    CreateNextVote();
    SetupTimeleftTimer();
    Call_MapDataLoaded();
    Call_MapVotePoolChanged();
}

public void OnMapEnd()
{
    g_bHasVoteStarted = false;
    g_bWaitingForVote = false;
    g_bChangeMapInProgress = false;

    g_tVote = null;
    g_tRetry = null;
    g_tWarning = null;
    g_iRunoffCount = 0;

    Data_OnMapEnd();
}

public void OnClientConnected(int client)
{
    if (GetClientCount(false) >= 15)
    {
        // allow countdown cooldown
        if (!g_bAllowCountdown)
        {
            char map[128];
            GetCurrentMap(map, 128);
            SetCooldown(map);
            SetLastPlayed(map);
            g_bAllowCountdown = true;
        }
    }
}

public void OnClientDisconnect(int client)
{
    if (!IsClientInGame(client))
        return;

    for (int index = 0; index < g_aNominations.Length; index++)
    {
        Nominations n;
        g_aNominations.GetArray(index, n, sizeof(Nominations));
        if (n.m_Owner == client)
        {
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            g_aNominations.Erase(index);

            LogMessage("Removed [%s] by %L from nomination list.", n.m_Map, n.m_Owner);

            // party block disconnect
            if (g_bPartyblock)
                g_bPartyblock = false;

            break;
        }
    }
}

public void OnMapTimeLeftChanged()
{
    SetupTimeleftTimer();
}

void SetupTimeleftTimer()
{
    if (g_bMapVoteCompleted)
    {
        LogMessage("Map vote had been completed.");
        return;
    }

    int timeLeft;
    if (!GetMapTimeLeft(timeLeft) || timeLeft <= 0)
    {
        LogMessage("Failed to GetMapTimeLeft()");
        return;
    }

    if (timeLeft - 300 < 0 && !g_bHasVoteStarted)
    {
        SetupWarningTimer(WarningType_Vote);
        return;
    }

    if (g_aMapList.Length <= 0)
    {
        LogError("No enough maps to start the vote.");
        return;
    }

    if (g_tWarning == null)
    {
        if (g_tVote != null)
            KillTimer(g_tVote);

        g_tVote = CreateTimer(float(timeLeft - 300), Timer_StartWarningTimer, _, TIMER_FLAG_NO_MAPCHANGE);
    }
}

public Action Timer_StartWarningTimer(Handle timer)
{
    g_tVote = null;

    if (!g_bWarningInProgress || g_tWarning == null)
        SetupWarningTimer(WarningType_Vote);

    return Plugin_Stop;
}

public Action Timer_StartMapVote(Handle timer, DataPack data)
{
    static int timePassed;

    if (!g_aMapList.Length || g_bMapVoteCompleted || g_bHasVoteStarted)
    {
        g_tWarning = null;
        return Plugin_Stop;
    }

    data.Reset();
    int warningMaxTime = data.ReadCell();
    int warningTimeRemaining = warningMaxTime - timePassed;

    switch(view_as<TimerLocation>(g_ConVars.TimeLoc.IntValue))
    {
        case TimerLocation_Text: tTextAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown text hint shuffle" : "mcr countdown text hint", warningTimeRemaining);
        case TimerLocation_Chat: tChatAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown chat shuffle"      : "mcr countdown chat",      warningTimeRemaining);
        case TimerLocation_Hint: tHintAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown text hint shuffle" : "mcr countdown text hint", warningTimeRemaining);
        case TimerLocation_HUD:  DisplayCountdownHUD(warningTimeRemaining);
    }

    if (timePassed++ >= warningMaxTime)
    {
        if (timer == g_tRetry)
        {
            g_bWaitingForVote = false;
            g_tRetry = null;
        }
        else
        {
            g_tWarning = null;
        }

        timePassed = 0;
        MapChange mapChange = view_as<MapChange>(data.ReadCell());
        ArrayList arraylist = view_as<ArrayList>(data.ReadCell());

        InitiateVote(mapChange, arraylist);

        return Plugin_Stop;
    }

    return Plugin_Continue;
}

public Action Command_Mapvote(int client, int args)
{
    tChatAll("%t", "mcr voting started");

    SetupWarningTimer(WarningType_Vote, MapChange_MapEnd, null, true);

    LogAction(client, -1, "%L -> called mapvote.", client);

    return Plugin_Handled;
}

void InitiateVote(MapChange when, ArrayList inputlist)
{
    g_bWaitingForVote = true;
    g_bWarningInProgress = false;

    if (IsVoteInProgress())
    {
        LogMessage("IsVoteInProgress -> %d", FAILURE_TIMER_LENGTH);

        DataPack data = new DataPack();
        data.WriteCell(FAILURE_TIMER_LENGTH);
        data.WriteCell(when);
        data.WriteCell(inputlist);
        data.Reset();

        g_tRetry = CreateTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT|TIMER_DATA_HNDL_CLOSE);

        return;
    }

    if (g_bMapVoteCompleted && g_bChangeMapInProgress)
        return;

    SetHudTextParams(-1.0, 0.32, 3.5, 0, 255, 255, 255, 0, 0.3, 0.3, 0.3);
    for(int client = 1; client <= MaxClients; ++client)
        if (IsClientInGame(client) && !IsFakeClient(client))
            ShowHudText(client, 0, "%T", "mcr voting started", client);

    g_MapChange = when;

    g_bWaitingForVote = false;
    g_bHasVoteStarted = true;

    Handle menuStyle = GetMenuStyleHandle(MenuStyle_Default);

    if (menuStyle != INVALID_HANDLE)
        g_hVoteMenu = CreateMenuEx(menuStyle, Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel);
    else
        g_hVoteMenu = new Menu(Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel);

    Handle radioStyle = GetMenuStyleHandle(MenuStyle_Radio);

    if (GetMenuStyle(g_hVoteMenu) == radioStyle)
    {
        g_bBlockedSlots = true;
        g_hVoteMenu.AddItem(LINE_ONE, "Choose something...", ITEMDRAW_DISABLED);
        g_hVoteMenu.AddItem(LINE_TWO, "...will ya?", ITEMDRAW_DISABLED);
    }
    else
        g_bBlockedSlots = false;

    g_hVoteMenu.OptionFlags = MENUFLAG_BUTTON_NOVOTE;

    g_hVoteMenu.SetTitle("选择下一张地图\n ");
    g_hVoteMenu.VoteResultCallback = Handler_MapVoteFinished;

    if (g_bPartyblock)
    {
        for(int i = 0; i < 5; i++)
        {
            Nominations n;
            g_aNominations.GetArray(0, n, sizeof(Nominations));
            AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner, i == 0 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
        }

        //AddExtendToMenu(g_hVoteMenu, when);
    }
    else if (inputlist == null)
    {
        char map[128];
        int voteSize = 5, nominationsToAdd = g_aNominations.Length >= voteSize ? voteSize : g_aNominations.Length;

        static ArrayList votePool = null;
        if (votePool != null)
            delete votePool;
        votePool = new ArrayList(sizeof(Nominations));

        if (g_ConVars.Shuffle.BoolValue && g_aNominations.Length >= g_ConVars.Require.IntValue)
        {
            // randomly pool
            for(int i = 0; i < nominationsToAdd; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                votePool.PushArray(n, sizeof(Nominations));
                RemoveStringFromArray(g_aNextMapList, n.m_Map);
            }
            /*
            for(int i = 0; i < g_aNominations.Length; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }
            */
            if (votePool.Length < voteSize && g_aNextMapList.Length == 0)
            {
                if (votePool.Length == 0)
                {
                    LogMessage("No maps available for vote.");
                    return;
                }
                else
                {
                    LogMessage("Not enough maps to fill map list.");
                    voteSize = votePool.Length;
                }
            }
            int count = 0;
            while(votePool.Length < voteSize && count < g_aNextMapList.Length)
            {
                g_aNextMapList.GetString(count, map, 128);
                count++;

                Nominations n;
                strcopy(n.m_Map, 128, map);
                n.m_Owner = -1;
                votePool.PushArray(n, sizeof(Nominations));
            }
            // Randomly menu
            while (votePool.Length > 0)
            {
                Nominations n;
                int i = RandomInt(0, votePool.Length - 1);
                votePool.GetArray(i, n, sizeof(Nominations));
                votePool.Erase(i);
                AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner);
            }
        }
        else
        {
            for(int i = 0; i < nominationsToAdd; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));

                AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner);
                RemoveStringFromArray(g_aNextMapList, map);

                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }

            /*
            for(int i = nominationsToAdd; i < g_aNominations.Length; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }
            */

            int i = nominationsToAdd;
            int count = 0;

            if (i < voteSize && g_aNextMapList.Length == 0)
            {
                if (i == 0)
                {
                    LogMessage("No maps available for vote.");
                    return;
                }
                else
                {
                    LogMessage("Not enough maps to fill map list.");
                    voteSize = i;
                }
            }

            while(i < voteSize && count < g_aNextMapList.Length)
            {
                g_aNextMapList.GetString(count, map, 128);
                count++;

                AddMapItem(g_hVoteMenu, map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue);
                i++;
            }
        }

        //g_aNominations.Clear();

        AddExtendToMenu(g_hVoteMenu, when);
    }
    else
    {
        char map[128];
        for(int i = 0; i < inputlist.Length; i++)
        {
            inputlist.GetString(i, map, 128);

            if (IsMapValid(map))
                AddMapItem(g_hVoteMenu, map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue);
            else if (StrEqual(map, VOTE_DONTCHANGE))
                g_hVoteMenu.AddItem(VOTE_DONTCHANGE, "Don't Change");
            else if (StrEqual(map, VOTE_EXTEND))
                g_hVoteMenu.AddItem(VOTE_EXTEND, "Extend Map");
        }
        delete inputlist;
    }

    if (5 <= GetMaxPageItems(GetMenuStyle(g_hVoteMenu)))
        g_hVoteMenu.Pagination = MENU_NO_PAGINATION;

    g_hVoteMenu.DisplayVoteToAll(15);

    Call_MapVoteStarted();

    LogAction(-1, -1, "Voting for next map has started.");
    tChatAll("%t", "mcr voting started");
}

public void Handler_VoteFinishedGeneric(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{
    char map[128];
    GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, 128);

    Call_MapVoteEnd(map);

    if (strcmp(map, VOTE_EXTEND, false) == 0)
    {
        g_iExtends++;

        int timeLimit;
        if (GetMapTimeLimit(timeLimit))
            if (timeLimit > 0)
                ExtendMapTimeLimit(1200);

        tChatAll("%t", "mcr extend map", item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");

        g_bHasVoteStarted = false;
        CreateNextVote();
        SetupTimeleftTimer();
    }
    else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
    {
        tChatAll("%t", "mcr dont change", item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");

        g_bHasVoteStarted = false;
        CreateNextVote();
        SetupTimeleftTimer();
    }
    else
    {
        if (g_MapChange == MapChange_Instant)
        {
            g_bChangeMapInProgress = true;
            CreateTimer(10.0 , Timer_ChangeMaprtv, _, TIMER_FLAG_NO_MAPCHANGE);
        }
        else if (g_MapChange == MapChange_RoundEnd)
        {
            g_bChangeMapAtRoundEnd = true;
            SetConVarInt(FindConVar("mp_timelimit"), 1);
        }

        FindConVar("nextlevel").SetString(map);
        SetNextMap(map);

        g_bHasVoteStarted = false;
        g_bMapVoteCompleted = true;

        tChatAll("%t", "mcr next map", map, item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        if (g_ConVars.DescTag.BoolValue)
        {
            char desc[128];
            GetDescEx(map, desc, 128, false, false);
            ChatAll("\x0A -> \x0E[\x05%s\x0E]", desc);
        }
        LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
    }

    // refunds
    RefundAllCredits(map);

    // reset
    g_bPartyblock = false;
}

public Action Timer_ChangeMaprtv(Handle hTimer)
{
    FindConVar("mp_halftime").SetInt(0);
    FindConVar("mp_timelimit").SetInt(0);
    FindConVar("mp_maxrounds").SetInt(0);
    FindConVar("mp_roundtime").SetInt(1);

    for(int client = 1; client <= MaxClients; ++client)
    if (IsClientInGame(client))
    if (IsPlayerAlive(client))
    ForcePlayerSuicide(client);

    CreateTimer(60.0, Timer_ChangeMap, 0, TIMER_FLAG_NO_MAPCHANGE);

    return Plugin_Stop;
}

public void Handler_MapVoteFinished(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{
    if (num_items > 1 && g_iRunoffCount < 1)
    {
        g_iRunoffCount++;
        int highest_votes = item_info[0][VOTEINFO_ITEM_VOTES];
        int required_percent = 50;
        int required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100);

        if (highest_votes == item_info[1][VOTEINFO_ITEM_VOTES])
        {
            g_bHasVoteStarted = false;

            ArrayList mapList = new ArrayList(ByteCountToCells(128));

            for(int i = 0; i < num_items; i++)
            {
                if (item_info[i][VOTEINFO_ITEM_VOTES] == highest_votes)
                {
                    char map[128];
                    GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, 128);
                    mapList.PushString(map);
                }
                else
                    break;
            }

            tChatAll("%t", "mcr tier", mapList.Length);
            SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_MapChange), mapList);
            return;
        }
        else if (highest_votes < required_votes)
        {
            g_bHasVoteStarted = false;

            ArrayList mapList = new ArrayList(ByteCountToCells(128));

            char map[128];
            GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, 128);

            mapList.PushString(map);

            for(int i = 1; i < num_items; i++)
            {
                if (mapList.Length < 2 || item_info[i][VOTEINFO_ITEM_VOTES] == item_info[i - 1][VOTEINFO_ITEM_VOTES])
                {
                    GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, 128);
                    mapList.PushString(map);
                }
                else
                    break;
            }
            tChatAll("%t", "mcr runoff", required_percent);
            SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_MapChange), mapList);
            return;
        }
    }

    Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
}

public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2)
{
    switch(action)
    {
        case MenuAction_End:
        {
            g_hVoteMenu = null;
            delete menu;
        }
        case MenuAction_Display:
        {
            char text[32];
            FormatEx(text, 32, "%T \n ", "vote item title", param1);
            SetPanelTitle(view_as<Handle>(param2), text);
        }
        case MenuAction_DisplayItem:
        {
            char map[128];
            char buffer[128];

            menu.GetItem(param2, map, 128);

            if (StrEqual(map, VOTE_EXTEND, false))
                FormatEx(buffer, 128, "%T", "vote item extend", param1);
            else if (StrEqual(map, VOTE_DONTCHANGE, false))
                FormatEx(buffer, 128, "%T", "vote item dont change", param1);
            else if (StrEqual(map, LINE_ONE, false))
                FormatEx(buffer, 128, "%T", "LINE_ONE", param1);
            else if (StrEqual(map, LINE_TWO, false))
                FormatEx(buffer, 128, "%T", "LINE_TWO", param1);

            if (buffer[0] != '\0')
                return RedrawMenuItem(buffer);
        }
        case MenuAction_VoteCancel:
        {
            if (param1 == VoteCancel_NoVotes)
            {
                int count = GetMenuItemCount(menu);

                int item;
                char map[128];

                do
                {
                    int startInt = 0;
                    if (g_bBlockedSlots)
                        startInt = 2;
                    item = RandomInt(startInt, count - 1);
                    menu.GetItem(item, map, 128);
                }
                while(strcmp(map, VOTE_EXTEND, false) == 0);

                SetNextMap(map);
                g_bMapVoteCompleted = true;
            }
            g_bHasVoteStarted = false;
        }
    }

    return 0;
}

public Action Timer_ChangeMap(Handle timer)
{
    g_bChangeMapInProgress = false;

    char map[128];

    if (!GetNextMap(map, 128))
    {
        ThrowError("Timer_ChangeMap -> !GetNextMap");
        return Plugin_Stop;
    }

    LogMessage("Timer_ChangeMap -> ForceChangeLevel -> %s", map);
    ForceChangeLevel(map, "Map Vote");

    return Plugin_Stop;
}

bool RemoveStringFromArray(ArrayList array, const char[] str)
{
    int index = array.FindString(str);
    if (index != -1)
    {
        array.Erase(index);
        return true;
    }

    return false;
}

void CreateNextVote()
{
    g_aNextMapList.Clear();

    ArrayList tempMaps = g_aMapList.Clone();

    char map[128];
    GetCurrentMap(map, 128);
    RemoveStringFromArray(tempMaps, map);

    for(int x = 0; x < tempMaps.Length; ++x)
    {
        tempMaps.GetString(x, map, 128);
        // we remove big maps( >150 will broken fastdl .bz2), nice map, and only nominations, in cooldown, is not in certain times, requires min players.
        if (IsBigMap(map) || IsNominateOnly(map) || IsAdminOnly(map) || IsVIPOnly(map) || GetCooldown(map) > 0 || !IsCertainTimes(map) || GetMinPlayers(map) > 0)
        {
            RemoveStringFromArray(tempMaps, map);
            if (x > 0) x--;
        }
    }

    int players = GetRealPlayers();
    for(int x = 0; x < tempMaps.Length; ++x)
    {
        tempMaps.GetString(x, map, 128);
        // we remove map if player amount not match with configs
        int max = GetMaxPlayers(map);
        int min = GetMinPlayers(map);
        if ((min != 0 && players < min) || (max != 0 && players > max))
        {
            RemoveStringFromArray(tempMaps, map);
            if (x > 0) x--;
        }
    }

    int limit = (5 < tempMaps.Length ? 5 : tempMaps.Length);

    for(int i = 0; i < limit; i++)
    {
        int b = RandomInt(0, tempMaps.Length - 1);
        tempMaps.GetString(b, map, 128);
        g_aNextMapList.PushString(map);
        tempMaps.Erase(b);
    }

    delete tempMaps;
}

bool CanVoteStart()
{
    if (g_bWaitingForVote || g_bHasVoteStarted)
        return false;

    return true;
}

NominateResult InternalNominateMap(const char[] map, bool force, int owner, bool partyblock)
{
    if (!IsMapValid(map))
        return NominateResult_InvalidMap;

    if (!Call_OnNominateMap(map, owner))
    {
        // rejected
        return NominateResult_Reject;
    }

    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (strcmp(n.m_Map, map) == 0)
            return NominateResult_AlreadyInVote;
    }

    if (g_aNominations.Length >= 5 && !force)
        return NominateResult_VoteFull;

    if (IsVIPOnly(map) && !IsClientVIP(owner))
        return NominateResult_VIPOnly;

    if (IsAdminOnly(map) && !IsClientAdmin(owner))
        return NominateResult_AdminOnly;

    if (GetCooldown(map) > 0)
        return NominateResult_RecentlyPlayed;

    if (!partyblock && !IsCertainTimes(map))
        return NominateResult_CertainTimes;

    int max = GetMaxPlayers(map);
    if (max != 0 && GetRealPlayers() > max)
        return NominateResult_MaxPlayers;

    int min = GetMinPlayers(map);
    if (min != 0 && GetRealPlayers() < min)
        return NominateResult_MinPlayers;

    if (g_pStore && Store_GetClientCredits(owner) < GetPrice(map))
        return NominateResult_NoCredits;

    if (g_pShop && MG_Shop_GetClientMoney(owner) < GetPrice(map))
        return NominateResult_NoCredits;

    if (g_bPartyblock)
        return NominateResult_PartyBlock;

    if (partyblock && owner)
    {
        if (!g_ConVars.BCState.BoolValue)
        {
            // ?
            return NominateResult_PartyBlockDisabled;
        }

        int price = GetPrice(map, false, true); Nominations n;
        if (!Call_OnNominatePrice(map, owner, price))
        {
            // block
            return NominateResult_NoCredits;
        }

        for (int i = 0; i < g_aNominations.Length; i++)
        {
            g_aNominations.GetArray(i, n, sizeof(Nominations));
            price += n.m_Price;
            PrintToServer("Foreach [%s] niminations list.", n.m_Map);
        }

        if (g_pStore && Store_GetClientCredits(owner) < price)
            return NominateResult_NoCredits;

        if (g_pShop && MG_Shop_GetClientMoney(owner) < price)
            return NominateResult_NoCredits;

        while(g_aNominations.Length > 0)
        {
            g_aNominations.GetArray(0, n, sizeof(Nominations));
            g_aNominations.Erase(0);
            Call_NominationsReset(n.m_Map, n.m_Owner, false);
            PrintToServer("Erase [%s] niminations list.", n.m_Map);

            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(n.m_Owner, n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }

            LogMessage("%L was remove from nominations list with map [%s]", n.m_Owner, n.m_Map);
        }

        if (g_pStore)
        {
            Store_SetClientCredits(owner, Store_GetClientCredits(owner)-price, "nomination-partyblock");
            Chat(owner, "%T", "nominate partyblock cost", owner, map, price);
        }
        else if (g_pShop)
        {
            MG_Shop_ClientCostMoney(owner, price, "nomination-partyblock");
            Chat(owner, "%T", "nominate partyblock cost", owner, map, price);
        }

        n.m_Owner = owner;
        strcopy(n.m_Map, 128, map);
        GetClientName(owner, n.m_OwnerName, 32);
        GetClientAuthId(owner, AuthId_Steam2, n.m_OwnerAuth, 32);
        g_aNominations.PushArray(n, sizeof(Nominations));
        g_bPartyblock = true;
        return NominateResult_PartyBlockAdded;
    }

    if (owner)
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));

        if (n.m_Owner == owner)
        {
            Call_NominationsReset(n.m_Map, n.m_Owner, false);

            int credits = GetPrice(map);
            if (!Call_OnNominatePrice(map, owner, credits))
            {
                // block
                return NominateResult_NoCredits;
            }

            if (g_pStore)
            {
                Store_SetClientCredits(owner, Store_GetClientCredits(owner)+n.m_Price-credits, "nomination-replace");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
                Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(owner, n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
                MG_Shop_ClientCostMoney(owner, credits, "nomination-nominate");
                Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
            }

            strcopy(n.m_Map, 128, map);
            g_aNominations.SetArray(i, n, sizeof(Nominations));

            return NominateResult_Replaced;
        }
    }

    while(g_aNominations.Length > 5)
    {
        Nominations n;
        g_aNominations.GetArray(0, n, sizeof(Nominations));
        Call_NominationsReset(n.m_Map, n.m_Owner, false);
        g_aNominations.Erase(0);
    }

    int credits = GetPrice(map);
    if (!Call_OnNominatePrice(map, owner, credits))
    {
        // block
        return NominateResult_NoCredits;
    }

    if (ClientIsValid(owner) && credits > 0)
    if (g_pStore)
    {

        Store_SetClientCredits(owner, Store_GetClientCredits(owner)-credits, "nomination-nominate");
        Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
    }
    else if (g_pShop)
    {
        MG_Shop_ClientCostMoney(owner, credits, "nomination-nominate");
        Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
    }

    Nominations n;
    n.m_Owner = owner;
    strcopy(n.m_Map, 128, map);
    GetClientName(owner, n.m_OwnerName, 32);
    GetClientAuthId(owner, AuthId_Steam2, n.m_OwnerAuth, 32);
    g_aNominations.PushArray(n, sizeof(Nominations));

    return NominateResult_Added;
}

bool InternalRemoveNominationByOwner(int owner)
{
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (n.m_Owner == owner)
        {
            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(owner, Store_GetClientCredits(owner)+n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(owner, n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
            }

            g_aNominations.Erase(i);
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            return true;
        }
    }

    return false;
}

bool InternalRemoveNominationByMap(const char[] map)
{
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (strcmp(map, n.m_Map) == 0)
        {
            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(n.m_Owner, n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }

            g_aNominations.Erase(i);
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            return true;
        }
    }

    return false;
}

void RefundAllCredits(const char[] map)
{
    while (g_aNominations.Length > 0)
    {
        Nominations n;
        g_aNominations.GetArray(0, n, sizeof(Nominations));

        g_aNominations.Erase(0);
        Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);

        if (strcmp(map, n.m_Map) == 0)
        {
            // skip passing map
            Call_NominationsVoted(n.m_Map, n.m_OwnerName, n.m_OwnerAuth);
            continue;
        }

        if (g_bPartyblock)
        {
            // skip all partyblocks
            continue;
        }

        int credits = GetRefundCredits(n.m_Map);

        if (credits > 0 && ClientIsValid(n.m_Owner))
        if (g_pStore)
        {
            Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+credits, "nomination-fallback");
            Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, credits);
        }
        else if (g_pShop)
        {
            MG_Shop_ClientEarnMoney(n.m_Owner, credits, "nomination-fallback");
            Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, credits);
        }
    }

    g_aNominations.Clear();
}

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <nextmap>
#include <smutils>
#include <mapchooser_redux>

// options
#undef REQUIRE_PLUGIN
#include <store>
#include <shop>
#include <fys.pupd>

Handle g_tVote;
Handle g_tRetry;
Handle g_tWarning;

Menu g_hVoteMenu;

ArrayList g_aMapList;
ArrayList g_aNextMapList;
ArrayList g_aNominations;

int g_iExtends;
int g_iMapFileSerial = -1;
int g_iNominateCount;
int g_iRunoffCount;
bool g_bPartyblock;
bool g_bAllowCountdown;
bool g_bHasVoteStarted;
bool g_bWaitingForVote;
bool g_bMapVoteCompleted;
bool g_bChangeMapInProgress;
bool g_bChangeMapAtRoundEnd;
bool g_bWarningInProgress;
bool g_bBlockedSlots;

bool g_pStore;
bool g_pShop;

enum TimerLocation
{
    TimerLocation_Hint = 0,
    TimerLocation_Text,
    TimerLocation_Chat,
    TimerLocation_HUD
}

enum WarningType
{
    WarningType_Vote,
    WarningType_Revote
}

enum struct Convars
{
    ConVar TimeLoc;
    ConVar NameTag;
    ConVar DescTag;
    ConVar MaxExts;
    ConVar Recents;
    ConVar LtpMtpl;
    ConVar BCState;
    ConVar Shuffle;
    ConVar Refunds;
    ConVar Require;
}

// cvars
Convars g_ConVars;

MapChange g_MapChange;

#include "mapchooser/cmds.sp"
#include "mapchooser/cvars.sp"
#include "mapchooser/data.sp"
#include "mapchooser/events.sp"
#include "mapchooser/natives.sp"
#include "mapchooser/stocks.sp"

public Plugin myinfo =
{
    name        = "MapChooser Redux",
    author      = "Kyle",
    description = "Automated Map Voting with Extensions",
    version     = MCR_VERSION,
    url         = "https://kxnrl.com"
};

public void OnPluginStart()
{
    SMUtils_SetChatPrefix("[\x02M\x04C\x0CR\x01]");
    SMUtils_SetChatSpaces("   ");
    SMUtils_SetChatConSnd(false);
    SMUtils_SetTextDest(HUD_PRINTCENTER);

    Cmds_OnPluginStart();
    Cvars_OnPluginStart();
    Data_OnPluginStart();
    Events_OnPluginStart();
    Natives_OnPluginStart();

    LoadTranslations("com.kxnrl.mcr.translations");

    g_aMapList     = new ArrayList(ByteCountToCells(128));
    g_aNextMapList = new ArrayList(ByteCountToCells(128));
    g_aNominations = new ArrayList(sizeof(Nominations));
}

public void OnAllPluginsLoaded()
{
    g_pStore = LibraryExists("store");
    g_pShop = LibraryExists("shop-core");

    Data_OnAllPluginsLoaded();
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public void OnConfigsExecuted()
{
    if (ReadMapList(g_aMapList, g_iMapFileSerial, "mapchooser", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) != null)
        if (g_iMapFileSerial == -1)
            SetFailState("Unable to create a valid map list.");

    g_iExtends = 0;
    g_bPartyblock = false;
    g_bAllowCountdown = false;
    g_bMapVoteCompleted = false;
    g_bChangeMapAtRoundEnd = false;
    g_iNominateCount = 0;

    g_aNominations.Clear();

    CreateNextVote();
    SetupTimeleftTimer();
    Call_MapDataLoaded();
    Call_MapVotePoolChanged();
}

public void OnMapEnd()
{
    g_bHasVoteStarted = false;
    g_bWaitingForVote = false;
    g_bChangeMapInProgress = false;

    g_tVote = null;
    g_tRetry = null;
    g_tWarning = null;
    g_iRunoffCount = 0;

    Data_OnMapEnd();
}

public void OnClientConnected(int client)
{
    if (GetClientCount(false) >= 15)
    {
        // allow countdown cooldown
        if (!g_bAllowCountdown)
        {
            char map[128];
            GetCurrentMap(map, 128);
            SetCooldown(map);
            SetLastPlayed(map);
            g_bAllowCountdown = true;
        }
    }
}

public void OnClientDisconnect(int client)
{
    if (!IsClientInGame(client))
        return;

    for (int index = 0; index < g_aNominations.Length; index++)
    {
        Nominations n;
        g_aNominations.GetArray(index, n, sizeof(Nominations));
        if (n.m_Owner == client)
        {
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            g_aNominations.Erase(index);

            LogMessage("Removed [%s] by %L from nomination list.", n.m_Map, n.m_Owner);

            // party block disconnect
            if (g_bPartyblock)
                g_bPartyblock = false;

            break;
        }
    }
}

public void OnMapTimeLeftChanged()
{
    SetupTimeleftTimer();
}

void SetupTimeleftTimer()
{
    if (g_bMapVoteCompleted)
    {
        LogMessage("Map vote had been completed.");
        return;
    }

    int timeLeft;
    if (!GetMapTimeLeft(timeLeft) || timeLeft <= 0)
    {
        LogMessage("Failed to GetMapTimeLeft()");
        return;
    }

    if (timeLeft - 300 < 0 && !g_bHasVoteStarted)
    {
        SetupWarningTimer(WarningType_Vote);
        return;
    }

    if (g_aMapList.Length <= 0)
    {
        LogError("No enough maps to start the vote.");
        return;
    }

    if (g_tWarning == null)
    {
        if (g_tVote != null)
            KillTimer(g_tVote);

        g_tVote = CreateTimer(float(timeLeft - 300), Timer_StartWarningTimer, _, TIMER_FLAG_NO_MAPCHANGE);
    }
}

public Action Timer_StartWarningTimer(Handle timer)
{
    g_tVote = null;

    if (!g_bWarningInProgress || g_tWarning == null)
        SetupWarningTimer(WarningType_Vote);

    return Plugin_Stop;
}

public Action Timer_StartMapVote(Handle timer, DataPack data)
{
    static int timePassed;

    if (!g_aMapList.Length || g_bMapVoteCompleted || g_bHasVoteStarted)
    {
        g_tWarning = null;
        return Plugin_Stop;
    }

    data.Reset();
    int warningMaxTime = data.ReadCell();
    int warningTimeRemaining = warningMaxTime - timePassed;

    switch(view_as<TimerLocation>(g_ConVars.TimeLoc.IntValue))
    {
        case TimerLocation_Text: tTextAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown text hint shuffle" : "mcr countdown text hint", warningTimeRemaining);
        case TimerLocation_Chat: tChatAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown chat shuffle"      : "mcr countdown chat",      warningTimeRemaining);
        case TimerLocation_Hint: tHintAll("%t", g_ConVars.Shuffle.BoolValue ? "mcr countdown text hint shuffle" : "mcr countdown text hint", warningTimeRemaining);
        case TimerLocation_HUD:  DisplayCountdownHUD(warningTimeRemaining);
    }

    if (timePassed++ >= warningMaxTime)
    {
        if (timer == g_tRetry)
        {
            g_bWaitingForVote = false;
            g_tRetry = null;
        }
        else
        {
            g_tWarning = null;
        }

        timePassed = 0;
        MapChange mapChange = view_as<MapChange>(data.ReadCell());
        ArrayList arraylist = view_as<ArrayList>(data.ReadCell());

        InitiateVote(mapChange, arraylist);

        return Plugin_Stop;
    }

    return Plugin_Continue;
}

public Action Command_Mapvote(int client, int args)
{
    tChatAll("%t", "mcr voting started");

    SetupWarningTimer(WarningType_Vote, MapChange_MapEnd, null, true);

    LogAction(client, -1, "%L -> called mapvote.", client);

    return Plugin_Handled;
}

void InitiateVote(MapChange when, ArrayList inputlist)
{
    g_bWaitingForVote = true;
    g_bWarningInProgress = false;

    if (IsVoteInProgress())
    {
        LogMessage("IsVoteInProgress -> %d", FAILURE_TIMER_LENGTH);

        DataPack data = new DataPack();
        data.WriteCell(FAILURE_TIMER_LENGTH);
        data.WriteCell(when);
        data.WriteCell(inputlist);
        data.Reset();

        g_tRetry = CreateTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT|TIMER_DATA_HNDL_CLOSE);

        return;
    }

    if (g_bMapVoteCompleted && g_bChangeMapInProgress)
        return;

    SetHudTextParams(-1.0, 0.32, 3.5, 0, 255, 255, 255, 0, 0.3, 0.3, 0.3);
    for(int client = 1; client <= MaxClients; ++client)
        if (IsClientInGame(client) && !IsFakeClient(client))
            ShowHudText(client, 0, "%T", "mcr voting started", client);

    g_MapChange = when;

    g_bWaitingForVote = false;
    g_bHasVoteStarted = true;

    Handle menuStyle = GetMenuStyleHandle(MenuStyle_Default);

    if (menuStyle != INVALID_HANDLE)
        g_hVoteMenu = CreateMenuEx(menuStyle, Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel);
    else
        g_hVoteMenu = new Menu(Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel);

    Handle radioStyle = GetMenuStyleHandle(MenuStyle_Radio);

    if (GetMenuStyle(g_hVoteMenu) == radioStyle)
    {
        g_bBlockedSlots = true;
        g_hVoteMenu.AddItem(LINE_ONE, "Choose something...", ITEMDRAW_DISABLED);
        g_hVoteMenu.AddItem(LINE_TWO, "...will ya?", ITEMDRAW_DISABLED);
    }
    else
        g_bBlockedSlots = false;

    g_hVoteMenu.OptionFlags = MENUFLAG_BUTTON_NOVOTE;

    g_hVoteMenu.SetTitle("选择下一张地图\n ");
    g_hVoteMenu.VoteResultCallback = Handler_MapVoteFinished;

    if (g_bPartyblock)
    {
        for(int i = 0; i < 5; i++)
        {
            Nominations n;
            g_aNominations.GetArray(0, n, sizeof(Nominations));
            AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner, i == 0 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
        }

        //AddExtendToMenu(g_hVoteMenu, when);
    }
    else if (inputlist == null)
    {
        char map[128];
        int voteSize = 5, nominationsToAdd = g_aNominations.Length >= voteSize ? voteSize : g_aNominations.Length;

        static ArrayList votePool = null;
        if (votePool != null)
            delete votePool;
        votePool = new ArrayList(sizeof(Nominations));

        if (g_ConVars.Shuffle.BoolValue && g_aNominations.Length >= g_ConVars.Require.IntValue)
        {
            // randomly pool
            for(int i = 0; i < nominationsToAdd; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                votePool.PushArray(n, sizeof(Nominations));
                RemoveStringFromArray(g_aNextMapList, n.m_Map);
            }
            /*
            for(int i = 0; i < g_aNominations.Length; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }
            */
            if (votePool.Length < voteSize && g_aNextMapList.Length == 0)
            {
                if (votePool.Length == 0)
                {
                    LogMessage("No maps available for vote.");
                    return;
                }
                else
                {
                    LogMessage("Not enough maps to fill map list.");
                    voteSize = votePool.Length;
                }
            }
            int count = 0;
            while(votePool.Length < voteSize && count < g_aNextMapList.Length)
            {
                g_aNextMapList.GetString(count, map, 128);
                count++;

                Nominations n;
                strcopy(n.m_Map, 128, map);
                n.m_Owner = -1;
                votePool.PushArray(n, sizeof(Nominations));
            }
            // Randomly menu
            while (votePool.Length > 0)
            {
                Nominations n;
                int i = RandomInt(0, votePool.Length - 1);
                votePool.GetArray(i, n, sizeof(Nominations));
                votePool.Erase(i);
                AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner);
            }
        }
        else
        {
            for(int i = 0; i < nominationsToAdd; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));

                AddMapItem(g_hVoteMenu, n.m_Map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue, n.m_Owner);
                RemoveStringFromArray(g_aNextMapList, map);

                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }

            /*
            for(int i = nominationsToAdd; i < g_aNominations.Length; i++)
            {
                Nominations n;
                g_aNominations.GetArray(i, n, sizeof(Nominations));
                Call_NominationsReset(n.m_Map, n.m_Owner, false);
            }
            */

            int i = nominationsToAdd;
            int count = 0;

            if (i < voteSize && g_aNextMapList.Length == 0)
            {
                if (i == 0)
                {
                    LogMessage("No maps available for vote.");
                    return;
                }
                else
                {
                    LogMessage("Not enough maps to fill map list.");
                    voteSize = i;
                }
            }

            while(i < voteSize && count < g_aNextMapList.Length)
            {
                g_aNextMapList.GetString(count, map, 128);
                count++;

                AddMapItem(g_hVoteMenu, map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue);
                i++;
            }
        }

        //g_aNominations.Clear();

        AddExtendToMenu(g_hVoteMenu, when);
    }
    else
    {
        char map[128];
        for(int i = 0; i < inputlist.Length; i++)
        {
            inputlist.GetString(i, map, 128);

            if (IsMapValid(map))
                AddMapItem(g_hVoteMenu, map, g_ConVars.NameTag.BoolValue, !g_ConVars.DescTag.BoolValue);
            else if (StrEqual(map, VOTE_DONTCHANGE))
                g_hVoteMenu.AddItem(VOTE_DONTCHANGE, "Don't Change");
            else if (StrEqual(map, VOTE_EXTEND))
                g_hVoteMenu.AddItem(VOTE_EXTEND, "Extend Map");
        }
        delete inputlist;
    }

    if (5 <= GetMaxPageItems(GetMenuStyle(g_hVoteMenu)))
        g_hVoteMenu.Pagination = MENU_NO_PAGINATION;

    g_hVoteMenu.DisplayVoteToAll(15);

    Call_MapVoteStarted();

    LogAction(-1, -1, "Voting for next map has started.");
    tChatAll("%t", "mcr voting started");
}

public void Handler_VoteFinishedGeneric(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{
    char map[128];
    GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, 128);

    Call_MapVoteEnd(map);

    if (strcmp(map, VOTE_EXTEND, false) == 0)
    {
        g_iExtends++;

        int timeLimit;
        if (GetMapTimeLimit(timeLimit))
            if (timeLimit > 0)
                ExtendMapTimeLimit(1200);

        tChatAll("%t", "mcr extend map", item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");

        g_bHasVoteStarted = false;
        CreateNextVote();
        SetupTimeleftTimer();
    }
    else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
    {
        tChatAll("%t", "mcr dont change", item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");

        g_bHasVoteStarted = false;
        CreateNextVote();
        SetupTimeleftTimer();
    }
    else
    {
        if (g_MapChange == MapChange_Instant)
        {
            g_bChangeMapInProgress = true;
            CreateTimer(10.0 , Timer_ChangeMaprtv, _, TIMER_FLAG_NO_MAPCHANGE);
        }
        else if (g_MapChange == MapChange_RoundEnd)
        {
            g_bChangeMapAtRoundEnd = true;
            SetConVarInt(FindConVar("mp_timelimit"), 1);
        }

        FindConVar("nextlevel").SetString(map);
        SetNextMap(map);

        g_bHasVoteStarted = false;
        g_bMapVoteCompleted = true;

        tChatAll("%t", "mcr next map", map, item_info[0][VOTEINFO_ITEM_VOTES], num_votes);
        if (g_ConVars.DescTag.BoolValue)
        {
            char desc[128];
            GetDescEx(map, desc, 128, false, false);
            ChatAll("\x0A -> \x0E[\x05%s\x0E]", desc);
        }
        LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
    }

    // refunds
    RefundAllCredits(map);

    // reset
    g_bPartyblock = false;
}

public Action Timer_ChangeMaprtv(Handle hTimer)
{
    FindConVar("mp_halftime").SetInt(0);
    FindConVar("mp_timelimit").SetInt(0);
    FindConVar("mp_maxrounds").SetInt(0);
    FindConVar("mp_roundtime").SetInt(1);

    for(int client = 1; client <= MaxClients; ++client)
    if (IsClientInGame(client))
    if (IsPlayerAlive(client))
    ForcePlayerSuicide(client);

    CreateTimer(60.0, Timer_ChangeMap, 0, TIMER_FLAG_NO_MAPCHANGE);

    return Plugin_Stop;
}

public void Handler_MapVoteFinished(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{
    if (num_items > 1 && g_iRunoffCount < 1)
    {
        g_iRunoffCount++;
        int highest_votes = item_info[0][VOTEINFO_ITEM_VOTES];
        int required_percent = 50;
        int required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100);

        if (highest_votes == item_info[1][VOTEINFO_ITEM_VOTES])
        {
            g_bHasVoteStarted = false;

            ArrayList mapList = new ArrayList(ByteCountToCells(128));

            for(int i = 0; i < num_items; i++)
            {
                if (item_info[i][VOTEINFO_ITEM_VOTES] == highest_votes)
                {
                    char map[128];
                    GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, 128);
                    mapList.PushString(map);
                }
                else
                    break;
            }

            tChatAll("%t", "mcr tier", mapList.Length);
            SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_MapChange), mapList);
            return;
        }
        else if (highest_votes < required_votes)
        {
            g_bHasVoteStarted = false;

            ArrayList mapList = new ArrayList(ByteCountToCells(128));

            char map[128];
            GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, 128);

            mapList.PushString(map);

            for(int i = 1; i < num_items; i++)
            {
                if (mapList.Length < 2 || item_info[i][VOTEINFO_ITEM_VOTES] == item_info[i - 1][VOTEINFO_ITEM_VOTES])
                {
                    GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, 128);
                    mapList.PushString(map);
                }
                else
                    break;
            }
            tChatAll("%t", "mcr runoff", required_percent);
            SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_MapChange), mapList);
            return;
        }
    }

    Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
}

public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2)
{
    switch(action)
    {
        case MenuAction_End:
        {
            g_hVoteMenu = null;
            delete menu;
        }
        case MenuAction_Display:
        {
            char text[32];
            FormatEx(text, 32, "%T \n ", "vote item title", param1);
            SetPanelTitle(view_as<Handle>(param2), text);
        }
        case MenuAction_DisplayItem:
        {
            char map[128];
            char buffer[128];

            menu.GetItem(param2, map, 128);

            if (StrEqual(map, VOTE_EXTEND, false))
                FormatEx(buffer, 128, "%T", "vote item extend", param1);
            else if (StrEqual(map, VOTE_DONTCHANGE, false))
                FormatEx(buffer, 128, "%T", "vote item dont change", param1);
            else if (StrEqual(map, LINE_ONE, false))
                FormatEx(buffer, 128, "%T", "LINE_ONE", param1);
            else if (StrEqual(map, LINE_TWO, false))
                FormatEx(buffer, 128, "%T", "LINE_TWO", param1);

            if (buffer[0] != '\0')
                return RedrawMenuItem(buffer);
        }
        case MenuAction_VoteCancel:
        {
            if (param1 == VoteCancel_NoVotes)
            {
                int count = GetMenuItemCount(menu);

                int item;
                char map[128];

                do
                {
                    int startInt = 0;
                    if (g_bBlockedSlots)
                        startInt = 2;
                    item = RandomInt(startInt, count - 1);
                    menu.GetItem(item, map, 128);
                }
                while(strcmp(map, VOTE_EXTEND, false) == 0);

                SetNextMap(map);
                g_bMapVoteCompleted = true;
            }
            g_bHasVoteStarted = false;
        }
    }

    return 0;
}

public Action Timer_ChangeMap(Handle timer)
{
    g_bChangeMapInProgress = false;

    char map[128];

    if (!GetNextMap(map, 128))
    {
        ThrowError("Timer_ChangeMap -> !GetNextMap");
        return Plugin_Stop;
    }

    LogMessage("Timer_ChangeMap -> ForceChangeLevel -> %s", map);
    ForceChangeLevel(map, "Map Vote");

    return Plugin_Stop;
}

bool RemoveStringFromArray(ArrayList array, const char[] str)
{
    int index = array.FindString(str);
    if (index != -1)
    {
        array.Erase(index);
        return true;
    }

    return false;
}

void CreateNextVote()
{
    g_aNextMapList.Clear();

    ArrayList tempMaps = g_aMapList.Clone();

    char map[128];
    GetCurrentMap(map, 128);
    RemoveStringFromArray(tempMaps, map);

    for(int x = 0; x < tempMaps.Length; ++x)
    {
        tempMaps.GetString(x, map, 128);
        // we remove big maps( >150 will broken fastdl .bz2), nice map, and only nominations, in cooldown, is not in certain times, requires min players.
        if (IsBigMap(map) || IsNominateOnly(map) || IsAdminOnly(map) || IsVIPOnly(map) || GetCooldown(map) > 0 || !IsCertainTimes(map) || GetMinPlayers(map) > 0)
        {
            RemoveStringFromArray(tempMaps, map);
            if (x > 0) x--;
        }
    }

    int players = GetRealPlayers();
    for(int x = 0; x < tempMaps.Length; ++x)
    {
        tempMaps.GetString(x, map, 128);
        // we remove map if player amount not match with configs
        int max = GetMaxPlayers(map);
        int min = GetMinPlayers(map);
        if ((min != 0 && players < min) || (max != 0 && players > max))
        {
            RemoveStringFromArray(tempMaps, map);
            if (x > 0) x--;
        }
    }

    int limit = (5 < tempMaps.Length ? 5 : tempMaps.Length);

    for(int i = 0; i < limit; i++)
    {
        int b = RandomInt(0, tempMaps.Length - 1);
        tempMaps.GetString(b, map, 128);
        g_aNextMapList.PushString(map);
        tempMaps.Erase(b);
    }

    delete tempMaps;
}

bool CanVoteStart()
{
    if (g_bWaitingForVote || g_bHasVoteStarted)
        return false;

    return true;
}

NominateResult InternalNominateMap(const char[] map, bool force, int owner, bool partyblock)
{
    if (!IsMapValid(map))
        return NominateResult_InvalidMap;

    if (!Call_OnNominateMap(map, owner))
    {
        // rejected
        return NominateResult_Reject;
    }

    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (strcmp(n.m_Map, map) == 0)
            return NominateResult_AlreadyInVote;
    }

    if (g_aNominations.Length >= 5 && !force)
        return NominateResult_VoteFull;

    if (IsVIPOnly(map) && !IsClientVIP(owner))
        return NominateResult_VIPOnly;

    if (IsAdminOnly(map) && !IsClientAdmin(owner))
        return NominateResult_AdminOnly;

    if (GetCooldown(map) > 0)
        return NominateResult_RecentlyPlayed;

    if (!partyblock && !IsCertainTimes(map))
        return NominateResult_CertainTimes;

    int max = GetMaxPlayers(map);
    if (max != 0 && GetRealPlayers() > max)
        return NominateResult_MaxPlayers;

    int min = GetMinPlayers(map);
    if (min != 0 && GetRealPlayers() < min)
        return NominateResult_MinPlayers;

    if (g_pStore && Store_GetClientCredits(owner) < GetPrice(map))
        return NominateResult_NoCredits;

    if (g_pShop && MG_Shop_GetClientMoney(owner) < GetPrice(map))
        return NominateResult_NoCredits;

    if (g_bPartyblock)
        return NominateResult_PartyBlock;

    if (partyblock && owner)
    {
        if (!g_ConVars.BCState.BoolValue)
        {
            // ?
            return NominateResult_PartyBlockDisabled;
        }

        int price = GetPrice(map, false, true); Nominations n;
        if (!Call_OnNominatePrice(map, client, price))
        {
            // block
            return NominateResult_NoCredits;
        }

        for (int i = 0; i < g_aNominations.Length; i++)
        {
            g_aNominations.GetArray(i, n, sizeof(Nominations));
            price += n.m_Price;
            PrintToServer("Foreach [%s] niminations list.", n.m_Map);
        }

        if (g_pStore && Store_GetClientCredits(owner) < price)
            return NominateResult_NoCredits;

        if (g_pShop && MG_Shop_GetClientMoney(owner) < price)
            return NominateResult_NoCredits;

        while(g_aNominations.Length > 0)
        {
            g_aNominations.GetArray(0, n, sizeof(Nominations));
            g_aNominations.Erase(0);
            Call_NominationsReset(n.m_Map, n.m_Owner, false);
            PrintToServer("Erase [%s] niminations list.", n.m_Map);

            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(n.m_Owner, n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }

            LogMessage("%L was remove from nominations list with map [%s]", n.m_Owner, n.m_Map);
        }

        if (g_pStore)
        {
            Store_SetClientCredits(owner, Store_GetClientCredits(owner)-price, "nomination-partyblock");
            Chat(owner, "%T", "nominate partyblock cost", owner, map, price);
        }
        else if (g_pShop)
        {
            MG_Shop_ClientCostMoney(owner, price, "nomination-partyblock");
            Chat(owner, "%T", "nominate partyblock cost", owner, map, price);
        }

        n.m_Owner = owner;
        strcopy(n.m_Map, 128, map);
        GetClientName(owner, n.m_OwnerName, 32);
        GetClientAuthId(owner, AuthId_Steam2, n.m_OwnerAuth, 32);
        g_aNominations.PushArray(n, sizeof(Nominations));
        g_bPartyblock = true;
        return NominateResult_PartyBlockAdded;
    }

    if (owner)
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));

        if (n.m_Owner == owner)
        {
            Call_NominationsReset(n.m_Map, n.m_Owner, false);

            int credits = GetPrice(map);
            if (!Call_OnNominatePrice(map, owner, credits))
            {
                // block
                return NominateResult_NoCredits;
            }

            if (g_pStore)
            {
                Store_SetClientCredits(owner, Store_GetClientCredits(owner)+n.m_Price-credits, "nomination-replace");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
                Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(owner, n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
                MG_Shop_ClientCostMoney(owner, credits, "nomination-nominate");
                Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
            }

            strcopy(n.m_Map, 128, map);
            g_aNominations.SetArray(i, n, sizeof(Nominations));

            return NominateResult_Replaced;
        }
    }

    while(g_aNominations.Length > 5)
    {
        Nominations n;
        g_aNominations.GetArray(0, n, sizeof(Nominations));
        Call_NominationsReset(n.m_Map, n.m_Owner, false);
        g_aNominations.Erase(0);
    }

    int credits = GetPrice(map);
    if (!Call_OnNominatePrice(map, owner, credits))
    {
        // block
        return NominateResult_NoCredits;
    }

    if (ClientIsValid(owner) && credits > 0)
    if (g_pStore)
    {

        Store_SetClientCredits(owner, Store_GetClientCredits(owner)-credits, "nomination-nominate");
        Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
    }
    else if (g_pShop)
    {
        MG_Shop_ClientCostMoney(owner, credits, "nomination-nominate");
        Chat(owner, "%T", "nominate nominate cost", owner, map, credits);
    }

    Nominations n;
    n.m_Owner = owner;
    strcopy(n.m_Map, 128, map);
    GetClientName(owner, n.m_OwnerName, 32);
    GetClientAuthId(owner, AuthId_Steam2, n.m_OwnerAuth, 32);
    g_aNominations.PushArray(n, sizeof(Nominations));

    return NominateResult_Added;
}

bool InternalRemoveNominationByOwner(int owner)
{
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (n.m_Owner == owner)
        {
            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(owner, Store_GetClientCredits(owner)+n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(owner, n.m_Price, "nomination-fallback");
                Chat(owner, "%T", "mcr nominate fallback", owner, n.m_Map, n.m_Price);
            }

            g_aNominations.Erase(i);
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            return true;
        }
    }

    return false;
}

bool InternalRemoveNominationByMap(const char[] map)
{
    for (int i = 0; i < g_aNominations.Length; i++)
    {
        Nominations n;
        g_aNominations.GetArray(i, n, sizeof(Nominations));
        if (strcmp(map, n.m_Map) == 0)
        {
            if (ClientIsValid(n.m_Owner) && n.m_Price > 0)
            if (g_pStore)
            {
                Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }
            else if (g_pShop)
            {
                MG_Shop_ClientEarnMoney(n.m_Owner, n.m_Price, "nomination-fallback");
                Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, n.m_Price);
            }

            g_aNominations.Erase(i);
            Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);
            return true;
        }
    }

    return false;
}

void RefundAllCredits(const char[] map)
{
    while (g_aNominations.Length > 0)
    {
        Nominations n;
        g_aNominations.GetArray(0, n, sizeof(Nominations));

        g_aNominations.Erase(0);
        Call_NominationsReset(n.m_Map, n.m_Owner, g_bPartyblock);

        if (strcmp(map, n.m_Map) == 0)
        {
            // skip passing map
            Call_NominationsVoted(n.m_Map, n.m_OwnerName, n.m_OwnerAuth);
            continue;
        }

        if (g_bPartyblock)
        {
            // skip all partyblocks
            continue;
        }

        int credits = GetRefundCredits(n.m_Map);

        if (credits > 0 && ClientIsValid(n.m_Owner))
        if (g_pStore)
        {
            Store_SetClientCredits(n.m_Owner, Store_GetClientCredits(n.m_Owner)+credits, "nomination-fallback");
            Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, credits);
        }
        else if (g_pShop)
        {
            MG_Shop_ClientEarnMoney(n.m_Owner, credits, "nomination-fallback");
            Chat(n.m_Owner, "%T", "mcr nominate fallback", n.m_Owner, n.m_Map, credits);
        }
    }

    g_aNominations.Clear();
}


SMJansson support

SMJansson support

/**
 * vim: set ts=4 :
 * =============================================================================
 * map_votes
 * Interact with the Map Votes web api - https://github.com/CrimsonTautology/map_votes
 *
 * Copyright 2013 The Crimson Tautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <socket>
#include <base64>

#undef REQUIRE_EXTENSIONS
#include <smjansson>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo = {
    name = "MapVotes",
    author = "CrimsonTautology",
    description = "Interact with the Map Votes web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_map_votes"
};

#define CAST_VOTE_ROUTE "/v1/api/cast_vote"
#define WRITE_MESSAGE_ROUTE "/v1/api/write_message"
#define SERVER_QUERY_ROUTE "/v1/api/server_query"
#define MAPS_ROUTE "/maps"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18

new Handle:g_Cvar_MapVotesUrl = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesPort = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesApiKey = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesVotingEnabled = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesCommentingEnabled = INVALID_HANDLE;

new bool:g_JanssonEnabled = false;

public OnPluginStart()
{

    g_Cvar_MapVotesUrl = CreateConVar("sm_map_votes_url", "", "URL to your Map Votes web page");
    g_Cvar_MapVotesPort = CreateConVar("sm_map_votes_port", "80", "HTTP Port used");
    g_Cvar_MapVotesApiKey = CreateConVar("sm_map_votes_api_key", "", "The API key you generated to interact with the Map Votes web page");
    g_Cvar_MapVotesVotingEnabled = CreateConVar("sm_map_votes_voting_enabled", "1", "Whether players are allowed to vote on the current map");
    g_Cvar_MapVotesCommentingEnabled = CreateConVar("sm_map_votes_commenting_enabled", "1", "Whether players are allowed to comment on the current map");

    RegConsoleCmd("sm_vote_menu", Command_VoteMenu, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_up", Command_VoteUp, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_down", Command_VoteDown, "Vote that you hate the current map");
    RegConsoleCmd("sm_map_comment", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_mc", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_view_map", Command_ViewMap, "View the Map Votes web page for this map");
    RegConsoleCmd("sm_call_vote", Command_CallVote, "Popup a vote panel to every player on the server that has not yet voted on this map");

}

public OnAllPluginsLoaded() {
	if (LibraryExists("jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryAdded(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryRemoved(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = false;
	}
}

public Action:Command_VoteMenu(client, args)
{
    if(client && IsClientAuthorized(client)){
        CallVoteOnClient(client);
    }
}

public Action:Command_VoteUp(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, 1);
    }
}

public Action:Command_VoteDown(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, -1);
    }
}

public Action:Command_ViewMap(client, args)
{
    if(client && IsClientAuthorized(client)){
        ViewMap(client);
    }
}

public Action:Command_MapComment(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[MapVotes] Usage: !map_comment <comment>");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:comment[256];
        GetCmdArgString(comment, sizeof(comment));
        WriteMessage(client, comment);
    }

    return Plugin_Handled;
}

public Action:Command_CallVote(client, args)
{
    //TODO
}

public OnSocketConnected(Handle:socket, any:headers_pack)
{
    decl String:request_string[1024];

    ResetPack(headers_pack);
    ReadPackString(headers_pack, request_string, sizeof(request_string));

    SocketSend(socket, request_string);
}

public OnSocketReceive(Handle:socket, String:receive_data[], const data_size, any:headers_pack) {
    if(g_JanssonEnabled) {
        //TODO parse JSON response
    }
    //Used for data received back
    PrintToConsole(0,"%s", receive_data);//TODO
}

public OnSocketDisconnected(Handle:socket, any:headers_pack) {
    // Connection: close advises the webserver to close the connection when the transfer is finished
    // we're done here
    CloseHandle(headers_pack);
    CloseHandle(socket);
}

public OnSocketError(Handle:socket, const error_type, const error_num, any:headers_pack) {
    // a socket error occured
    if(error_type == EMPTY_HOST )
    {
        LogError("[MapVotes] Empty Host (errno %d)", error_num);
    } else if (error_type == NO_HOST )
    {
        LogError("[MapVotes] No Host (errno %d)", error_num);
    } else if (error_type == CONNECT_ERROR )
    {
        LogError("[MapVotes] Connection Error (errno %d)", error_num);
    } else if (error_type == SEND_ERROR )
    {
        LogError("[MapVotes] Send Error (errno %d)", error_num);
    } else if (error_type == BIND_ERROR )
    {
        LogError("[MapVotes] Bind Error (errno %d)", error_num);
    } else if (error_type == RECV_ERROR )
    {
        LogError("[MapVotes] Recieve Error (errno %d)", error_num);
    } else if (error_type == LISTEN_ERROR )
    {
        LogError("[MapVotes] Listen Error (errno %d)", error_num);
    } else
    {
        LogError("[MapVotes] socket error %d (errno %d)", errorType, error_num);
    }

    CloseHandle(headers_pack);
    CloseHandle(socket);
}

//By 11530
//GetSteamAccountID(client) does not work because we don't have 64 bit types
stock bool:GetCommunityIDString(const String:SteamID[], String:CommunityID[], const CommunityIDSize)
{
    decl String:SteamIDParts[3][11];
    new const String:Identifier[] = "76561197960265728";

    if ((CommunityIDSize < 1) || (ExplodeString(SteamID, ":", SteamIDParts, sizeof(SteamIDParts), sizeof(SteamIDParts[])) != 3))
    {
        CommunityID[0] = '\0';
        return false;
    }

    new Current, CarryOver = (SteamIDParts[1][0] == '1');
    for (new i = (CommunityIDSize - 2), j = (strlen(SteamIDParts[2]) - 1), k = (strlen(Identifier) - 1); i >= 0; i--, j--, k--)
    {
        Current = (j >= 0 ? (2 * (SteamIDParts[2][j] - '0')) : 0) + CarryOver + (k >= 0 ? ((Identifier[k] - '0') * 1) : 0);
        CarryOver = Current / 10;
        CommunityID[i] = (Current % 10) + '0';
    }

    CommunityID[CommunityIDSize - 1] = '\0';
    return true;
}

public MapVotesCall(String:route[128], String:query_params[512])
{
    new port= GetConVarInt(g_Cvar_MapVotesPort);
    decl String:base_url[128], String:api_key[128];
    GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
    GetConVarString(g_Cvar_MapVotesApiKey, api_key, sizeof(api_key));

    ReplaceString(base_url, sizeof(base_url), "http://", "", false);
    ReplaceString(base_url, sizeof(base_url), "https://", "", false);

    Format(query_params, sizeof(query_params), "%s&access_token=%s", query_params, api_key);

    HTTPPost(base_url, route, query_params, port);
}

public HTTPPost(String:base_url[128], String:route[128], String:query_params[512], port)
{
    new Handle:socket = SocketCreate(SOCKET_TCP, OnSocketError);

    //This Formats the headers needed to make a HTTP/1.1 POST request.
    new String:request_string[1024];
    Format(request_string, sizeof(request_string), "POST %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: %d\r\n\r\n%s", route, base_url, strlen(headers), headers);

    new Handle:headers_pack = CreateDataPack();
    WritePackString(headers_pack, request_string);
    SocketSetArg(socket, headers_pack);

    SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, base_url, port);
}
public WriteMessage(client, String:message[256])
{
    //Encode the message to be url safe
    decl String:base64[256], String:base64_url[256];
    EncodeBase64(base64, sizeof(base64), message);
    Base64MimeToUrl(base64_url, sizeof(base64_url), base64);

    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    decl String:query_params[512], String:map[128];
    GetCurrentMap(map, sizeof(map));
    Format(query_params, sizeof(query_params),
            "map=%s&uid=%s&comment=%s&base64=true", map, uid, base64);

    MapVotesCall(WRITE_MESSAGE_ROUTE, query_params);
}

public CastVote(client, value)
{
    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    if(!(value == -1 || value == 0 || value == 1)){
        LogError("[MapVotes] invalid vote value %d (steam_user: %s)", value, uid);
    }else{
        decl String:query_params[512], String:map[128];

        GetCurrentMap(map, sizeof(map));

        Format(query_params, sizeof(query_params),
                "map=%s&uid=%s&value=%d", map, uid, value);

        MapVotesCall(CAST_VOTE_ROUTE, query_params);
    }

}

public CallVoteOnClient(client)
{
    new Handle:menu = CreateMenu(VoteMenuHandler);
    SetMenuTitle(menu, "Do you like this map?");
    AddMenuItem(menu, "1","Like it.");
    AddMenuItem(menu, "-1","Hate it.");
    AddMenuItem(menu, "0","I have no strong feelings one way or the other.");
    DisplayMenu(menu, client, 20);
}

public VoteMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_End)
    {
        CloseHandle(menu);
    } else if (action == MenuAction_VoteCancel)
    {
    } else if (action == MenuAction_Select)
    {
        new String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        new value = StringToInt(info);

        CastVote(param1, value);
    }
}
public ViewMap(client)
{
        decl String:map[128], String:url[256], String:base_url[128];
        GetCurrentMap(map, sizeof(map));
        GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
        ReplaceString(base_url, sizeof(base_url), "http://", "", false);
        ReplaceString(base_url, sizeof(base_url), "https://", "", false);

        Format(url, sizeof(url),
                "http://%s%s/%s", base_url, MAPS_ROUTE, map);

        ShowMOTDPanel(client, "Map Viewer", url, MOTDPANEL_TYPE_URL);

}

public ServerQuery()
{
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * map_votes
 * Interact with the Map Votes web api - https://github.com/CrimsonTautology/map_votes
 *
 * Copyright 2013 The Crimson Tautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <socket>
#include <base64>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo = {
    name = "MapVotes",
    author = "CrimsonTautology",
    description = "Interact with the Map Votes web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_map_votes"
};

#define CAST_VOTE_ROUTE "/v1/api/cast_vote"
#define WRITE_MESSAGE_ROUTE "/v1/api/write_message"
#define SERVER_QUERY_ROUTE "/v1/api/server_query"
#define MAPS_ROUTE "/maps"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18

new Handle:g_Cvar_MapVotesUrl = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesPort = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesApiKey = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesVotingEnabled = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesCommentingEnabled = INVALID_HANDLE;

public OnPluginStart()
{

    g_Cvar_MapVotesUrl = CreateConVar("sm_map_votes_url", "", "URL to your Map Votes web page");
    g_Cvar_MapVotesPort = CreateConVar("sm_map_votes_port", "80", "HTTP Port used");
    g_Cvar_MapVotesApiKey = CreateConVar("sm_map_votes_api_key", "", "The API key you generated to interact with the Map Votes web page");
    g_Cvar_MapVotesVotingEnabled = CreateConVar("sm_map_votes_voting_enabled", "1", "Whether players are allowed to vote on the current map");
    g_Cvar_MapVotesCommentingEnabled = CreateConVar("sm_map_votes_commenting_enabled", "1", "Whether players are allowed to comment on the current map");

    RegConsoleCmd("sm_vote_menu", Command_VoteMenu, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_up", Command_VoteUp, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_down", Command_VoteDown, "Vote that you hate the current map");
    RegConsoleCmd("sm_map_comment", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_mc", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_view_map", Command_ViewMap, "View the Map Votes web page for this map");
    RegConsoleCmd("sm_call_vote", Command_CallVote, "Popup a vote panel to every player on the server that has not yet voted on this map");

}

public Action:Command_VoteMenu(client, args)
{
    if(client && IsClientAuthorized(client)){
        CallVoteOnClient(client);
    }
}

public Action:Command_VoteUp(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, 1);
    }
}

public Action:Command_VoteDown(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, -1);
    }
}

public Action:Command_ViewMap(client, args)
{
    if(client && IsClientAuthorized(client)){
        ViewMap(client);
    }
}

public Action:Command_MapComment(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[MapVotes] Usage: !map_comment <comment>");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:comment[256];
        GetCmdArgString(comment, sizeof(comment));
        WriteMessage(client, comment);
    }

    return Plugin_Handled;
}

public Action:Command_CallVote(client, args)
{
    //TODO
}

public OnSocketConnected(Handle:socket, any:headers_pack)
{
    decl String:request_string[1024];

    ResetPack(headers_pack);
    ReadPackString(headers_pack, request_string, sizeof(request_string));

    SocketSend(socket, request_string);
}

public OnSocketReceive(Handle:socket, String:receive_data[], const data_size, any:headers_pack) {
    //Used for data received back
    PrintToConsole(0,"%s", receive_data);//TODO
}

public OnSocketDisconnected(Handle:socket, any:headers_pack) {
    // Connection: close advises the webserver to close the connection when the transfer is finished
    // we're done here
    CloseHandle(headers_pack);
    CloseHandle(socket);
}

public OnSocketError(Handle:socket, const error_type, const error_num, any:headers_pack) {
    // a socket error occured
    if(error_type == EMPTY_HOST )
    {
        LogError("[MapVotes] Empty Host (errno %d)", error_num);
    } else if (error_type == NO_HOST )
    {
        LogError("[MapVotes] No Host (errno %d)", error_num);
    } else if (error_type == CONNECT_ERROR )
    {
        LogError("[MapVotes] Connection Error (errno %d)", error_num);
    } else if (error_type == SEND_ERROR )
    {
        LogError("[MapVotes] Send Error (errno %d)", error_num);
    } else if (error_type == BIND_ERROR )
    {
        LogError("[MapVotes] Bind Error (errno %d)", error_num);
    } else if (error_type == RECV_ERROR )
    {
        LogError("[MapVotes] Recieve Error (errno %d)", error_num);
    } else if (error_type == LISTEN_ERROR )
    {
        LogError("[MapVotes] Listen Error (errno %d)", error_num);
    } else
    {
        LogError("[MapVotes] socket error %d (errno %d)", errorType, error_num);
    }

    CloseHandle(headers_pack);
    CloseHandle(socket);
}

//By 11530
//GetSteamAccountID(client) does not work because we don't have 64 bit types
stock bool:GetCommunityIDString(const String:SteamID[], String:CommunityID[], const CommunityIDSize)
{
    decl String:SteamIDParts[3][11];
    new const String:Identifier[] = "76561197960265728";

    if ((CommunityIDSize < 1) || (ExplodeString(SteamID, ":", SteamIDParts, sizeof(SteamIDParts), sizeof(SteamIDParts[])) != 3))
    {
        CommunityID[0] = '\0';
        return false;
    }

    new Current, CarryOver = (SteamIDParts[1][0] == '1');
    for (new i = (CommunityIDSize - 2), j = (strlen(SteamIDParts[2]) - 1), k = (strlen(Identifier) - 1); i >= 0; i--, j--, k--)
    {
        Current = (j >= 0 ? (2 * (SteamIDParts[2][j] - '0')) : 0) + CarryOver + (k >= 0 ? ((Identifier[k] - '0') * 1) : 0);
        CarryOver = Current / 10;
        CommunityID[i] = (Current % 10) + '0';
    }

    CommunityID[CommunityIDSize - 1] = '\0';
    return true;
}

public MapVotesCall(String:route[128], String:query_params[512])
{
    new port= GetConVarInt(g_Cvar_MapVotesPort);
    decl String:base_url[128], String:api_key[128];
    GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
    GetConVarString(g_Cvar_MapVotesApiKey, api_key, sizeof(api_key));

    ReplaceString(base_url, sizeof(base_url), "http://", "", false);
    ReplaceString(base_url, sizeof(base_url), "https://", "", false);

    Format(query_params, sizeof(query_params), "%s&access_token=%s", query_params, api_key);

    HTTPPost(base_url, route, query_params, port);
}

public HTTPPost(String:base_url[128], String:route[128], String:query_params[512], port)
{
    new Handle:socket = SocketCreate(SOCKET_TCP, OnSocketError);

    //This Formats the headers needed to make a HTTP/1.1 POST request.
    new String:request_string[1024];
    Format(request_string, sizeof(request_string), "POST %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: %d\r\n\r\n%s", route, base_url, strlen(headers), headers);

    new Handle:headers_pack = CreateDataPack();
    WritePackString(headers_pack, request_string);
    SocketSetArg(socket, headers_pack);

    SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, base_url, port);
}
public WriteMessage(client, String:message[256])
{
    //Encode the message to be url safe
    decl String:base64[256], String:base64_url[256];
    EncodeBase64(base64, sizeof(base64), message);
    Base64MimeToUrl(base64_url, sizeof(base64_url), base64);

    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    decl String:query_params[512], String:map[128];
    GetCurrentMap(map, sizeof(map));
    Format(query_params, sizeof(query_params),
            "map=%s&uid=%s&comment=%s&base64=true", map, uid, base64);

    MapVotesCall(WRITE_MESSAGE_ROUTE, query_params);
}

public CastVote(client, value)
{
    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    if(!(value == -1 || value == 0 || value == 1)){
        LogError("[MapVotes] invalid vote value %d (steam_user: %s)", value, uid);
    }else{
        decl String:query_params[512], String:map[128];

        GetCurrentMap(map, sizeof(map));

        Format(query_params, sizeof(query_params),
                "map=%s&uid=%s&value=%d", map, uid, value);

        MapVotesCall(CAST_VOTE_ROUTE, query_params);
    }

}

public CallVoteOnClient(client)
{
    new Handle:menu = CreateMenu(VoteMenuHandler);
    SetMenuTitle(menu, "Do you like this map?");
    AddMenuItem(menu, "1","Like it.");
    AddMenuItem(menu, "-1","Hate it.");
    AddMenuItem(menu, "0","I have no strong feelings one way or the other.");
    DisplayMenu(menu, client, 20);
}

public VoteMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_End)
    {
        CloseHandle(menu);
    } else if (action == MenuAction_VoteCancel)
    {
    } else if (action == MenuAction_Select)
    {
        new String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        new value = StringToInt(info);

        CastVote(param1, value);
    }
}
public ViewMap(client)
{
        decl String:map[128], String:url[256], String:base_url[128];
        GetCurrentMap(map, sizeof(map));
        GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
        ReplaceString(base_url, sizeof(base_url), "http://", "", false);
        ReplaceString(base_url, sizeof(base_url), "https://", "", false);

        Format(url, sizeof(url),
                "http://%s%s/%s", base_url, MAPS_ROUTE, map);

        ShowMOTDPanel(client, "Map Viewer", url, MOTDPANEL_TYPE_URL);

}

public ServerQuery()
{
}


Remove unneeded file

Remove unneeded file

#if defined _mckay_updater_included
 #endinput
#endif
#define _mckay_updater_included

#if defined REQUIRE_PLUGIN
 #undef REQUIRE_PLUGIN
#endif
#include <updater>
#define REQUIRE_PLUGIN

#define UPDATER_BASE_URL "http://hg.doctormckay.com/public-plugins/raw/default"

ConVar cvarEnableUpdater;
ConVar cvarPluginVersion;

public void OnAllPluginsLoaded()
{
	char cvarName[64];
	Format(cvarName, sizeof(cvarName), "%s_auto_update", CONVAR_PREFIX);
	cvarEnableUpdater = CreateConVar(cvarName, "1", "Enables automatic updating (has no effect if Updater is not installed)");

	Format(cvarName, sizeof(cvarName), "%s_version", CONVAR_PREFIX);
	cvarPluginVersion = CreateConVar(cvarName, PLUGIN_VERSION, "Plugin Version", FCVAR_DONTRECORD|FCVAR_CHEAT|FCVAR_NOTIFY);

	HookConVarChange(cvarEnableUpdater, CheckUpdaterStatus);
	HookConVarChange(cvarPluginVersion, CheckUpdaterStatus);
	CheckUpdaterStatus(INVALID_HANDLE, "", "");

#if defined ALL_PLUGINS_LOADED_FUNC
	ALL_PLUGINS_LOADED_FUNC();
#endif
}

public void OnLibraryAdded(const char[] name)
{
	CheckUpdaterStatus(null, "", "");

#if defined LIBRARY_ADDED_FUNC
	LIBRARY_ADDED_FUNC(name);
#endif
}

public void OnLibraryRemoved(const char[] name)
{
	CheckUpdaterStatus(null, "", "");

#if defined LIBRARY_REMOVED_FUNC
	LIBRARY_REMOVED_FUNC(name);
#endif
}

public void CheckUpdaterStatus(ConVar convar, const char[] name, const char[] value)
{
	if (cvarPluginVersion == null)
	{
		return; // Version cvar not created yet
	}

	if (LibraryExists("updater") && cvarEnableUpdater.BoolValue)
	{
		char url[512], version[12];
		Format(url, sizeof(url), "%s/%s", UPDATER_BASE_URL, UPDATE_FILE);
		Updater_AddPlugin(url); // Has no effect if we're already in Updater's pool

		Format(version, sizeof(version), "%sA", PLUGIN_VERSION);
		cvarPluginVersion.SetString(version);
	}
	else
	{
		cvarPluginVersion.SetString(PLUGIN_VERSION);
	}
}

public Action Updater_OnPluginChecking()
{
	if(!cvarEnableUpdater.BoolValue)
	{
		return Plugin_Handled;
	}
	return Plugin_Continue;
}

#if defined RELOAD_ON_UPDATE
public Updater_OnPluginUpdated()
{
	ReloadPlugin();
}
#endif