/**
 * vim: set ts=4 :
 * =============================================================================
 * MapChooser Extended
 * Creates a map vote at appropriate times, setting sm_nextmap to the winning
 * vote.  Includes extra options not present in the SourceMod MapChooser
 *
 * MapChooser Extended (C)2011-2013 Powerlord (Ross Bemrose)
 * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
 * =============================================================================
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 3.0, as published by the
 * Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As a special exception, AlliedModders LLC gives you permission to link the
 * code of this program (as well as its derivative works) to "Half-Life 2," the
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
 * by the Valve Corporation.  You must obey the GNU General Public License in
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
 * this exception to all derivative works.  AlliedModders LLC defines further
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
 * or <http://www.sourcemod.net/license.php>.
 *
 * Version: $Id$
 */

//#define DEBUG

#if defined DEBUG
	#define assert(%1) if(!(%1)) ThrowError("Debug Assertion Failed");
	#define assert_msg(%1,%2) if(!(%1)) ThrowError(%2);
#else
	#define assert(%1)
	#define assert_msg(%1,%2)
#endif

#include <sourcemod>
#include <mapchooser>
#include <mapchooser_extended>
#include <nextmap>
#include <sdktools>
#include <multicolors>
#include <PlayerManager>

#pragma semicolon 1
#pragma newdecls required

#define MCE_VERSION "1.3.1"

enum RoundCounting
{
	RoundCounting_Standard = 0,
	RoundCounting_MvM,
	RoundCounting_ArmsRace,
}

// CSGO requires two cvars to get the game type
enum
{
	GameType_Classic	= 0,
	GameType_GunGame	= 1,
	GameType_Training	= 2,
	GameType_Custom		= 3,
}

enum
{
	GunGameMode_ArmsRace	= 0,
	GunGameMode_Demolition	= 1,
	GunGameMode_DeathMatch	= 2,
}

public Plugin myinfo =
{
	name = "MapChooser Extended",
	author = "Powerlord, Zuko, BotoX and AlliedModders LLC",
	description = "Automated Map Voting with Extensions",
	version = MCE_VERSION,
	url = ""
};

/* Valve ConVars */
ConVar g_Cvar_Winlimit;
ConVar g_Cvar_Maxrounds;
ConVar g_Cvar_Fraglimit;
ConVar g_Cvar_Bonusroundtime;
ConVar g_Cvar_GameType;
ConVar g_Cvar_GameMode;

/* Plugin ConVars */
ConVar g_Cvar_StartTime;
ConVar g_Cvar_StartRounds;
ConVar g_Cvar_StartFrags;
ConVar g_Cvar_ExtendTimeStep;
ConVar g_Cvar_ExtendRoundStep;
ConVar g_Cvar_ExtendFragStep;
ConVar g_Cvar_ExcludeMaps;
ConVar g_Cvar_ExcludeMapsTime;
ConVar g_Cvar_IncludeMaps;
ConVar g_Cvar_IncludeMapsReserved;
ConVar g_Cvar_NoVoteMode;
ConVar g_Cvar_Extend;
ConVar g_Cvar_DontChange;
ConVar g_Cvar_EndOfMapVote;
ConVar g_Cvar_VoteDuration;

Handle g_VoteTimer = INVALID_HANDLE;
Handle g_RetryTimer = INVALID_HANDLE;
Handle g_WarningTimer = INVALID_HANDLE;

/* Data Handles */
Handle g_MapList = INVALID_HANDLE;
Handle g_NominateList[MAXPLAYERS + 1];
Handle g_NominateOwners = INVALID_HANDLE;
StringMap g_OldMapList;
StringMap g_TimeMapList;
Handle g_NextMapList = INVALID_HANDLE;
Handle g_VoteMenu = INVALID_HANDLE;
KeyValues g_Config;

int g_Extends;
int g_TotalRounds;
bool g_HasVoteStarted;
bool g_WaitingForVote;
bool g_MapVoteCompleted;
bool g_ChangeMapAtRoundEnd;
bool g_ChangeMapInProgress;
bool g_HasIntermissionStarted = false;
int g_mapFileSerial = -1;

int g_NominateCount = 0;
int g_NominateReservedCount = 0;
MapChange g_ChangeTime;

//check if autismbot
bool is_bot_player[MAXPLAYERS + 1];

Handle g_NominationsResetForward = INVALID_HANDLE;
Handle g_MapVoteStartedForward = INVALID_HANDLE;

/* Mapchooser Extended Plugin ConVars */

ConVar g_Cvar_RunOff;
ConVar g_Cvar_RunOffPercent;
ConVar g_Cvar_BlockSlots;
ConVar g_Cvar_MaxRunOffs;
ConVar g_Cvar_StartTimePercent;
ConVar g_Cvar_StartTimePercentEnable;
ConVar g_Cvar_WarningTime;
ConVar g_Cvar_RunOffWarningTime;
ConVar g_Cvar_TimerLocation;
ConVar g_Cvar_ExtendPosition;
ConVar g_Cvar_MarkCustomMaps;
ConVar g_Cvar_RandomizeNominations;
ConVar g_Cvar_HideTimer;
ConVar g_Cvar_NoVoteOption;
ConVar g_Cvar_ShufflePerClient;
ConVar g_Cvar_NoRestrictionTimeframeEnable;
ConVar g_Cvar_NoRestrictionTimeframeMinTime;
ConVar g_Cvar_NoRestrictionTimeframeMaxTime;

/* Mapchooser Extended Data Handles */
Handle g_OfficialList = INVALID_HANDLE;

/* Mapchooser Extended Forwards */
Handle g_MapVoteWarningStartForward = INVALID_HANDLE;
Handle g_MapVoteWarningTickForward = INVALID_HANDLE;
Handle g_MapVoteStartForward = INVALID_HANDLE;
Handle g_MapVoteEndForward = INVALID_HANDLE;
Handle g_MapVoteRunoffStartForward = INVALID_HANDLE;

/* Mapchooser Extended Globals */
int g_RunoffCount = 0;
int g_mapOfficialFileSerial = -1;
char g_GameModName[64];
bool g_WarningInProgress = false;
bool g_AddNoVote = false;
bool g_SaveCDOnMapEnd = true;

RoundCounting g_RoundCounting = RoundCounting_Standard;

/* Upper bound of how many team there could be */
#define MAXTEAMS 10
int g_winCount[MAXTEAMS];

bool g_BlockedSlots = false;
int g_ObjectiveEnt = -1;

enum TimerLocation
{
	TimerLocation_Hint = 0,
	TimerLocation_Center = 1,
	TimerLocation_Chat = 2,
}

enum WarningType
{
	WarningType_Vote,
	WarningType_Revote,
}

#define VOTE_EXTEND "##extend##"
#define VOTE_DONTCHANGE "##dontchange##"

/* Mapchooser Extended Defines */
#define LINE_ONE "##lineone##"
#define LINE_TWO "##linetwo##"
#define LINE_SPACER "##linespacer##"
#define FAILURE_TIMER_LENGTH 5

//call forward to reset all nominations
public void OnPluginEnd()
{
    for (int i = 0; i < MaxClients; i++)
    {
        int index = FindValueInArray(g_NominateOwners, i);
        if (index == -1) continue;
        for (int j = 0; j < GetArraySize(g_NominateList[i]); j++)
        {
            char oldmap[PLATFORM_MAX_PATH];
            Call_StartForward(g_NominationsResetForward);
            GetArrayString(g_NominateList[i], j, oldmap, PLATFORM_MAX_PATH);
            Call_PushString(oldmap);
            Call_Finish();
        }
    }
}

public void OnPluginStart()
{
    LoadTranslations("mapchooser_extended.phrases");
    LoadTranslations("basevotes.phrases");
    LoadTranslations("common.phrases");

    int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
    g_MapList = CreateArray(arraySize);
    g_NominateOwners = CreateArray(1);
    g_OldMapList = new StringMap();
    g_TimeMapList = new StringMap();
    g_NextMapList = CreateArray(arraySize);
    g_OfficialList = CreateArray(arraySize);

    for (int i = 0; i < MaxClients; i++)
    {
        g_NominateList[i] = CreateArray(arraySize);
    }

    GetGameFolderName(g_GameModName, sizeof(g_GameModName));

    g_Cvar_EndOfMapVote = CreateConVar("mce_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);

    g_Cvar_StartTime = CreateConVar("mce_starttime", "10.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
    g_Cvar_StartRounds = CreateConVar("mce_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on DoD:S, CS:S, and TF2 to start vote during bonus round time", _, true, 0.0);
    g_Cvar_StartFrags = CreateConVar("mce_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
    g_Cvar_ExtendTimeStep = CreateConVar("mce_extend_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
    g_Cvar_ExtendRoundStep = CreateConVar("mce_extend_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
    g_Cvar_ExtendFragStep = CreateConVar("mce_extend_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
    g_Cvar_ExcludeMaps = CreateConVar("mce_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
    g_Cvar_ExcludeMapsTime = CreateConVar("mce_exclude_time", "5h", "Specifies how long in minutes an old map is excluded from the vote.");
    g_Cvar_IncludeMaps = CreateConVar("mce_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 7.0);
    g_Cvar_IncludeMapsReserved = CreateConVar("mce_include_reserved", "2", "Specifies how many private/random maps to include in the vote.", _, true, 0.0, true, 5.0);
    g_Cvar_NoVoteMode = CreateConVar("mce_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0);
    g_Cvar_Extend = CreateConVar("mce_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
    g_Cvar_DontChange = CreateConVar("mce_dontchange", "1", "Specifies if a 'Don't Change option should be added to early votes", _, true, 0.0);
    g_Cvar_VoteDuration = CreateConVar("mce_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);

    // MapChooser Extended cvars
    CreateConVar("mce_version", MCE_VERSION, "MapChooser Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD);

    g_Cvar_RunOff = CreateConVar("mce_runoff", "1", "Hold run off votes if winning choice has less than a certain percentage of votes", _, true, 0.0, true, 1.0);
    g_Cvar_RunOffPercent = CreateConVar("mce_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0);
    g_Cvar_BlockSlots = CreateConVar("mce_blockslots", "0", "Block slots to prevent accidental votes.  Only applies when Voice Command style menus are in use.", _, true, 0.0, true, 1.0);
    //g_Cvar_BlockSlotsCount = CreateConVar("mce_blockslots_count", "2", "Number of slots to block.", _, true, 1.0, true, 3.0);
    g_Cvar_MaxRunOffs = CreateConVar("mce_maxrunoffs", "1", "Number of run off votes allowed each map.", _, true, 0.0);
    g_Cvar_StartTimePercent = CreateConVar("mce_start_percent", "35.0", "Specifies when to start the vote based on percents.", _, true, 0.0, true, 100.0);
    g_Cvar_StartTimePercentEnable = CreateConVar("mce_start_percent_enable", "0", "Enable or Disable percentage calculations when to start vote.", _, true, 0.0, true, 1.0);
    g_Cvar_WarningTime = CreateConVar("mce_warningtime", "15.0", "Warning time in seconds.", _, true, 0.0, true, 60.0);
    g_Cvar_RunOffWarningTime = CreateConVar("mce_runoffvotewarningtime", "5.0", "Warning time for runoff vote in seconds.", _, true, 0.0, true, 30.0);
    g_Cvar_TimerLocation = CreateConVar("mce_warningtimerlocation", "0", "Location for the warning timer text. 0 is HintBox, 1 is Center text, 2 is Chat.  Defaults to HintBox.", _, true, 0.0, true, 2.0);
    g_Cvar_MarkCustomMaps = CreateConVar("mce_markcustommaps", "1", "Mark custom maps in the vote list. 0 = Disabled, 1 = Mark with *, 2 = Mark with phrase.", _, true, 0.0, true, 2.0);
    g_Cvar_ExtendPosition = CreateConVar("mce_extendposition", "0", "Position of Extend/Dont Change options. 0 = at end, 1 = at start.", _, true, 0.0, true, 1.0);
    g_Cvar_RandomizeNominations = CreateConVar("mce_randomizeorder", "0", "Randomize map order?", _, true, 0.0, true, 1.0);
    g_Cvar_HideTimer = CreateConVar("mce_hidetimer", "0", "Hide the MapChooser Extended warning timer", _, true, 0.0, true, 1.0);
    g_Cvar_NoVoteOption = CreateConVar("mce_addnovote", "1", "Add \"No Vote\" to vote menu?", _, true, 0.0, true, 1.0);
    g_Cvar_ShufflePerClient = CreateConVar("mce_shuffle_per_client", "1", "Random shuffle map vote menu per client?", _, true, 0.0, true, 1.0);
    g_Cvar_NoRestrictionTimeframeEnable = CreateConVar("mce_no_restriction_timeframe_enable", "1", "Enable timeframe where all nomination restrictions and cooldowns are disabled?", _, true, 0.0, true, 1.0);
    g_Cvar_NoRestrictionTimeframeMinTime = CreateConVar("mce_no_restriction_timeframe_mintime", "0100", "Start of the timeframe where all nomination restrictions and cooldowns are disabled (Format: HHMM)", _, true, 0000.0, true, 2359.0);
    g_Cvar_NoRestrictionTimeframeMaxTime = CreateConVar("mce_no_restriction_timeframe_maxtime", "0700", "End of the timeframe where all nomination restrictions and cooldowns are disabled (Format: HHMM)", _, true, 0000.0, true, 2359.0);

    RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
    RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");

    // Mapchooser Extended Commands
    RegAdminCmd("mce_reload_maplist", Command_ReloadMaps, ADMFLAG_CHANGEMAP, "mce_reload_maplist - Reload the Official Maplist file.");

    RegConsoleCmd("sm_extends", Command_ExtendsLeft, "sm_extends - Shows how many extends are left on the current map.");
    RegConsoleCmd("sm_extendsleft", Command_ExtendsLeft, "sm_extendsleft - Shows how many extends are left on the current map.");

    g_Cvar_Winlimit = FindConVar("mp_winlimit");
    g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
    g_Cvar_Fraglimit = FindConVar("mp_fraglimit");

    EngineVersion version = GetEngineVersion();

    static char mapListPath[PLATFORM_MAX_PATH];

    BuildPath(Path_SM, mapListPath, PLATFORM_MAX_PATH, "configs/mapchooser_extended/maps/%s.txt", g_GameModName);
    SetMapListCompatBind("official", mapListPath);

    switch(version)
    {
        case Engine_TF2:
        {
            g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
        }

        case Engine_CSGO:
        {
            g_Cvar_GameType = FindConVar("game_type");
            g_Cvar_GameMode = FindConVar("game_mode");
            g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay");
        }

        case Engine_DODS:
        {
            g_Cvar_Bonusroundtime = FindConVar("dod_bonusroundtime");
        }

        case Engine_CSS:
        {
            g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay");
        }

        default:
        {
            g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
        }
    }

    switch(version)
    {
        case Engine_TF2:
        {
            HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
            HookEvent("teamplay_restart_round", Event_TFRestartRound);
            HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
            HookEvent("pve_win_panel", Event_MvMWinPanel);
        }

        case Engine_NuclearDawn:
        {
            HookEvent("round_win", Event_RoundEnd);
        }

        case Engine_CSGO:
        {
            HookEvent("round_end", Event_RoundEnd);
            HookEvent("cs_intermission", Event_Intermission);
            HookEvent("announce_phase_end", Event_PhaseEnd);
        }

        case Engine_DODS:
        {
            HookEvent("dod_round_win", Event_RoundEnd);
        }

        default:
        {
            HookEvent("round_end", Event_RoundEnd);
        }
    }

    HookEvent("player_death", Event_PlayerDeath);

    AutoExecConfig(true, "mapchooser_extended");

    //Change the mp_bonusroundtime max so that we have time to display the vote
    //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
    SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);

    g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
    g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore);

    //MapChooser Extended Forwards
    g_MapVoteStartForward = CreateGlobalForward("OnMapVoteStart", ET_Ignore); // Deprecated
    g_MapVoteEndForward = CreateGlobalForward("OnMapVoteEnd", ET_Ignore, Param_String);
    g_MapVoteWarningStartForward = CreateGlobalForward("OnMapVoteWarningStart", ET_Ignore);
    g_MapVoteWarningTickForward = CreateGlobalForward("OnMapVoteWarningTick", ET_Ignore, Param_Cell);
    g_MapVoteRunoffStartForward = CreateGlobalForward("OnMapVoteRunnoffWarningStart", ET_Ignore);

    InternalRestoreMapCooldowns();
}

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
	if(LibraryExists("mapchooser"))
	{
		strcopy(error, err_max, "MapChooser already loaded, aborting.");
		return APLRes_Failure;
	}

	RegPluginLibrary("mapchooser");

	MarkNativeAsOptional("GetEngineVersion");

	CreateNative("NominateMap", Native_NominateMap);
	CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap);
	CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner);
	CreateNative("InitiateMapChooserVote", Native_InitiateVote);
	CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
	CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
	CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
	CreateNative("GetNominatedMapList", Native_GetNominatedMapList);
	CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);

	// MapChooser Extended natives
	CreateNative("IsMapOfficial", Native_IsMapOfficial);
	CreateNative("CanNominate", Native_CanNominate);
	CreateNative("ExcludeMap", Native_ExcludeMap);
	CreateNative("ExcludeMapTime", Native_ExcludeMapTime);
	CreateNative("GetMapCooldown", Native_GetMapCooldown);
	CreateNative("GetMapCooldownTime", Native_GetMapCooldownTime);
	CreateNative("GetMapMinTime", Native_GetMapMinTime);
	CreateNative("GetMapMaxTime", Native_GetMapMaxTime);
	CreateNative("GetMapMinPlayers", Native_GetMapMinPlayers);
	CreateNative("GetMapMaxPlayers", Native_GetMapMaxPlayers);
	CreateNative("GetMapTimeRestriction", Native_GetMapTimeRestriction);
	CreateNative("GetMapPlayerRestriction", Native_GetMapPlayerRestriction);
	CreateNative("GetMapGroups", Native_GetMapGroups);
	CreateNative("GetMapGroupRestriction", Native_GetMapGroupRestriction);
	CreateNative("GetMapVIPRestriction", Native_GetMapVIPRestriction);
	CreateNative("GetExtendsLeft", Native_GetExtendsLeft);
	CreateNative("AreRestrictionsActive", Native_AreRestrictionsActive);
	CreateNative("SimulateMapEnd", Native_SimulateMapEnd);

	return APLRes_Success;
}

public void OnMapStart()
{
	static char folder[64];
	GetGameFolderName(folder, sizeof(folder));

	g_RoundCounting = RoundCounting_Standard;
	g_ObjectiveEnt = -1;

	if(strcmp(folder, "tf") == 0 && GameRules_GetProp("m_bPlayingMannVsMachine"))
	{
		g_RoundCounting = RoundCounting_MvM;
		g_ObjectiveEnt = EntIndexToEntRef(FindEntityByClassname(-1, "tf_objective_resource"));
	}
	else if(strcmp(folder, "csgo") == 0 && GetConVarInt(g_Cvar_GameType) == GameType_GunGame &&
		GetConVarInt(g_Cvar_GameMode) == GunGameMode_ArmsRace)
	{
		g_RoundCounting = RoundCounting_ArmsRace;
	}

	if(g_Config)
		delete g_Config;

	char sConfigFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/mapchooser_extended.cfg");
	if(!FileExists(sConfigFile))
	{
		LogMessage("Could not find config: \"%s\"", sConfigFile);
		return;
	}
	LogMessage("Found config: \"%s\"", sConfigFile);

	g_Config = new KeyValues("mapchooser_extended");
	if(!g_Config.ImportFromFile(sConfigFile))
	{
		delete g_Config;
		LogMessage("ImportFromFile() failed!");
		return;
	}
	g_Config.Rewind();

	if(InternalAreRestrictionsActive())
		g_SaveCDOnMapEnd = true;
	else
		g_SaveCDOnMapEnd = false;
}

public void OnConfigsExecuted()
{
    if(ReadMapList(g_MapList,
                     g_mapFileSerial,
                     "mapchooser",
                     MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
        != INVALID_HANDLE)

    {
        if(g_mapFileSerial == -1)
            LogError("Unable to create a valid map list.");
    }


    SetupTimeleftTimer();

    g_TotalRounds = 0;

    g_Extends = 0;

    g_MapVoteCompleted = false;

    g_NominateCount = 0;
    g_NominateReservedCount = 0;
    for (int i = 0; i < MaxClients; i++)
    {
        if (g_NominateList[i] != INVALID_HANDLE)
        {
            ClearArray(g_NominateList[i]);
        }
    }

    ClearArray(g_NominateOwners);

    for(int i = 0; i < MAXTEAMS; i++)
        g_winCount[i] = 0;

    /* Check if mapchooser will attempt to start mapvote during bonus round time */
    if (!GetConVarInt(g_Cvar_StartRounds))
    {
        if(!GetConVarInt(g_Cvar_StartTime) && GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
            LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
    }

    InitializeOfficialMapList();
}

public void OnMapEnd()
{
	g_HasVoteStarted = false;
	g_WaitingForVote = false;
	g_ChangeMapAtRoundEnd = false;
	g_ChangeMapInProgress = false;
	g_HasIntermissionStarted = false;

	g_VoteTimer = INVALID_HANDLE;
	g_RetryTimer = INVALID_HANDLE;
	g_WarningTimer = INVALID_HANDLE;
	g_RunoffCount = 0;

	static char map[PLATFORM_MAX_PATH];
	int Cooldown;

	if(g_SaveCDOnMapEnd)
	{
		GetCurrentMap(map, PLATFORM_MAX_PATH);
		Cooldown = InternalGetMapCooldown(map);
		g_OldMapList.SetValue(map, Cooldown, true);

		Cooldown = GetTime() + InternalGetMapCooldownTime(map) - RoundToFloor(GetGameTime());
		g_TimeMapList.SetValue(map, Cooldown, true);
	}

	StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot();
	for(int i = 0; i < OldMapListSnapshot.Length; i++)
	{
		OldMapListSnapshot.GetKey(i, map, sizeof(map));
		g_OldMapList.GetValue(map, Cooldown);

		Cooldown--;
		if(Cooldown > 0)
			g_OldMapList.SetValue(map, Cooldown, true);
		else
			g_OldMapList.Remove(map);
	}
	delete OldMapListSnapshot;

	StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot();
	for(int i = 0; i < TimeMapListSnapshot.Length; i++)
	{
		TimeMapListSnapshot.GetKey(i, map, sizeof(map));
		g_TimeMapList.GetValue(map, Cooldown);

		if(Cooldown < GetTime())
			g_TimeMapList.Remove(map);
	}
	delete OldMapListSnapshot;

	InternalStoreMapCooldowns();
}

public void OnClientPutInServer(int client)
{
    if (g_NominateList[client] != INVALID_HANDLE)
    {
        ClearArray(g_NominateList[client]);
    }
    CheckMapRestrictions(false, true);
}

public void OnClientDisconnect_Post(int client)
{
	CheckMapRestrictions(false, true);
}

public void OnClientDisconnect(int client)
{
    is_bot_player[client] = false;
    int index = FindValueInArray(g_NominateOwners, client);

    if(index == -1)
        return;

    //2023 edit for handling multiple nominations -jenz
    for (int i = 0; i < GetArraySize(g_NominateList[client]); i++)
    {
        Call_StartForward(g_NominationsResetForward);
        char oldmap[PLATFORM_MAX_PATH];
        GetArrayString(g_NominateList[client], i, oldmap, PLATFORM_MAX_PATH);
        Call_PushString(oldmap);
        Call_Finish();
    }

    RemoveFromArray(g_NominateOwners, index);
    for (int i = 0; i < GetArraySize(g_NominateList[client]); i++)
    {
        RemoveFromArray(g_NominateList[client], i);
    }

    ClearArray(g_NominateList[client]);
    g_NominateCount--;
}

public Action Command_SetNextmap(int client, int args)
{
	if(args < 1)
	{
		CReplyToCommand(client, "[MCE] Usage: sm_setnextmap <map>");
		return Plugin_Handled;
	}

	static char map[PLATFORM_MAX_PATH];
	GetCmdArg(1, map, PLATFORM_MAX_PATH);

	if(!IsMapValid(map))
	{
		CReplyToCommand(client, "[MCE] %t", "Map was not found", map);
		return Plugin_Handled;
	}

	ShowActivity(client, "%t", "Changed Next Map", map);
	LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);

	SetNextMap(map);
	g_MapVoteCompleted = true;

	return Plugin_Handled;
}

public Action Command_ReloadMaps(int client, int args)
{
	InitializeOfficialMapList();
}

public Action Command_ExtendsLeft(int client, int args)
{
	CReplyToCommand(client, "[MCE] Available Extends: %d", GetConVarInt(g_Cvar_Extend) - g_Extends);
	return Plugin_Handled;
}

public void OnMapTimeLeftChanged()
{
	if(GetArraySize(g_MapList))
		SetupTimeleftTimer();
}

void SetupTimeleftTimer()
{
	int time;
	if(GetMapTimeLeft(time) && time > 0)
	{
		int startTime;
		if(GetConVarBool(g_Cvar_StartTimePercentEnable))
		{
			int timeLimit;
			if(GetMapTimeLimit(timeLimit) && timeLimit > 0)
			{
				startTime = GetConVarInt(g_Cvar_StartTimePercent) * (timeLimit * 60) / 100;
			}
		}
		else
			startTime = GetConVarInt(g_Cvar_StartTime) * 60;

		if(time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
		{
			SetupWarningTimer(WarningType_Vote);
		}
		else
		{
			if(g_WarningTimer == INVALID_HANDLE)
			{
				if(g_VoteTimer != INVALID_HANDLE)
				{
					KillTimer(g_VoteTimer);
					g_VoteTimer = INVALID_HANDLE;
				}

				//g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVoteTimer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
				g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartWarningTimer, _, TIMER_FLAG_NO_MAPCHANGE);
			}
		}
	}
}

public Action Timer_StartWarningTimer(Handle timer)
{
	g_VoteTimer = INVALID_HANDLE;

	if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE)
		SetupWarningTimer(WarningType_Vote);
}

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

	// This is still necessary because InitiateVote still calls this directly via the retry timer
	if(!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
	{
		g_WarningTimer = INVALID_HANDLE;
		return Plugin_Stop;
	}

	ResetPack(data);
	int warningMaxTime = ReadPackCell(data);
	int warningTimeRemaining = warningMaxTime - timePassed;

	char warningPhrase[32];
	ReadPackString(data, warningPhrase, sizeof(warningPhrase));

	// Tick timer for external plugins
	Call_StartForward(g_MapVoteWarningTickForward);
	Call_PushCell(warningTimeRemaining);
	Call_Finish();

	if(timePassed == 0 || !GetConVarBool(g_Cvar_HideTimer))
	{
		TimerLocation timerLocation = view_as<TimerLocation>(GetConVarInt(g_Cvar_TimerLocation));

		switch(timerLocation)
		{
			case TimerLocation_Center:
			{
				PrintCenterTextAll("%t", warningPhrase, warningTimeRemaining);
			}

			case TimerLocation_Chat:
			{
				PrintToChatAll("%t", warningPhrase, warningTimeRemaining);
			}

			default:
			{
				PrintHintTextToAll("%t", warningPhrase, warningTimeRemaining);
			}
		}
	}

	if(timePassed++ >= warningMaxTime)
	{
		if(timer == g_RetryTimer)
		{
			g_WaitingForVote = false;
			g_RetryTimer = INVALID_HANDLE;
		}
		else
			g_WarningTimer = INVALID_HANDLE;

		timePassed = 0;
		MapChange mapChange = view_as<MapChange>(ReadPackCell(data));
		Handle hndl = view_as<Handle>(ReadPackCell(data));

		InitiateVote(mapChange, hndl);

		return Plugin_Stop;
	}

	return Plugin_Continue;
}

public void Event_TFRestartRound(Handle event, const char[] name, bool dontBroadcast)
{
	/* Game got restarted - reset our round count tracking */
	g_TotalRounds = 0;
}

public void Event_TeamPlayWinPanel(Handle event, const char[] name, bool dontBroadcast)
{
	if(g_ChangeMapAtRoundEnd)
	{
		g_ChangeMapAtRoundEnd = false;
		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
		g_ChangeMapInProgress = true;
	}

	int bluescore = GetEventInt(event, "blue_score");
	int redscore = GetEventInt(event, "red_score");

	if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
	{
		g_TotalRounds++;

		if(!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
			return;

		CheckMaxRounds(g_TotalRounds);

		switch(GetEventInt(event, "winning_team"))
		{
			case 3:
			{
				CheckWinLimit(bluescore);
			}
			case 2:
			{
				CheckWinLimit(redscore);
			}
			//We need to do nothing on winning_team == 0 this indicates stalemate.
			default:
			{
				return;
			}
		}
	}
}

public void Event_MvMWinPanel(Handle event, const char[] name, bool dontBroadcast)
{
	if(GetEventInt(event, "winning_team") == 2)
	{
		int objectiveEnt = EntRefToEntIndex(g_ObjectiveEnt);
		if(objectiveEnt != INVALID_ENT_REFERENCE)
		{
			g_TotalRounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineWaveCount");
			CheckMaxRounds(g_TotalRounds);
		}
	}
}

public void Event_Intermission(Handle event, const char[] name, bool dontBroadcast)
{
	g_HasIntermissionStarted = true;
}

public void Event_PhaseEnd(Handle event, const char[] name, bool dontBroadcast)
{
	/* announce_phase_end fires for both half time and the end of the map, but intermission fires first for end of the map. */
	if(g_HasIntermissionStarted)
		return;

	/* No intermission yet, so this must be half time. Swap the score counters. */
	int t_score = g_winCount[2];
	g_winCount[2] =  g_winCount[3];
	g_winCount[3] = t_score;
}

public void Event_WeaponRank(Handle event, const char[] name, bool dontBroadcast)
{
	int rank = GetEventInt(event, "weaponrank");
	if(rank > g_TotalRounds)
	{
		g_TotalRounds = rank;
		CheckMaxRounds(g_TotalRounds);
	}
}

/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast)
{
	int timeleft;
	GetMapTimeLeft(timeleft);

	if(timeleft <= 0 || g_ChangeMapAtRoundEnd)
	{
		char map[32];
		GetNextMap(map, sizeof(map));
		PrintToChatAll("[MCE] Next Map: %s", map);
		PrintToChatAll("[MCE] Next Map: %s", map);
		PrintToChatAll("[MCE] Next Map: %s", map);
	}

	if(g_RoundCounting == RoundCounting_ArmsRace)
		return;

	if(g_ChangeMapAtRoundEnd)
	{
		g_ChangeMapAtRoundEnd = false;
		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
		g_ChangeMapInProgress = true;
	}

	int winner;
	if(strcmp(name, "round_win") == 0 || strcmp(name, "dod_round_win") == 0)
		winner = GetEventInt(event, "team"); // Nuclear Dawn & DoD:S
	else
		winner = GetEventInt(event, "winner");

	if(winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
		return;

	if(winner >= MAXTEAMS)
		SetFailState("Mod exceed maximum team count - Please file a bug report.");

	g_TotalRounds++;

	g_winCount[winner]++;

	if(!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
	{
		return;
	}

	CheckWinLimit(g_winCount[winner]);
	CheckMaxRounds(g_TotalRounds);
}

public void CheckWinLimit(int winner_score)
{
		int winlimit = GetConVarInt(g_Cvar_Winlimit);
		if(winlimit)
		{
			if(winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
			{
				if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE)
				{
					SetupWarningTimer(WarningType_Vote, MapChange_MapEnd);
					//InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
				}
			}
		}
}

public void CheckMaxRounds(int roundcount)
{
	int maxrounds = 0;

	if(g_RoundCounting == RoundCounting_ArmsRace)
		maxrounds = GameRules_GetProp("m_iNumGunGameProgressiveWeaponsCT");
	else if(g_RoundCounting == RoundCounting_MvM)
		maxrounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineMaxWaveCount");
	else
		return;

	if(maxrounds)
	{
		if(roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
		{
			if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE)
			{
				SetupWarningTimer(WarningType_Vote, MapChange_MapEnd);
				//InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
			}
		}
	}
}

public Action Event_PlayerDeath(Handle event, const char[] name, bool dontBroadcast)
{
    if(!GetArraySize(g_MapList) || g_HasVoteStarted)
        return Plugin_Continue;

    if(!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
        return Plugin_Continue;

    if(g_MapVoteCompleted)
        return Plugin_Continue;

    int fragger = GetClientOfUserId(GetEventInt(event, "attacker"));

    if(!fragger)
        return Plugin_Continue;

    if(GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
    {
        if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE)
        {
            SetupWarningTimer(WarningType_Vote, MapChange_MapEnd);
            //InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
        }
    }
    return Plugin_Continue;
}

public Action Command_Mapvote(int client, int args)
{
	ShowActivity2(client, "[MCE] ", "%t", "Initiated Vote Map");

	SetupWarningTimer(WarningType_Vote, MapChange_MapEnd, INVALID_HANDLE, true);

	//InitiateVote(MapChange_MapEnd, INVALID_HANDLE);

	return Plugin_Handled;
}

public Handle get_most_nominated_maps()
{
    int voteSize = GetVoteSize(2);
    int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
    Handle most_nominated_maps = CreateArray(arraySize);
    StringMap sm = new StringMap();

    for (int i = 0; i < MaxClients; i++)
    {
        for (int j = 0; j < GetArraySize(g_NominateList[i]); j++)
        {
            char map_iteration[PLATFORM_MAX_PATH];
            GetArrayString(g_NominateList[i], j, map_iteration, PLATFORM_MAX_PATH);
            //PrintToChatAll("map_iteration: %s", map_iteration);
            int nominate_count_for_particular_map = 0;
            sm.GetValue(map_iteration, nominate_count_for_particular_map);
            nominate_count_for_particular_map++;
            //if i is 0 its admin nominated map that most come into the vote
            if(!i)
            {
                nominate_count_for_particular_map = 999;
            }
            sm.SetValue(map_iteration, nominate_count_for_particular_map, true);
        }
    }
    static char map_[PLATFORM_MAX_PATH];

    for (int i = 0; i < voteSize; i++)
    {
        int max_count = 0;
        char picked_map[PLATFORM_MAX_PATH];
        StringMapSnapshot keys = sm.Snapshot();
        for (int j = 0; j < keys.Length; j++) 
        {
            int size = keys.KeyBufferSize(j);
            char[] buffer = new char[size];
            keys.GetKey(j, buffer, size);
            //PrintToChatAll("buffer: %s", buffer);
            if (StrEqual(buffer, "nominated_maps"))
            {
                continue;
            }
            int value = 0;
            sm.GetValue(buffer, value);
            //PrintToChatAll("value: %i", value);

            //first selection has most nominates, second selection second most etc etc
            if (value >= max_count)
            {
                max_count = value;
                strcopy(picked_map, sizeof(picked_map), buffer);
            }
        }

        delete keys;
        if (strlen(picked_map) == 0)
        {
            continue;
        }
        sm.Remove(picked_map);

        //2023 edit: respecting that only right amount of maps per group is allowed in vote
        int groups_[32];
        int groups[32];
        int groupsfound = InternalGetMapGroups(picked_map, groups, sizeof(groups));
        bool skip_nomination = false;
        for (int group = 0; group < groupsfound; group ++)
        {
            int groupcur = 0;
            int groupmax = InternalGetGroupMax(groups[group]);
            if (groupmax >= 0)
            {
                for (int j = 0; j < GetArraySize(most_nominated_maps); j++)
                {
                    //Native_GetMapGroupRestriction
                    GetArrayString(most_nominated_maps, j, map_, PLATFORM_MAX_PATH);
                    int tmp = InternalGetMapGroups(map_, groups_, sizeof(groups_));
                    if (FindIntInArray(groups_, tmp, groups[group]) != -1)
                    {
                        groupcur++;
                    }
                    if (groupcur >= groupmax)
                    {
                        skip_nomination = true;
                        break;
                    }
                }
                if (skip_nomination)
                {
                    break;
                }
            }
        }
        if (skip_nomination)
        {
            continue;
        }
        PushArrayString(most_nominated_maps, picked_map);
        //PrintToChatAll("picked_map: %s", picked_map);
    }

    delete sm;
    return most_nominated_maps;
}

/**
 * Starts a new map vote
 *
 * @param when			When the resulting map change should occur.
 * @param inputlist		Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
 */
void InitiateVote(MapChange when, Handle inputlist=INVALID_HANDLE)
{
    g_WaitingForVote = true;
    g_WarningInProgress = false;
    int MenuRandomShuffleStart = 0;
    int MenuRandomShuffleStop = 0;

    // Check if a vote is in progress first
    if(IsVoteInProgress())
    {
        // Cant start a vote, try again in 5 seconds.
        //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);

        CPrintToChatAll("[MCE] %t", "Cannot Start Vote", FAILURE_TIMER_LENGTH);
        Handle data;
        g_RetryTimer = CreateDataTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);

        /* Mapchooser Extended */
        WritePackCell(data, FAILURE_TIMER_LENGTH);

        if(GetConVarBool(g_Cvar_RunOff) && g_RunoffCount > 0)
            WritePackString(data, "Revote Warning");
        else
            WritePackString(data, "Vote Warning");
        /* End Mapchooser Extended */

        WritePackCell(data, view_as<int>(when));
        WritePackCell(data, view_as<int>(inputlist));
        ResetPack(data);
        return;
    }

    /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
    if(g_MapVoteCompleted && g_ChangeMapInProgress)
        return;

    CheckMapRestrictions(true, true);
    CreateNextVote();

    g_ChangeTime = when;

    g_WaitingForVote = false;

    g_HasVoteStarted = true;

    g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel);

    g_AddNoVote = GetConVarBool(g_Cvar_NoVoteOption);

    // Block Vote Slots
    if(GetConVarBool(g_Cvar_BlockSlots))
    {
        Handle radioStyle = GetMenuStyleHandle(MenuStyle_Radio);

        if(GetMenuStyle(g_VoteMenu) == radioStyle)
        {
            g_BlockedSlots = true;
            AddMenuItem(g_VoteMenu, LINE_ONE, "Choose something...", ITEMDRAW_DISABLED);
            AddMenuItem(g_VoteMenu, LINE_TWO, "...will ya?", ITEMDRAW_DISABLED);
            MenuRandomShuffleStart += 2;

            if(!g_AddNoVote) {
                AddMenuItem(g_VoteMenu, LINE_SPACER, "", ITEMDRAW_SPACER);
                MenuRandomShuffleStart++;
            }
        }
        else
            g_BlockedSlots = false;
    }

    if(g_AddNoVote)
        SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE);

    SetMenuTitle(g_VoteMenu, "Vote Nextmap");
    SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);

    /* Call OnMapVoteStarted() Forward */
//	Call_StartForward(g_MapVoteStartedForward);
//	Call_Finish();

    /**
     * TODO: Make a proper decision on when to clear the nominations list.
     * Currently it clears when used, and stays if an external list is provided.
     * Is this the right thing to do? External lists will probably come from places
     * like sm_mapvote from the adminmenu in the future.
     */

    static char map[PLATFORM_MAX_PATH];

    /* No input given - User our internal nominations and maplist */
    if(inputlist == INVALID_HANDLE)
    {
        Handle randomizeList = INVALID_HANDLE;
        //2023 edit to allow multiple nominations per player
        Handle most_nominated_maps = get_most_nominated_maps();

        int voteSize = GetVoteSize(2); //voteSize wrong size probably for my for loop

        if(GetConVarBool(g_Cvar_RandomizeNominations))
            randomizeList = CloneArray(most_nominated_maps);

        int nominateCount = GetArraySize(most_nominated_maps);


        /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
        int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;

        bool extendFirst = GetConVarBool(g_Cvar_ExtendPosition);

        if(extendFirst) {
            AddExtendToMenu(g_VoteMenu, when);
            MenuRandomShuffleStart++;
        }

        for(int i = 0; i < nominationsToAdd; i++)
        {
            GetArrayString(most_nominated_maps, i, map, PLATFORM_MAX_PATH);

            if(randomizeList == INVALID_HANDLE)
                AddMapItem(map);

            RemoveStringFromArray(g_NextMapList, map);

            /* Notify Nominations that this map is now free */
            Call_StartForward(g_NominationsResetForward);
            Call_PushString(map);
            Call_Finish();
        }

        /* Clear out the rest of the nominations array */
        for(int i = nominationsToAdd; i < nominateCount; i++)
        {
            //2023 edit: might need to run all g_NominateList[client] through this instead
            GetArrayString(most_nominated_maps, i, map, PLATFORM_MAX_PATH);
            /* These maps shouldn't be excluded from the vote as they weren't really nominated at all */

            /* Notify Nominations that this map is now free */
            Call_StartForward(g_NominationsResetForward);
            Call_PushString(map);
            Call_Finish();
        }

        /* There should currently be 'nominationsToAdd' unique maps in the vote */

        int i = nominationsToAdd;
        int count = 0;
        int availableMaps = GetArraySize(g_NextMapList);

        if(i < voteSize && availableMaps == 0)
        {
            if(i == 0)
            {
                LogError("No maps available for vote.");
                return;
            }
            else
            {
                LogMessage("Not enough maps to fill map list, reducing map count. Adjust mce_include and mce_exclude to avoid this warning.");
                voteSize = i;
            }
        }

        while(i < voteSize)
        {
            GetArrayString(g_NextMapList, count, map, PLATFORM_MAX_PATH);
            count++;

            if(randomizeList == INVALID_HANDLE)
            {
                /* Insert the map and increment our count */
                AddMapItem(map);
            }
            else
                PushArrayString(randomizeList, map);
            i++;

            //Run out of maps, this will have to do.
            if(count >= availableMaps)
                break;
        }

        if(randomizeList != INVALID_HANDLE)
        {
            // Fisher-Yates Shuffle
            for(int j = GetArraySize(randomizeList) - 1; j >= 1; j--)
            {
                int k = GetRandomInt(0, j);
                SwapArrayItems(randomizeList, j, k);
            }

            for(int j = 0; j < GetArraySize(randomizeList); j++)
            {
                GetArrayString(randomizeList, j, map, PLATFORM_MAX_PATH);
                AddMapItem(map);
            }

            delete randomizeList;
            randomizeList = INVALID_HANDLE;
            delete most_nominated_maps;
            most_nominated_maps = INVALID_HANDLE;
        }

        /* Wipe out our nominations list - Nominations have already been informed of this */
        g_NominateCount = 0;
        g_NominateReservedCount = 0;
        ClearArray(g_NominateOwners);
        for (int j = 0; j < MaxClients; j++)
        {
            ClearArray(g_NominateList[j]);
        }

        if(!extendFirst) {
            AddExtendToMenu(g_VoteMenu, when);
            MenuRandomShuffleStop++;
        }
    }
    else //We were given a list of maps to start the vote with
    {
        int size = GetArraySize(inputlist);

        for(int i = 0; i < size; i++)
        {
            GetArrayString(inputlist, i, map, PLATFORM_MAX_PATH);

            if(IsMapValid(map))
            {
                AddMapItem(map);
            }
            // New in Mapchooser Extended
            else if(StrEqual(map, VOTE_DONTCHANGE))
            {
                AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Dont Change");
            }
            else if(StrEqual(map, VOTE_EXTEND))
            {
                AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
            }
        }
        delete inputlist;
    }

    int voteDuration = GetConVarInt(g_Cvar_VoteDuration);

    //SetMenuExitButton(g_VoteMenu, false);

    if(GetVoteSize(2) <= GetMaxPageItems(GetMenuStyle(g_VoteMenu)))
    {
        //This is necessary to get items 9 and 0 as usable voting items
        SetMenuPagination(g_VoteMenu, MENU_NO_PAGINATION);
    }

    if(GetConVarInt(g_Cvar_ShufflePerClient))
        MenuShufflePerClient(g_VoteMenu, MenuRandomShuffleStart, GetMenuItemCount(g_VoteMenu) - MenuRandomShuffleStop);

    VoteMenuToAll(g_VoteMenu, voteDuration);

    /* Call OnMapVoteStarted() Forward */
    Call_StartForward(g_MapVoteStartForward); // Deprecated
    Call_Finish();

    Call_StartForward(g_MapVoteStartedForward);
    Call_Finish();

    LogAction(-1, -1, "Voting for next map has started.");
    CPrintToChatAll("[MCE] %t", "Nextmap Voting Started");
}

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

    Call_StartForward(g_MapVoteEndForward);
    Call_PushString(map);
    Call_Finish();

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

        int time;
        if(GetMapTimeLimit(time))
        {
            if(time > 0)
                ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);
        }

        int winlimit = GetConVarInt(g_Cvar_Winlimit);
        if(winlimit)
            SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));

        int maxrounds = GetConVarInt(g_Cvar_Maxrounds);
        if(maxrounds)
            SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));

        int fraglimit = GetConVarInt(g_Cvar_Fraglimit);
        if(fraglimit)
            SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));

        CPrintToChatAll("[MCE] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100.0), num_votes);
        LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");
        CPrintToChatAll("[MCE] Available Extends: %d", GetConVarInt(g_Cvar_Extend) - g_Extends);

        // We extended, so well have to vote again.
        g_RunoffCount = 0;
        g_HasVoteStarted = false;
        SetupTimeleftTimer();

    }
    else if(strcmp(map, VOTE_DONTCHANGE, false) == 0)
    {
        CPrintToChatAll("[MCE] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100.0), num_votes);
        LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");

        g_RunoffCount = 0;
        g_HasVoteStarted = false;
        SetupTimeleftTimer();
    }
    else
    {
        if(g_ChangeTime == MapChange_MapEnd)
        {
            SetNextMap(map);
        }
        else if(g_ChangeTime == MapChange_Instant)
        {
            Handle data;
            CreateDataTimer(4.0, Timer_ChangeMap, data);
            WritePackString(data, map);
            g_ChangeMapInProgress = false;
        }
        else // MapChange_RoundEnd
        {
            SetNextMap(map);
            g_ChangeMapAtRoundEnd = true;
        }

        g_HasVoteStarted = false;
        g_MapVoteCompleted = true;

        CPrintToChatAll("[MCE] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100.0), num_votes);
        LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
    }
}

public void Handler_MapVoteFinished(Handle menu,
									int num_votes,
									int num_clients,
									const int[][] client_info,
									int num_items,
									const int[][] item_info)
{
	// Implement revote logic - Only run this` block if revotes are enabled and this isn't the last revote
	if(GetConVarBool(g_Cvar_RunOff) && num_items > 1 && g_RunoffCount < GetConVarInt(g_Cvar_MaxRunOffs))
	{
		g_RunoffCount++;
		int highest_votes = item_info[0][VOTEINFO_ITEM_VOTES];
		int required_percent = GetConVarInt(g_Cvar_RunOffPercent);
		int required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100.0);

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

			//Revote is needed
			int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
			Handle mapList = CreateArray(arraySize);

			for(int i = 0; i < num_items; i++)
			{
				if(item_info[i][VOTEINFO_ITEM_VOTES] == highest_votes)
				{
					static char map[PLATFORM_MAX_PATH];

					GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, PLATFORM_MAX_PATH);
					PushArrayString(mapList, map);
				}
				else
					break;
			}

			CPrintToChatAll("[MCE] %t", "Tie Vote", GetArraySize(mapList));
			SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_ChangeTime), mapList);
			return;
		}
		else if(highest_votes < required_votes)
		{
			g_HasVoteStarted = false;

			//Revote is needed
			int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
			Handle mapList = CreateArray(arraySize);

			static char map1[PLATFORM_MAX_PATH];
			GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map1, PLATFORM_MAX_PATH);

			PushArrayString(mapList, map1);

			// We allow more than two maps for a revote if they are tied
			for(int i = 1; i < num_items; i++)
			{
				if(GetArraySize(mapList) < 2 || item_info[i][VOTEINFO_ITEM_VOTES] == item_info[i - 1][VOTEINFO_ITEM_VOTES])
				{
					static char map[PLATFORM_MAX_PATH];
					GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, PLATFORM_MAX_PATH);
					PushArrayString(mapList, map);
				}
				else
					break;
			}

			CPrintToChatAll("[MCE] %t", "Revote Is Needed", required_percent);
			SetupWarningTimer(WarningType_Revote, view_as<MapChange>(g_ChangeTime), mapList);
			return;
		}
	}

	// No revote needed, continue as normal.
	Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
}

public int Handler_MapVoteMenu(Handle menu, MenuAction action, int param1, int param2)
{
	switch(action)
	{
		case MenuAction_End:
		{
			g_VoteMenu = INVALID_HANDLE;
			delete menu;
		}

		case MenuAction_Display:
		{
			static char buffer[255];
			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
			Handle panel = view_as<Handle>(param2);
			SetPanelTitle(panel, buffer);
			DrawPanelText(panel, "Warning: The Position of the Maps are different for each Player.");
		}

		case MenuAction_DisplayItem:
		{
			char map[PLATFORM_MAX_PATH];
			char buffer[255];
			int mark = GetConVarInt(g_Cvar_MarkCustomMaps);

			GetMenuItem(menu, param2, map, PLATFORM_MAX_PATH, _, _, _, param1);

			if(StrEqual(map, VOTE_EXTEND, false))
			{
				Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
			}
			else if(StrEqual(map, VOTE_DONTCHANGE, false))
			{
				Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
			}
			// Mapchooser Extended
			else if(StrEqual(map, LINE_ONE, false))
			{
				Format(buffer, sizeof(buffer),"%T", "Line One", param1);
			}
			else if(StrEqual(map, LINE_TWO, false))
			{
				Format(buffer, sizeof(buffer),"%T", "Line Two", param1);
			}
			// Note that the first part is to discard the spacer line
			else if(!StrEqual(map, LINE_SPACER, false))
			{
				if(mark == 1 && !InternalIsMapOfficial(map))
				{
					Format(buffer, sizeof(buffer), "%T", "Custom Marked", param1, map);
				}
				else if(mark == 2 && !InternalIsMapOfficial(map))
				{
					Format(buffer, sizeof(buffer), "%T", "Custom", param1, map);
				}
				else if(InternalGetMapVIPRestriction(map))
				{
					Format(buffer, sizeof(buffer), "%s (%T)", map, "VIP Nomination", param1);
				}
			}

			if(buffer[0] != '\0')
			{
				return RedrawMenuItem(buffer);
			}
			// End Mapchooser Extended
		}

		case MenuAction_VoteCancel:
		{
			// If we receive 0 votes, pick at random.
			if(param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
			{
				int count = GetMenuItemCount(menu);

				int item;
				static char map[PLATFORM_MAX_PATH];

				do
				{
					int startInt = 0;
					if(g_BlockedSlots)
					{
						if(g_AddNoVote)
						{
							startInt = 2;
						}
						else
						{
							startInt = 3;
						}
					}
					item = GetRandomInt(startInt, count - 1);

					GetMenuItem(menu, item, map, PLATFORM_MAX_PATH, _, _, _, param1);
				}
				while(strcmp(map, VOTE_EXTEND, false) == 0);

				SetNextMap(map);
				g_MapVoteCompleted = true;
			}

			g_HasVoteStarted = false;
		}
	}

	return 0;
}

public Action Timer_ChangeMap(Handle hTimer, Handle dp)
{
	g_ChangeMapInProgress = false;

	char map[PLATFORM_MAX_PATH];

	if(dp == INVALID_HANDLE)
	{
		if(!GetNextMap(map, PLATFORM_MAX_PATH))
		{
			//No passed map and no set nextmap. fail!
			return Plugin_Stop;
		}
	}
	else
	{
		ResetPack(dp);
		ReadPackString(dp, map, PLATFORM_MAX_PATH);
	}

	ForceChangeLevel(map, "Map Vote");

	return Plugin_Stop;
}

bool RemoveStringFromArray(Handle array, char[] str)
{
	int index = FindStringInArray(array, str);
	if(index != -1)
	{
		RemoveFromArray(array, index);
		return true;
	}

	return false;
}

void CreateNextVote()
{
    assert(g_NextMapList)
    ClearArray(g_NextMapList);

    static char map[PLATFORM_MAX_PATH];
    Handle tempMaps = CloneArray(g_MapList);

    GetCurrentMap(map, PLATFORM_MAX_PATH);
    RemoveStringFromArray(tempMaps, map);

    if(GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps) && InternalAreRestrictionsActive())
    {
        StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot();
        for(int i = 0; i < OldMapListSnapshot.Length; i++)
        {
            OldMapListSnapshot.GetKey(i, map, sizeof(map));
            RemoveStringFromArray(tempMaps, map);
        }
        delete OldMapListSnapshot;
    }

    if(InternalAreRestrictionsActive())
    {
        StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot();
        for(int i = 0; i < TimeMapListSnapshot.Length; i++)
        {
            TimeMapListSnapshot.GetKey(i, map, sizeof(map));
            int Cooldown;
            g_TimeMapList.GetValue(map, Cooldown);

            if(Cooldown > GetTime())
                RemoveStringFromArray(tempMaps, map);
        }
        delete TimeMapListSnapshot;
    }

    int voteSize = GetVoteSize(2);
    int limit = (voteSize < GetArraySize(tempMaps) ? voteSize : GetArraySize(tempMaps));

    // group -> number of maps nominated from group
    StringMap groupmap = new StringMap();
    char groupstr[8];

    // populate groupmap with maps from nomination list
    static char map_[PLATFORM_MAX_PATH];
    int groups_[32];

    //2023 edit
    Handle most_nominated_maps = get_most_nominated_maps();
    for(int i = 0; i < GetArraySize(most_nominated_maps); i++)
    {
        GetArrayString(most_nominated_maps, i, map_, PLATFORM_MAX_PATH);
        int groupsfound = InternalGetMapGroups(map_, groups_, sizeof(groups_));
        for(int group = 0; group < groupsfound; group++)
        {
            IntToString(group, groupstr, sizeof(groupstr));
            int groupcur = 0;
            groupmap.GetValue(groupstr, groupcur);
            groupcur++;
            groupmap.SetValue(groupstr, groupcur, true);
        }
    }

    // find random maps which honor all restrictions
    for(int i = 0; i < limit; i++)
    {
        int b;
        for(int j = 0; j < 1000; j++)
        {
            b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
            GetArrayString(tempMaps, b, map, PLATFORM_MAX_PATH);

            if(!InternalAreRestrictionsActive())
                break;

            if(InternalGetMapVIPRestriction(map))
                continue;

            if(InternalGetMapTimeRestriction(map) != 0)
                continue;

            if(InternalGetMapPlayerRestriction(map) != 0)
                continue;

            bool okay = true;

            int groups[32];
            int groupsfound = InternalGetMapGroups(map, groups, sizeof(groups));
            for(int group = 0; group < groupsfound; group++)
            {
                IntToString(group, groupstr, sizeof(groupstr));

                int groupmax = InternalGetGroupMax(groups[group]);
                if(groupmax >= 0)
                {
                    int groupcur = 0;
                    groupmap.GetValue(groupstr, groupcur);

                    if(groupcur >= groupmax)
                    {
                        okay = false;
                        break;
                    }

                    groupcur++;
                    groupmap.SetValue(groupstr, groupcur, true);
                }
            }

            if(okay)
                break;
        }
        PushArrayString(g_NextMapList, map);
        RemoveFromArray(tempMaps, b);
    }

    delete groupmap;
    delete tempMaps;
}

bool CanVoteStart()
{
    if(g_WaitingForVote || g_HasVoteStarted)
        return false;
    return true;
}

NominateResult InternalNominateMap(char[] map, int owner)
{
    if(!IsMapValid(map))
    {
        return Nominate_InvalidMap;
    }


    /* Look to replace an existing nomination by this client - Nominations made with owner = 0 arent replaced */
    //2023 edit: change clients first nomination out of the clients multiple nominations, make a check if client filled all his nomination slots
    //currently hard coded to 3 maps, just add a cvar to replace it with in the future
    if(owner && ((FindValueInArray(g_NominateOwners, owner)) != -1) && GetArraySize(g_NominateList[owner]) > 2)
    {
        char oldmap[PLATFORM_MAX_PATH];
        GetArrayString(g_NominateList[owner], 0, oldmap, PLATFORM_MAX_PATH);
        Call_StartForward(g_NominationsResetForward);
        Call_PushString(oldmap);
        Call_Finish();

        RemoveFromArray(g_NominateList[owner], 0);
        PushArrayString(g_NominateList[owner], map);
        return Nominate_Replaced;
    }


    /* Too many nominated maps. */
    //2023 edit: we dont want this check
    /*
    if(g_NominateCount >= GetVoteSize(0) && !force)
    {
        return Nominate_VoteFull;
    }
    */

    if (owner != 0 && g_NominateList[owner] != INVALID_HANDLE)
    {
        for (int j = 0; j < GetArraySize(g_NominateList[owner]); j++)
        {
            char map_iteration[PLATFORM_MAX_PATH];
            GetArrayString(g_NominateList[owner], j, map_iteration, PLATFORM_MAX_PATH);
            if (StrEqual(map, map_iteration, false))
            {
                return Nominate_InvalidMap;
            }
        }
    }
    
    PushArrayString(g_NominateList[owner], map);
    PushArrayCell(g_NominateOwners, owner); //maybe i only want to do this for the first nomination of each client
    if(owner == 0 && g_NominateReservedCount < GetVoteSize(1))
        g_NominateReservedCount++;
    else
        g_NominateCount++;

    while(GetArraySize(g_NominateList[owner]) > GetVoteSize(2)) 
    {
        char oldmap[PLATFORM_MAX_PATH];
        GetArrayString(g_NominateList[owner], 0, oldmap, PLATFORM_MAX_PATH);
        Call_StartForward(g_NominationsResetForward);
        Call_PushString(oldmap);
        int owner_ = GetArrayCell(g_NominateOwners, 0);
        Call_Finish();

        RemoveFromArray(g_NominateList[owner], 0);
        RemoveFromArray(g_NominateOwners, 0);
        if(owner_ == 0)
            g_NominateReservedCount--;
        else
            g_NominateCount--;
    }

    return Nominate_Added;
}

/* Add natives to allow nominate and initiate vote to be call */

/* native  bool NominateMap(const char[] map, bool force, &NominateError:error); */
public int Native_NominateMap(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return view_as<int>(InternalNominateMap(map, GetNativeCell(3)));
}

bool InternalRemoveNominationByMap(char[] map)
{
    for (int client = 0; client < MaxClients; client++)
    {
        for(int i = 0; i < GetArraySize(g_NominateList[client]); i++)
        {
            char oldmap[PLATFORM_MAX_PATH];
            GetArrayString(g_NominateList[client], i, oldmap, PLATFORM_MAX_PATH);

            if(strcmp(map, oldmap, false) == 0)
            {
                Call_StartForward(g_NominationsResetForward);
                Call_PushString(oldmap);
                Call_Finish();

                int owner = GetArrayCell(g_NominateOwners, i);
                if(owner)
                    g_NominateCount--;
                else
                    g_NominateReservedCount--;

                RemoveFromArray(g_NominateList[client], i);
                RemoveFromArray(g_NominateOwners, i);

                return true;
            }
        }
    }
    return false;
}

/* native  bool RemoveNominationByMap(const char[] map); */
public int Native_RemoveNominationByMap(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return view_as<int>(InternalRemoveNominationByMap(map));
}

bool InternalRemoveNominationByOwner(int owner)
{
	int index;

	if(owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
	{
		char oldmap[PLATFORM_MAX_PATH];
		GetArrayString(g_NominateList[owner], index, oldmap, PLATFORM_MAX_PATH);

		Call_StartForward(g_NominationsResetForward);
		Call_PushString(oldmap);
		Call_Finish();

		RemoveFromArray(g_NominateList[owner], index);
        //maybe only do once or change g_NominateOwners
		RemoveFromArray(g_NominateOwners, index);
		g_NominateCount--;

		return true;
	}

	return false;
}

/* native  bool RemoveNominationByOwner(owner); */
public int Native_RemoveNominationByOwner(Handle plugin, int numParams)
{
	return view_as<int>(InternalRemoveNominationByOwner(GetNativeCell(1)));
}

/* native InitiateMapChooserVote(); */
public int Native_InitiateVote(Handle plugin, int numParams)
{
	MapChange when = view_as<MapChange>(GetNativeCell(1));
	Handle inputarray = view_as<Handle>(GetNativeCell(2));

	LogAction(-1, -1, "Starting map vote because outside request");

	SetupWarningTimer(WarningType_Vote, when, inputarray);
	//InitiateVote(when, inputarray);
}

public int Native_CanVoteStart(Handle plugin, int numParams)
{
	return CanVoteStart();
}

public int Native_CheckVoteDone(Handle plugin, int numParams)
{
	return g_MapVoteCompleted;
}

public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams)
{
	return GetConVarBool(g_Cvar_EndOfMapVote);
}

public int Native_GetExcludeMapList(Handle plugin, int numParams)
{
	Handle array = view_as<Handle>(GetNativeCell(1));
	if(array == INVALID_HANDLE)
		return;

	static char map[PLATFORM_MAX_PATH];
	StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot();
	for(int i = 0; i < OldMapListSnapshot.Length; i++)
	{
		OldMapListSnapshot.GetKey(i, map, sizeof(map));
		PushArrayString(array, map);
	}
	delete OldMapListSnapshot;
}

public int Native_GetNominatedMapList(Handle plugin, int numParams)
{
    Handle maparray = view_as<Handle>(GetNativeCell(1));
    Handle ownerarray = view_as<Handle>(GetNativeCell(2));

    if(maparray == INVALID_HANDLE)
        return;

    static char map[PLATFORM_MAX_PATH];

    for (int client = 0; client < MaxClients; client++)
    {
        for(int i = 0; i < GetArraySize(g_NominateList[client]); i++)
        {
            GetArrayString(g_NominateList[client], i, map, PLATFORM_MAX_PATH);
            PushArrayString(maparray, map);

            // If the optional parameter for an owner list was passed, then we need to fill that out as well
            if(ownerarray != INVALID_HANDLE)
            {
                //int index = GetArrayCell(g_NominateOwners, i);
                PushArrayCell(ownerarray, client);
            }
        }
    }
}

// Functions new to Mapchooser Extended
stock void SetupWarningTimer(WarningType type, MapChange when=MapChange_MapEnd, Handle mapList=INVALID_HANDLE, bool force=false)
{
	if(!GetArraySize(g_MapList) || g_ChangeMapInProgress || g_HasVoteStarted || (!force && ((when == MapChange_MapEnd && !GetConVarBool(g_Cvar_EndOfMapVote)) || g_MapVoteCompleted)))
		return;

	bool interrupted = false;
	if(g_WarningInProgress && g_WarningTimer != INVALID_HANDLE)
	{
		interrupted = true;
		KillTimer(g_WarningTimer);
	}

	g_WarningInProgress = true;

	Handle forwardVote;
	Handle cvarTime;
	static char translationKey[64];

	switch(type)
	{
		case WarningType_Vote:
		{
			forwardVote = g_MapVoteWarningStartForward;
			cvarTime = g_Cvar_WarningTime;
			strcopy(translationKey, sizeof(translationKey), "Vote Warning");

		}

		case WarningType_Revote:
		{
			forwardVote = g_MapVoteRunoffStartForward;
			cvarTime = g_Cvar_RunOffWarningTime;
			strcopy(translationKey, sizeof(translationKey), "Revote Warning");

		}
	}

	if(!interrupted)
	{
		Call_StartForward(forwardVote);
		Call_Finish();
	}

	Handle data;
	g_WarningTimer = CreateDataTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
	WritePackCell(data, GetConVarInt(cvarTime));
	WritePackString(data, translationKey);
	WritePackCell(data, view_as<int>(when));
	WritePackCell(data, view_as<int>(mapList));
	ResetPack(data);
}

stock void InitializeOfficialMapList()
{
	// If this fails, we want it to have an empty adt_array
	if(ReadMapList(g_OfficialList,
		g_mapOfficialFileSerial,
		"official",
		MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_NO_DEFAULT)
		!= INVALID_HANDLE)
	{
		LogMessage("Loaded map list for %s.", g_GameModName);
	}
	// Check if the map list was ever loaded
	else if(g_mapOfficialFileSerial == -1)
	{
		LogMessage("No official map list found for %s. Consider submitting one!", g_GameModName);
	}
}

stock bool IsMapEndVoteAllowed()
{
	if(!GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
		return false;
	return true;
}

public int Native_IsMapOfficial(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalIsMapOfficial(map);
}

bool InternalIsMapOfficial(const char[] mapname)
{
	int officialMapIndex = FindStringInArray(g_OfficialList, mapname);
	return (officialMapIndex > -1);
}

public int Native_IsWarningTimer(Handle plugin, int numParams)
{
	return g_WarningInProgress;
}

public int Native_CanNominate(Handle plugin, int numParams)
{
	if(g_HasVoteStarted)
	{
		return view_as<int>(CanNominate_No_VoteInProgress);
	}

	if(g_MapVoteCompleted)
	{
		return view_as<int>(CanNominate_No_VoteComplete);
	}

	if(g_NominateCount >= GetVoteSize())
	{
		return view_as<int>(CanNominate_No_VoteFull);
	}

	return view_as<int>(CanNominate_Yes);
}

public int Native_ExcludeMap(Handle plugin, int numParams)
{
	if(!InternalAreRestrictionsActive())
		return true;

	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	int Cooldown;
	int Mode = GetNativeCell(3);

	if(Mode == 0)
	{
		Cooldown = InternalGetMapCooldown(map);
	}
	else if(Mode == 1)
	{
		Cooldown = GetNativeCell(2);
	}
	else if(Mode == 2)
	{
		g_OldMapList.GetValue(map, Cooldown);
		int NewCooldown = GetNativeCell(2);
		if(NewCooldown > Cooldown)
			Cooldown = NewCooldown;
	}

	g_OldMapList.SetValue(map, Cooldown, true);
	InternalStoreMapCooldowns();

	return true;
}

public int Native_ExcludeMapTime(Handle plugin, int numParams)
{
	if(!InternalAreRestrictionsActive())
		return true;

	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	int Cooldown;
	int Mode = GetNativeCell(3);

	if(Mode == 0)
	{
		Cooldown = InternalGetMapCooldownTime(map);
	}
	else if(Mode == 1)
	{
		Cooldown = GetNativeCell(2);
	}
	else if(Mode == 2)
	{
		g_TimeMapList.GetValue(map, Cooldown);
		int NewCooldown = GetTime() + GetNativeCell(2);
		if(NewCooldown > Cooldown)
			Cooldown = GetNativeCell(2);
		else
			return true;
	}

	Cooldown += GetTime();
	g_TimeMapList.SetValue(map, Cooldown, true);
	InternalStoreMapCooldowns();

	return true;
}

public int Native_GetMapCooldown(Handle plugin, int numParams)
{
	if(!InternalAreRestrictionsActive())
		return 0;

	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	int Cooldown = 0;
	g_OldMapList.GetValue(map, Cooldown);

	return Cooldown;
}

public int Native_GetMapCooldownTime(Handle plugin, int numParams)
{
	if(!InternalAreRestrictionsActive())
		return 0;

	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	int Cooldown = 0;
	g_TimeMapList.GetValue(map, Cooldown);

	return Cooldown;
}

public int Native_GetMapMinTime(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapMinTime(map);
}

public int Native_GetMapMaxTime(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapMaxTime(map);
}

public int Native_GetMapMinPlayers(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapMinPlayers(map);
}

public int Native_GetMapMaxPlayers(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapMaxPlayers(map);
}

public int Native_GetMapTimeRestriction(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapTimeRestriction(map);
}

public int Native_GetMapPlayerRestriction(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	return InternalGetMapPlayerRestriction(map);
}

public int Native_GetMapGroups(Handle plugin, int numParams)
{
	int len;
	GetNativeStringLength(1, len);
	int size = GetNativeCell(3);

	if(len <= 0)
		return -999;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	int[] groups = new int[size];
	int found = InternalGetMapGroups(map, groups, size);
	if(found >= 0)
		SetNativeArray(2, groups, size);
	return found;
}

//GetMapGroupRestriction
public int Native_GetMapGroupRestriction(Handle plugin, int numParams)
{
    GetNativeCell(2);
    int len;
    GetNativeStringLength(1, len);

    if(len <= 0)
        return -999;

    char[] map = new char[len+1];
    GetNativeString(1, map, len+1);

    int groups[32];
    int groupsfound = InternalGetMapGroups(map, groups, sizeof(groups));

    for(int group = 0; group < groupsfound; group ++)
    {
        int groupcur = 0;
        int groupmax = InternalGetGroupMax(groups[group]);

        if(groupmax >= 0)
        {
            static char map_[PLATFORM_MAX_PATH];
            char kv_map[PLATFORM_MAX_PATH];
            int groups_[32];
            KeyValues kv = CreateKeyValues("cur_groups");
            for (int clienti = 0; clienti < MaxClients; clienti++)
            {
                for(int i = 0; i < GetArraySize(g_NominateList[clienti]); i++)
                {
                    GetArrayString(g_NominateList[clienti], i, map_, PLATFORM_MAX_PATH);
                    int tmp = InternalGetMapGroups(map_, groups_, sizeof(groups_));
                    kv.GetString(map_, kv_map, sizeof(kv_map), "not_found");
                    if(FindIntInArray(groups_, tmp, groups[group]) != -1 && StrEqual(kv_map, "not_found"))
                    {
                        groupcur++;
                        kv.SetString(map_, map_);
                    }
                }
            }
            delete kv;
        }
    }

    return -1;
}

public int Native_GetMapVIPRestriction(Handle plugin, int numParams)
{
	int client = GetNativeCell(2);
	int len;
	GetNativeStringLength(1, len);

	if(len <= 0)
		return false;

	char[] map = new char[len+1];
	GetNativeString(1, map, len+1);

	// Check if client should bypass vip restrictions
	if(client >= 1 && client <= MaxClients)
	{
		// Client has bypass flag, dont return vip restrictions
		if(CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_CHEATS))
			return false;

		// Client has vip flag, dont return vip restrictions
		if(CheckCommandAccess(client, "sm_nominate_vip", ADMFLAG_CUSTOM1))
			return false;
	}

	return InternalGetMapVIPRestriction(map);
}

public int Native_GetExtendsLeft(Handle plugin, int numParams)
{
	return GetConVarInt(g_Cvar_Extend) - g_Extends;
}

public int Native_AreRestrictionsActive(Handle plugin, int numParams)
{
	return InternalAreRestrictionsActive();
}

public int Native_SimulateMapEnd(Handle plugin, int numParams)
{
	OnMapEnd();
}

stock void AddMapItem(const char[] map)
{
	AddMenuItem(g_VoteMenu, map, map);
}

stock void GetMapItem(Handle menu, int position, char[] map, int mapLen)
{
	GetMenuItem(menu, position, map, mapLen, _, _, _, -1);
}

stock void AddExtendToMenu(Handle menu, MapChange when)
{
	/* Do we add any special items? */
	// Moved for Mapchooser Extended

	if((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
	{
		// Built-in votes doesnt have "Dont Change", send Extend instead
		AddMenuItem(menu, VOTE_DONTCHANGE, "Dont Change");
	}
	else if(GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
	{
		AddMenuItem(menu, VOTE_EXTEND, "Extend Map");
	}
}

// 0 = IncludeMaps, 1 = Reserved, 2 = IncludeMaps+Reserved
stock int GetVoteSize(int what=0)
{
	int includeMaps = GetConVarInt(g_Cvar_IncludeMaps);
	int includeMapsReserved = GetConVarInt(g_Cvar_IncludeMapsReserved);

	if(what == 0)
		return includeMaps;
	else if(what == 1)
		return includeMapsReserved;
	else if(what == 2)
		return includeMaps + includeMapsReserved;

	return 0;
}

stock int InternalGetMapCooldown(const char[] map)
{
	int Cooldown = g_Cvar_ExcludeMaps.IntValue;

	if(g_Config && g_Config.JumpToKey(map))
	{
		Cooldown = g_Config.GetNum("Cooldown", Cooldown);
		g_Config.Rewind();
	}

	return Cooldown;
}

stock int InternalGetMapCooldownTime(const char[] map)
{
	char time[16];
	g_Cvar_ExcludeMapsTime.GetString(time, sizeof(time));
	int Cooldown = TimeStrToSeconds(time);

	if(g_Config && g_Config.JumpToKey(map))
	{
		g_Config.GetString("CooldownTime", time, sizeof(time), "");
		if(time[0])
			Cooldown = TimeStrToSeconds(time);

		g_Config.Rewind();
	}

	return Cooldown;
}

void CheckMapRestrictions(bool time = false, bool players = false)
{
    if(!InternalAreRestrictionsActive())
        return;

    static char map[PLATFORM_MAX_PATH];

    for (int client = 0; client < MaxClients; client++)
    {
        if(CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_CHEATS, true))
            continue;
        for (int i = 0; i < GetArraySize(g_NominateList[client]); i++)
        {
            bool remove;
            GetArrayString(g_NominateList[client], i, map, PLATFORM_MAX_PATH);

            if (time)
            {
                int TimeRestriction = InternalGetMapTimeRestriction(map);
                if(TimeRestriction)
                {
                    remove = true;
                    
                    if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
                    {
                        CPrintToChat(client, "[MCE] %t", "Nomination Removed Time Error", map);
                    }
                }
            }

            if (players)
            {
                int PlayerRestriction = InternalGetMapPlayerRestriction(map);
                if(PlayerRestriction)
                {
                    remove = true;

                    if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
                    {
                        if(PlayerRestriction < 0)
                            CPrintToChat(client, "[MCE] %t", "Nomination Removed MinPlayers Error", map);
                        else
                            CPrintToChat(client, "[MCE] %t", "Nomination Removed MaxPlayers Error", map);
                    }
                }
            }

            if (remove)
            {
                if (strlen(map) > 0)
                {
                    Call_StartForward(g_NominationsResetForward);
                    Call_PushString(map);
                    Call_Finish();
                }

                RemoveFromArray(g_NominateList[client], i);
                RemoveFromArray(g_NominateOwners, i);
                g_NominateCount--;
            }
        }
    }
}

stock int InternalGetMapMinTime(const char[] map)
{
	int MinTime = 0;

	if(g_Config && g_Config.JumpToKey(map))
	{
		MinTime = g_Config.GetNum("MinTime", MinTime);
		g_Config.Rewind();
	}

	return MinTime;
}

stock int InternalGetMapMaxTime(const char[] map)
{
	int MaxTime = 0;

	if(g_Config && g_Config.JumpToKey(map))
	{
		MaxTime = g_Config.GetNum("MaxTime", MaxTime);
		g_Config.Rewind();
	}

	return MaxTime;
}

stock int InternalGetMapMinPlayers(const char[] map)
{
	int MinPlayers = 0;

	if(g_Config && g_Config.JumpToKey(map))
	{
		MinPlayers = g_Config.GetNum("MinPlayers", MinPlayers);
		g_Config.Rewind();
	}

	return MinPlayers;
}

stock int InternalGetMapMaxPlayers(const char[] map)
{
	int MaxPlayers = 0;

	if(g_Config && g_Config.JumpToKey(map))
	{
		MaxPlayers = g_Config.GetNum("MaxPlayers", MaxPlayers);
		g_Config.Rewind();
	}

	return MaxPlayers;
}

stock int InternalGetMapGroups(const char[] map, int[] groups, int size)
{
	int found = 0;
	if(g_Config && g_Config.JumpToKey("_groups"))
	{
		if(!g_Config.GotoFirstSubKey(false))
		{
			g_Config.Rewind();
			return -999;
		}

		do
		{
			char groupstr[8];
			g_Config.GetSectionName(groupstr, sizeof(groupstr));
			int group = StringToInt(groupstr);
			if(g_Config.JumpToKey(map, false))
			{
				groups[found++] = group;
				if(found >= size)
				{
					g_Config.Rewind();
					return found;
				}
				g_Config.GoBack();
			}
		} while(g_Config.GotoNextKey());

		g_Config.Rewind();
	}

	return found;
}

stock int InternalGetGroupMax(int group)
{
	char groupstr[8];
	IntToString(group, groupstr, sizeof(groupstr));
	if(g_Config && g_Config.JumpToKey("_groups"))
	{
		if(g_Config.JumpToKey(groupstr, false))
		{
			int max = g_Config.GetNum("_max", -1);
			g_Config.Rewind();
			return max;
		}

		g_Config.Rewind();
	}

	return -1;
}

// 0 = Okay
// >0 = Minutes till Okay
stock int InternalGetMapTimeRestriction(const char[] map)
{
	char sTime[8];
	FormatTime(sTime, sizeof(sTime), "%H%M");

	int CurTime = StringToInt(sTime);
	int MinTime = InternalGetMapMinTime(map);
	int MaxTime = InternalGetMapMaxTime(map);

	//Wrap around.
	CurTime = (CurTime <= MinTime) ? CurTime + 2400 : CurTime;
	MaxTime = (MaxTime <= MinTime) ? MaxTime + 2400 : MaxTime;

	if (!(MinTime <= CurTime <= MaxTime))
	{
		//Wrap around.
		MinTime = (MinTime <= CurTime) ? MinTime + 2400 : MinTime;
		MinTime = (MinTime <= MaxTime) ? MinTime + 2400 : MinTime;

		// Convert our 'time' to minutes.
		CurTime = (RoundToFloor(float(CurTime / 100)) * 60) + (CurTime % 100);
		MinTime = (RoundToFloor(float(MinTime / 100)) * 60) + (MinTime % 100);
		MaxTime = (RoundToFloor(float(MaxTime / 100)) * 60) + (MaxTime % 100);

		return MinTime - CurTime;
	}

	return 0;
}

public void OnClientPostAdminCheck(int client)
{
    is_bot_player[client] = false;
    char auth[50];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (StrEqual("[U:1:1221121532]", auth, false) || StrEqual("STEAM_0:0:610560766", auth, false))
    {
        is_bot_player[client] = true;
    }
    if (StrEqual("[U:1:408797742]", auth, false) || StrEqual("STEAM_0:0:204398871", auth, false))
    {
        is_bot_player[client] = true;
    }
    if (StrEqual("[U:1:1036189204]", auth, false) || StrEqual("STEAM_0:0:518094602", auth, false))
    {
        is_bot_player[client] = true;
    }
    if (StrEqual("[U:1:120378081]", auth, false) || StrEqual("STEAM_0:1:60189040", auth, false))
    {
        is_bot_player[client] = true;
    }
}

// <0 = Less than MinPlayers
// 0 = Okay
// >0 = More than MaxPlayers
stock int InternalGetMapPlayerRestriction(const char[] map)
{
	//int NumPlayers = GetClientCount(false);
    int NumPlayers = 0;
    //excluding non connected players, fakeclients, sourceTV, autism bots and nosteamers from player count restriction
    for (int client = 1; client <= MaxClients; client++)
    {
        if (IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client) && !is_bot_player[client]
        && PM_IsPlayerSteam(client))
        {
            NumPlayers++;
        }
    }

    int MinPlayers = InternalGetMapMinPlayers(map);
    int MaxPlayers = InternalGetMapMaxPlayers(map);

    if(MinPlayers && NumPlayers < MinPlayers)
        return NumPlayers - MinPlayers;

    if(MaxPlayers && NumPlayers > MaxPlayers)
        return NumPlayers - MaxPlayers;

    return 0;
}

stock bool InternalAreRestrictionsActive()
{
	if (!GetConVarBool(g_Cvar_NoRestrictionTimeframeEnable))
		return true;

	char sTime[8];
	FormatTime(sTime, sizeof(sTime), "%H%M");

	int CurTime = StringToInt(sTime);
	int MinTime = GetConVarInt(g_Cvar_NoRestrictionTimeframeMinTime);
	int MaxTime = GetConVarInt(g_Cvar_NoRestrictionTimeframeMaxTime);

	//Wrap around.
	CurTime = (CurTime <= MinTime) ? CurTime + 2400 : CurTime;
	MaxTime = (MaxTime <= MinTime) ? MaxTime + 2400 : MaxTime;

	if ((MinTime <= CurTime <= MaxTime))
	{
		return false;
	}

	return true;
}

stock int FindIntInArray(int[] array, int size, int value)
{
	for(int i = 0; i < size; i++)
	{
		if(array[i] == value)
			return i;
	}

	return -1;
}

stock bool InternalGetMapVIPRestriction(const char[] map)
{
	int VIP = 0;

	if(g_Config && g_Config.JumpToKey(map))
	{
		VIP = g_Config.GetNum("VIP", VIP);
		g_Config.Rewind();
	}

	return view_as<bool>(VIP);
}

stock void InternalRestoreMapCooldowns()
{
	char sCooldownFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, sCooldownFile, sizeof(sCooldownFile), "configs/mapchooser_extended/cooldowns.cfg");

	if(!FileExists(sCooldownFile))
	{
		LogMessage("Could not find cooldown file: \"%s\"", sCooldownFile);
		return;
	}

	KeyValues Cooldowns = new KeyValues("mapchooser_extended");

	if(!Cooldowns.ImportFromFile(sCooldownFile))
	{
		LogMessage("Unable to load cooldown file: \"%s\"", sCooldownFile);
		delete Cooldowns;
		return;
	}

	if(!Cooldowns.GotoFirstSubKey(true))
	{
		LogMessage("Unable to goto first sub key: \"%s\"", sCooldownFile);
		delete Cooldowns;
		return;
	}

	int Cooldown;
	char map[PLATFORM_MAX_PATH];

	do
	{
		if(!Cooldowns.GetSectionName(map, sizeof(map)))
		{
			LogMessage("Unable to get section name: \"%s\"", sCooldownFile);
			delete Cooldowns;
			return;
		}

		if((Cooldown = Cooldowns.GetNum("Cooldown", -1)) > 0)
		{
			LogMessage("Restored cooldown: %s -> %d", map, Cooldown);
			g_OldMapList.SetValue(map, Cooldown, true);
		}

		if((Cooldown = Cooldowns.GetNum("CooldownTime", -1)) > 0)
		{
			LogMessage("Restored time cooldown: %s -> %d", map, Cooldown);
			g_TimeMapList.SetValue(map, Cooldown, true);
		}
	} while(Cooldowns.GotoNextKey(true));

	delete Cooldowns;
}

stock void InternalStoreMapCooldowns()
{
	char sCooldownFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, sCooldownFile, sizeof(sCooldownFile), "configs/mapchooser_extended/cooldowns.cfg");

	if(!FileExists(sCooldownFile))
	{
		LogMessage("Could not find cooldown file: \"%s\"", sCooldownFile);
		return;
	}

	KeyValues Cooldowns = new KeyValues("mapchooser_extended");

	int Cooldown;
	char map[PLATFORM_MAX_PATH];

	StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot();
	for(int i = 0; i < OldMapListSnapshot.Length; i++)
	{
		OldMapListSnapshot.GetKey(i, map, sizeof(map));
		g_OldMapList.GetValue(map, Cooldown);

		if (!Cooldowns.JumpToKey(map, true))
		{
			LogMessage("Unable to create/find key: %s", map);
			delete OldMapListSnapshot;
			delete Cooldowns;
			return;
		}

		Cooldowns.SetNum("Cooldown", Cooldown);
		Cooldowns.Rewind();
	}
	delete OldMapListSnapshot;

	StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot();
	for(int i = 0; i < TimeMapListSnapshot.Length; i++)
	{
		TimeMapListSnapshot.GetKey(i, map, sizeof(map));
		g_TimeMapList.GetValue(map, Cooldown);

		if (!Cooldowns.JumpToKey(map, true))
		{
			LogMessage("Unable to create/find key: %s", map);
			delete TimeMapListSnapshot;
			delete Cooldowns;
			return;
		}

		Cooldowns.SetNum("CooldownTime", Cooldown);
		Cooldowns.Rewind();
	}
	delete TimeMapListSnapshot;

	if(!Cooldowns.ExportToFile(sCooldownFile))
	{
		LogMessage("Unable to export cooldown file: \"%s\"", sCooldownFile);
		delete Cooldowns;
		return;
	}

	delete Cooldowns;
}

stock int TimeStrToSeconds(const char[] str)
{
	int seconds = 0;
	int maxlen = strlen(str);
	for(int i = 0; i < maxlen;)
	{
		int val = 0;
		i += StringToIntEx(str[i], val);
		if(str[i] == 'h')
		{
			val *= 60;
			i++;
		}
		seconds += val * 60;
	}
	return seconds;
}