Improve FWBlock Function #2

Improve FWBlock Function #2

Add switch statement with labeled constants. (faster and easier to understand)

Proceduralize code by adding setMute and setGag stocks to remove switch statement redundancy.

Use decl instead of new to create variables. (skip the initialization stage)

Move variable out of loop for a single cast. (instead of 1 x MaxClients casts)

// *************************************************************************
//  This file is part of SourceBans: Reloaded.
//
//  Copyright (C) 2014-2015 Sarabveer Singh <sarabveer@sarabveer.me>
//
//  SourceBans: Reloaded is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans: Reloaded 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 Affero General Public License
//  along with SourceBans: Reloaded. If not, see <http://www.gnu.org/licenses/>.
//
//  This file incorporates work covered by the following copyright(s):
//
//   SourceComms 0.9.266
//   Copyright (C) 2013-2014 Alexandr Duplishchev
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1883705> - <https://github.com/d-ai/SourceComms>
//
// *************************************************************************

#pragma semicolon 1

#include <sourcemod>
#include <basecomm>
#include <sourcecomms>

#undef REQUIRE_PLUGIN
#include <adminmenu>

#define UNBLOCK_FLAG ADMFLAG_CUSTOM2
#define DATABASE "sourcebans"

// #define DEBUG
// #define LOG_QUERIES

// Do not edit below this line //
//-----------------------------//

#define PLUGIN_VERSION "SBR-1.5.2F-R2-dev"
#define PREFIX "\x04[SourceComms]\x01 "

#define MAX_TIME_MULTI 30       // maximum mass-target punishment length

#define NOW 0
#define TYPE_TEMP_SHIFT 10

#define TYPE_MUTE 1
#define TYPE_GAG 2
#define TYPE_SILENCE 3
#define TYPE_UNMUTE 4
#define TYPE_UNGAG 5
#define TYPE_UNSILENCE 6
#define TYPE_TEMP_UNMUTE 14     // TYPE_TEMP_SHIFT + TYPE_UNMUTE
#define TYPE_TEMP_UNGAG 15      // TYPE_TEMP_SHIFT + TYPE_UNGAG
#define TYPE_TEMP_UNSILENCE 16  // TYPE_TEMP_SHIFT + TYPE_UNSILENCE

#define MAX_REASONS 32
#define DISPLAY_SIZE 64
#define REASON_SIZE 192

new iNumReasons;
new String:g_sReasonDisplays[MAX_REASONS][DISPLAY_SIZE], String:g_sReasonKey[MAX_REASONS][REASON_SIZE];

#define MAX_TIMES 32
new iNumTimes, g_iTimeMinutes[MAX_TIMES];
new String:g_sTimeDisplays[MAX_TIMES][DISPLAY_SIZE];

enum State /* ConfigState */
{
    ConfigStateNone = 0,
    ConfigStateConfig,
    ConfigStateReasons,
    ConfigStateTimes,
    ConfigStateServers,
}
enum DatabaseState /* Database connection state */
{
    DatabaseState_None = 0,
    DatabaseState_Wait,
    DatabaseState_Connecting,
    DatabaseState_Connected,
}

new DatabaseState:g_DatabaseState;
new g_iConnectLock = 0;
new g_iSequence    = 0;

new State:ConfigState;
new Handle:ConfigParser;

new Handle:hTopMenu = INVALID_HANDLE;

/* Cvar handle*/
new Handle:CvarHostIp;
new Handle:CvarPort;

new String:ServerIp[24];
new String:ServerPort[7];

/* Database handle */
new Handle:g_hDatabase;
new Handle:SQLiteDB;

new String:DatabasePrefix[10] = "sb";

/* Timer handles */
new Handle:g_hPlayerRecheck[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:g_hGagExpireTimer[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:g_hMuteExpireTimer[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};

/* Log Stuff */
#if defined LOG_QUERIES
    new String:logQuery[256];
#endif

new Float:RetryTime     = 15.0;
new DefaultTime         = 30;
new DisUBImCheck        = 0;
new ConsoleImmunity     = 0;
new ConfigMaxLength     = 0;
new ConfigWhiteListOnly = 0;
new serverID = 0;

/* List menu */
enum PeskyPanels
{
    curTarget,
    curIndex,
    viewingMute,
    viewingGag,
    viewingList,
}
new g_iPeskyPanels[MAXPLAYERS + 1][PeskyPanels];

new bool:g_bPlayerStatus[MAXPLAYERS + 1];   // Player block check status
new String:g_sName[MAXPLAYERS + 1][MAX_NAME_LENGTH];

new bType:g_MuteType[MAXPLAYERS + 1];
new g_iMuteTime[MAXPLAYERS + 1];
new g_iMuteLength[MAXPLAYERS + 1];  // in sec
new g_iMuteLevel[MAXPLAYERS + 1];   // immunity level of admin
new String:g_sMuteAdminName[MAXPLAYERS + 1][MAX_NAME_LENGTH];
new String:g_sMuteReason[MAXPLAYERS + 1][256];
new String:g_sMuteAdminAuth[MAXPLAYERS + 1][64];

new bType:g_GagType[MAXPLAYERS + 1];
new g_iGagTime[MAXPLAYERS + 1];
new g_iGagLength[MAXPLAYERS + 1]; // in sec
new g_iGagLevel[MAXPLAYERS + 1]; // immunity level of admin
new String:g_sGagAdminName[MAXPLAYERS + 1][MAX_NAME_LENGTH];
new String:g_sGagReason[MAXPLAYERS + 1][256];
new String:g_sGagAdminAuth[MAXPLAYERS + 1][64];

new Handle:g_hServersWhiteList = INVALID_HANDLE;

public Plugin:myinfo =
{
    name = "SourceComms",
    author = "Alex, Sarabveer(VEER™)",
    description = "Advanced punishments management for the Source engine in SourceBans style",
    version = PLUGIN_VERSION,
    url = "https://forums.alliedmods.net/showthread.php?t=207176"
};

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    CreateNative("SourceComms_SetClientMute",       Native_SetClientMute);
    CreateNative("SourceComms_SetClientGag",        Native_SetClientGag);
    CreateNative("SourceComms_GetClientMuteType",   Native_GetClientMuteType);
    CreateNative("SourceComms_GetClientGagType",    Native_GetClientGagType);
    MarkNativeAsOptional("SQL_SetCharset");
    RegPluginLibrary("sourcecomms");
    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("common.phrases");
    LoadTranslations("sourcecomms.phrases");

    new Handle:hTemp = INVALID_HANDLE;
    if (LibraryExists("adminmenu") && ((hTemp = GetAdminTopMenu()) != INVALID_HANDLE))
        OnAdminMenuReady(hTemp);

    CvarHostIp = FindConVar("hostip");
    CvarPort = FindConVar("hostport");
    g_hServersWhiteList = CreateArray();

    CreateConVar("sourcecomms_version", PLUGIN_VERSION, _, FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
    AddCommandListener(CommandCallback,       "sm_gag");
    AddCommandListener(CommandCallback,       "sm_mute");
    AddCommandListener(CommandCallback,       "sm_silence");
    AddCommandListener(CommandCallback,       "sm_ungag");
    AddCommandListener(CommandCallback,       "sm_unmute");
    AddCommandListener(CommandCallback,       "sm_unsilence");
    RegServerCmd("sc_fw_block", FWBlock,      "Blocking player comms by command from sourceban web site", FCVAR_PLUGIN);
    RegServerCmd("sc_fw_ungag", FWUngag,      "Ungagging player by command from sourceban web site", FCVAR_PLUGIN);
    RegServerCmd("sc_fw_unmute",FWUnmute,     "Unmuting player by command from sourceban web site", FCVAR_PLUGIN);
    RegConsoleCmd("sm_comms",   CommandComms, "Shows current player communications status", FCVAR_PLUGIN);

    HookEvent("player_changename", Event_OnPlayerName, EventHookMode_Post);

    #if defined LOG_QUERIES
        BuildPath(Path_SM, logQuery, sizeof(logQuery), "logs/sourcecomms-q.log");
    #endif

    #if defined DEBUG
        PrintToServer("Sourcecomms plugin loading. Version %s", PLUGIN_VERSION);
    #endif

    // Catch config error
    if (!SQL_CheckConfig(DATABASE))
    {
        SetFailState("Database failure: could not find database conf  %s", DATABASE);
        return;
    }
    DB_Connect();
    InitializeBackupDB();

    ServerInfo();

}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "adminmenu"))
        hTopMenu = INVALID_HANDLE;
}

public OnMapStart()
{
    ReadConfig();
}

public OnMapEnd()
{
    // Clean up on map end just so we can start a fresh connection when we need it later.
    // Also it is necessary for using SQL_SetCharset
    if(g_hDatabase)
        CloseHandle(g_hDatabase);

    g_hDatabase = INVALID_HANDLE;
}

// CLIENT CONNECTION FUNCTIONS //

public OnClientDisconnect(client)
{
    if (g_hPlayerRecheck[client] != INVALID_HANDLE && CloseHandle(g_hPlayerRecheck[client]))
        g_hPlayerRecheck[client] = INVALID_HANDLE;

    CloseMuteExpireTimer(client);
    CloseGagExpireTimer(client);
}

public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
{
    g_bPlayerStatus[client] = false;
    return true;
}

public OnClientConnected(client)
{
    g_sName[client][0] = '\0';

    MarkClientAsUnMuted(client);
    MarkClientAsUnGagged(client);
}

public OnClientPostAdminCheck(client)
{
    decl String:clientAuth[64];
    GetClientAuthId(client, AuthId_Steam2, clientAuth, sizeof(clientAuth));
    GetClientName(client, g_sName[client], sizeof(g_sName[]));

    /* Do not check bots or check player with lan steamid. */
    if (clientAuth[0] == 'B' || clientAuth[9] == 'L' || !DB_Connect())
    {
        g_bPlayerStatus[client] = true;
        return;
    }

    if (client > 0 && IsClientInGame(client) && !IsFakeClient(client))
    {
        // if plugin was late loaded
        if (BaseComm_IsClientMuted(client))
        {
            MarkClientAsMuted(client);
        }
        if (BaseComm_IsClientGagged(client))
        {
            MarkClientAsGagged(client);
        }

        new String:sClAuthYZEscaped[sizeof(clientAuth) * 2 + 1];
        SQL_EscapeString(g_hDatabase, clientAuth[8], sClAuthYZEscaped, sizeof(sClAuthYZEscaped));

        decl String:Query[4096];
        FormatEx(Query, sizeof(Query),
           "SELECT      (c.ends - UNIX_TIMESTAMP()) AS remaining, \
                        c.length, c.type, c.created, c.reason, a.user, \
                        IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) AS immunity, \
                        c.aid, c.sid, a.authid \
            FROM        %s_comms     AS c \
            LEFT JOIN   %s_admins    AS a  ON a.aid = c.aid \
            LEFT JOIN   %s_srvgroups AS g  ON g.name = a.srv_group \
            WHERE       RemoveType IS NULL \
                          AND c.authid REGEXP '^STEAM_[0-9]:%s$' \
                          AND (length = '0' OR ends > UNIX_TIMESTAMP())",
            DatabasePrefix, DatabasePrefix, DatabasePrefix, sClAuthYZEscaped);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "OnClientPostAdminCheck for: %s. QUERY: %s", clientAuth, Query);
        #endif
        SQL_TQuery(g_hDatabase, Query_VerifyBlock, Query, GetClientUserId(client), DBPrio_High);
    }
}

// OTHER CLIENT CODE //

public Action:Event_OnPlayerName(Handle:event, const String:name[], bool:dontBroadcast)
{
    new client = GetClientOfUserId(GetEventInt(event, "userid"));
    if (client > 0 && IsClientInGame(client))
        GetEventString(event, "newname", g_sName[client], sizeof(g_sName[]));
}

public BaseComm_OnClientMute(client, bool:muteState)
{
    if (client > 0 && client <= MaxClients)
    {
        if (muteState)
        {
            if (g_MuteType[client] == bNot)
            {
                MarkClientAsMuted(client, _, _, _, _, _, "Muted through BaseComm natives");
                SavePunishment(_, client, TYPE_MUTE,  _, "Muted through BaseComm natives");
            }
        }
        else
        {
            if (g_MuteType[client] > bNot)
            {
                MarkClientAsUnMuted(client);
            }
        }
    }
}

public BaseComm_OnClientGag(client, bool:gagState)
{
    if (client > 0 && client <= MaxClients)
    {
        if (gagState)
        {
            if (g_GagType[client] == bNot)
            {
                MarkClientAsGagged(client, _, _, _, _, _, "Gagged through BaseComm natives");
                SavePunishment(_,  client, TYPE_GAG,   _, "Gagged through BaseComm natives");
            }
        }
        else
        {
            if (g_GagType[client] > bNot)
            {
                MarkClientAsUnGagged(client);
            }
        }
    }
}

// COMMAND CODE //

public Action:CommandComms(client, args)
{
    if (!client)
    {
        ReplyToCommand(client, "%s%t", PREFIX, "CommandComms_na");
        return Plugin_Continue;
    }

    if (g_MuteType[client] > bNot || g_GagType[client] > bNot)
        AdminMenu_ListTarget(client, client, 0);
    else
        ReplyToCommand(client,  "%s%t", PREFIX, "CommandComms_nb");

    return Plugin_Handled;
}

public Action:FWBlock(args)
{
    decl String:arg_string[256], String:sArg[3][64];
    GetCmdArgString(arg_string, sizeof(arg_string));

    decl type, length;
    if(ExplodeString(arg_string, " ", sArg, 3, 64) != 3 || !StringToIntEx(sArg[0], type) || type < 1 || type > 3 || !StringToIntEx(sArg[1], length))
    {
        LogError("Wrong usage of sc_fw_block");
        return Plugin_Stop;
    }

    LogMessage("Received block command from web: steam %s, type %d, length %d", sArg[2], type, length);

    decl String:clientAuth[64];
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[2], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for blocking from web", clientAuth);
                #endif

                switch (type) {
                    case TYPE_MUTE: setMute(i, length, clientAuth);
                    case TYPE_GAG: setGag(i, length, clientAuth);
                    case TYPE_SILENCE: { setMute(i, length, clientAuth); setGag(i, length, clientAuth); }
                }
                break;
            }
        }
    }

    return Plugin_Handled;
}

public Action:FWUngag(args)
{
    new String:arg_string[256], String:sArg[1][64];
    GetCmdArgString(arg_string, sizeof(arg_string));
    if(!ExplodeString(arg_string, " ", sArg, 1, 64))
    {
        LogError("Wrong usage of sc_fw_ungag");
        return Plugin_Stop;
    }

    LogMessage("Received ungag command from web: steam %s", sArg[0]);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            decl String:clientAuth[64];
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[0], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for ungagging from web", clientAuth);
                #endif

                if (g_GagType[i] > bNot)
                {
                    PerformUnGag(i);
                    PrintToChat(i, "%s%t", PREFIX, "FWUngag");
                    LogMessage("%s is ungagged from web", clientAuth);
                }
                else
                    LogError("Can't ungag %s from web, it isn't gagged", clientAuth);
                break;
            }
        }
    }
    return Plugin_Handled;
}

public Action:FWUnmute(args)
{
    new String:arg_string[256], String:sArg[1][64];
    GetCmdArgString(arg_string, sizeof(arg_string));
    if(!ExplodeString(arg_string, " ", sArg, 1, 64))
    {
        LogError("Wrong usage of sc_fw_ungag");
        return Plugin_Stop;
    }

    LogMessage("Received unmute command from web: steam %s", sArg[0]);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            decl String:clientAuth[64];
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[0], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for unmuting from web", clientAuth);
                #endif

                if (g_MuteType[i] > bNot)
                {
                    PerformUnMute(i);
                    PrintToChat(i, "%s%t", PREFIX, "FWUnmute");
                    LogMessage("%s is unmuted from web", clientAuth);
                }
                else
                    LogError("Can't unmute %s from web, it isn't muted", clientAuth);
                break;
            }
        }
    }
    return Plugin_Handled;
}

public Action:CommandCallback(client, const String:command[], args)
{
    if (client && !CheckCommandAccess(client, command, ADMFLAG_CHAT))
        return Plugin_Continue;

    new type;
    if (StrEqual(command, "sm_gag", false))
        type = TYPE_GAG;
    else if (StrEqual(command, "sm_mute", false))
        type = TYPE_MUTE;
    else if (StrEqual(command, "sm_ungag", false))
        type = TYPE_UNGAG;
    else if (StrEqual(command, "sm_unmute", false))
        type = TYPE_UNMUTE;
    else if (StrEqual(command, "sm_silence", false))
        type = TYPE_SILENCE;
    else if (StrEqual(command, "sm_unsilence", false))
        type = TYPE_UNSILENCE;
    else
        return Plugin_Stop;

    if (args < 1)
    {
        ReplyToCommand(client, "%sUsage: %s <#userid|name> %s", PREFIX, command, type <= TYPE_SILENCE ? "[time|0] [reason]" : "[reason]");
        if (type <= TYPE_SILENCE)
            ReplyToCommand(client, "%sUsage: %s <#userid|name> [reason]", PREFIX, command);
        return Plugin_Stop;
    }

    new String:sBuffer[256];
    GetCmdArgString(sBuffer, sizeof(sBuffer));

    if (type <= TYPE_SILENCE)
        CreateBlock(client, _, _, type, _, sBuffer);
    else
        ProcessUnBlock(client, _, type, _, sBuffer);

    return Plugin_Stop;
}

// MENU CODE //

public OnAdminMenuReady(Handle:topmenu)
{
    /* Block us from being called twice */
    if (topmenu == hTopMenu)
        return;

    /* Save the Handle */
    hTopMenu = topmenu;

    new TopMenuObject:MenuObject = AddToTopMenu(hTopMenu, "sourcecomm_cmds", TopMenuObject_Category, Handle_Commands, INVALID_TOPMENUOBJECT);
    if (MenuObject == INVALID_TOPMENUOBJECT)
        return;

    AddToTopMenu(hTopMenu, "sourcecomm_gag",       TopMenuObject_Item, Handle_MenuGag,       MenuObject, "sm_gag",       ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_ungag",     TopMenuObject_Item, Handle_MenuUnGag,     MenuObject, "sm_ungag",     ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_mute",      TopMenuObject_Item, Handle_MenuMute,      MenuObject, "sm_mute",      ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_unmute",    TopMenuObject_Item, Handle_MenuUnMute,    MenuObject, "sm_unmute",    ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_silence",   TopMenuObject_Item, Handle_MenuSilence,   MenuObject, "sm_silence",   ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_unsilence", TopMenuObject_Item, Handle_MenuUnSilence, MenuObject, "sm_unsilence", ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_list",      TopMenuObject_Item, Handle_MenuList,      MenuObject, "sm_commlist",  ADMFLAG_CHAT);
}

public Handle_Commands(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    switch(action)
    {
        case TopMenuAction_DisplayOption:
            Format(buffer, maxlength, "%T", "AdminMenu_Main", param1);
        case TopMenuAction_DisplayTitle:
            Format(buffer, maxlength, "%T", "AdminMenu_Select_Main", param1);
    }
}

public Handle_MenuGag(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Gag", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_GAG);
}

public Handle_MenuUnGag(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnGag", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNGAG);
}

public Handle_MenuMute(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Mute", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_MUTE);
}

public Handle_MenuUnMute(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnMute", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNMUTE);
}

public Handle_MenuSilence(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Silence", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_SILENCE);
}

public Handle_MenuUnSilence(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnSilence", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNSILENCE);
}

public Handle_MenuList(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_List", param1);
    else if (action == TopMenuAction_SelectOption)
    {
        g_iPeskyPanels[param1][viewingList] = false;
        AdminMenu_List(param1, 0);
    }
}

AdminMenu_Target(client, type)
{
    decl String:Title[192], String:Option[32];
    switch(type)
    {
        case TYPE_GAG:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Gag", client);
        case TYPE_MUTE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Mute", client);
        case TYPE_SILENCE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Silence", client);
        case TYPE_UNGAG:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Ungag", client);
        case TYPE_UNMUTE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Unmute", client);
        case TYPE_UNSILENCE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Unsilence", client);
    }

    new Handle:hMenu = CreateMenu(MenuHandler_MenuTarget);    // Common menu - players list. Almost full for blocking, and almost empty for unblocking
    SetMenuTitle(hMenu, Title);
    SetMenuExitBackButton(hMenu, true);

    new iClients;
    if (type <= 3)    // Mute, gag, silence
    {
        for (new i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i) && !IsFakeClient(i))
            {
                switch(type)
                {
                    case TYPE_MUTE:
                        if (g_MuteType[i] > bNot)
                            continue;
                    case TYPE_GAG:
                        if (g_GagType[i] > bNot)
                            continue;
                    case TYPE_SILENCE:
                        if (g_MuteType[i] > bNot || g_GagType[i] > bNot)
                            continue;
                }
                iClients++;
                strcopy(Title, sizeof(Title), g_sName[i]);
                AdminMenu_GetPunishPhrase(client, i, Title, sizeof(Title));
                Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
            }
        }
    }
    else        // UnMute, ungag, unsilence
    {
        for (new i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i) && !IsFakeClient(i))
            {
                switch(type)
                {
                    case TYPE_UNMUTE:
                    {
                        if (g_MuteType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                    case TYPE_UNGAG:
                    {
                        if (g_GagType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                    case TYPE_UNSILENCE:
                    {
                        if (g_MuteType[i] > bNot && g_GagType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                }
            }
        }
    }
    if (!iClients)
    {
        switch(type)
        {
            case TYPE_UNMUTE:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Mute_Empty", client);
            case TYPE_UNGAG:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Gag_Empty", client);
            case TYPE_UNSILENCE:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Silence_Empty", client);
            default:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Empty", client);
        }
        AddMenuItem(hMenu, "0", Title, ITEMDRAW_DISABLED);
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuTarget(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:Option[32], String:Temp[2][8];
            GetMenuItem(menu, param2, Option, sizeof(Option));
            ExplodeString(Option, " ", Temp, 2, 8);
            new target = GetClientOfUserId(StringToInt(Temp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(Temp[1]);
                if (type <= TYPE_SILENCE)
                    AdminMenu_Duration(param1, target, type);
                else
                    ProcessUnBlock(param1, target, type);
            }
        }
    }
}

AdminMenu_Duration(client, target, type)
{
    new Handle:hMenu = CreateMenu(MenuHandler_MenuDuration);
    decl String:sBuffer[192], String:sTemp[64];
    Format(sBuffer, sizeof(sBuffer), "%T", "AdminMenu_Title_Durations", client);
    SetMenuTitle(hMenu, sBuffer);
    SetMenuExitBackButton(hMenu, true);

    for (new i = 0; i <= iNumTimes; i++)
    {
        if (IsAllowedBlockLength(client, g_iTimeMinutes[i]))
        {
            Format(sTemp, sizeof(sTemp), "%d %d %d", GetClientUserId(target), type, i);    // TargetID TYPE_BLOCK index_of_Time
            AddMenuItem(hMenu, sTemp, g_sTimeDisplays[i]);
        }
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuDuration(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[32], String:sTemp[3][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 3, 8);
            // TargetID TYPE_BLOCK index_of_Time
            new target = GetClientOfUserId(StringToInt(sTemp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(sTemp[1]);
                new lengthIndex = StringToInt(sTemp[2]);

                if (iNumReasons) // we have reasons to show
                    AdminMenu_Reason(param1, target, type, lengthIndex);
                else
                    CreateBlock(param1, target, g_iTimeMinutes[lengthIndex], type);
            }
        }
    }
}

AdminMenu_Reason(client, target, type, lengthIndex)
{
    new Handle:hMenu = CreateMenu(MenuHandler_MenuReason);
    decl String:sBuffer[192], String:sTemp[64];
    Format(sBuffer, sizeof(sBuffer), "%T", "AdminMenu_Title_Reasons", client);
    SetMenuTitle(hMenu, sBuffer);
    SetMenuExitBackButton(hMenu, true);

    for (new i = 0; i <= iNumReasons; i++)
    {
        Format(sTemp, sizeof(sTemp), "%d %d %d %d", GetClientUserId(target), type, i, lengthIndex);    // TargetID TYPE_BLOCK ReasonIndex LenghtIndex
        AddMenuItem(hMenu, sTemp, g_sReasonDisplays[i]);
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuReason(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[64], String:sTemp[4][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 4, 8);
            // TargetID TYPE_BLOCK ReasonIndex LenghtIndex
            new target = GetClientOfUserId(StringToInt(sTemp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(sTemp[1]);
                new reasonIndex = StringToInt(sTemp[2]);
                new lengthIndex = StringToInt(sTemp[3]);
                new length;
                if (lengthIndex >= 0 && lengthIndex <= iNumTimes)
                    length = g_iTimeMinutes[lengthIndex];
                else
                {
                    length = DefaultTime;
                    LogError("Wrong length index in menu - using default time");
                }

                CreateBlock(param1, target, length, type, g_sReasonKey[reasonIndex]);
            }
        }
    }
}

AdminMenu_List(client, index)
{
    decl String:sTitle[192], String:sOption[32];
    Format(sTitle, sizeof(sTitle), "%T", "AdminMenu_Select_List", client);
    new iClients, Handle:hMenu = CreateMenu(MenuHandler_MenuList);
    SetMenuTitle(hMenu, sTitle);
    if (!g_iPeskyPanels[client][viewingList])
        SetMenuExitBackButton(hMenu, true);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && !IsFakeClient(i) && (g_MuteType[i] > bNot || g_GagType[i] > bNot))
        {
            iClients++;
            strcopy(sTitle, sizeof(sTitle), g_sName[i]);
            AdminMenu_GetPunishPhrase(client, i, sTitle, sizeof(sTitle));
            Format(sOption, sizeof(sOption), "%d", GetClientUserId(i));
            AddMenuItem(hMenu, sOption, sTitle);
        }
    }

    if (!iClients)
    {
        Format(sTitle, sizeof(sTitle), "%T", "ListMenu_Option_Empty", client);
        AddMenuItem(hMenu, "0", sTitle, ITEMDRAW_DISABLED);
    }

    DisplayMenuAtItem(hMenu, client, index, MENU_TIME_FOREVER);
}

public MenuHandler_MenuList(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (!g_iPeskyPanels[param1][viewingList])
                if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                    DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[32];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            new target = GetClientOfUserId(StringToInt(sOption));

            if (Bool_ValidMenuTarget(param1, target))
                AdminMenu_ListTarget(param1, target, GetMenuSelectionPosition());
            else
                AdminMenu_List(param1, GetMenuSelectionPosition());
        }
    }
}

AdminMenu_ListTarget(client, target, index, viewMute = 0, viewGag = 0)
{
    new userid = GetClientUserId(target), Handle:hMenu = CreateMenu(MenuHandler_MenuListTarget);
    decl String:sBuffer[192], String:sOption[32];
    SetMenuTitle(hMenu, g_sName[target]);
    SetMenuPagination(hMenu, MENU_NO_PAGINATION);
    SetMenuExitButton(hMenu, true);
    SetMenuExitBackButton(hMenu, false);

    if (g_MuteType[target] > bNot)
    {
        Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Mute", client);
        Format(sOption, sizeof(sOption), "0 %d %d %b %b", userid, index, viewMute, viewGag);
        AddMenuItem(hMenu, sOption, sBuffer);

        if (viewMute)
        {
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Admin", client, g_sMuteAdminName[target]);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            decl String:sMuteTemp[192], String:_sMuteTime[192];
            Format(sMuteTemp, sizeof(sMuteTemp), "%T", "ListMenu_Option_Duration", client);
            switch (g_MuteType[target])
            {
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Perm", client);
                case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Time", client, g_iMuteLength[target]);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Temp", client);
                default:    Format(sBuffer, sizeof(sBuffer), "error");
            }
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            FormatTime(_sMuteTime, sizeof(_sMuteTime), NULL_STRING, g_iMuteTime[target]);
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Issue", client, _sMuteTime);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            Format(sMuteTemp, sizeof(sMuteTemp), "%T", "ListMenu_Option_Expire", client);
            switch (g_MuteType[target])
            {
                case bTime:
                {
                    FormatTime(_sMuteTime, sizeof(_sMuteTime), NULL_STRING, (g_iMuteTime[target] + g_iMuteLength[target] * 60));
                    Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Time", client, _sMuteTime);
                }
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Perm", client);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Temp_Reconnect", client);
                default: Format(sBuffer, sizeof(sBuffer), "error");
            }
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            if (strlen(g_sMuteReason[target]) > 0)
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason", client);
                Format(sOption, sizeof(sOption), "1 %d %d %b %b", userid, index, viewMute, viewGag);
                AddMenuItem(hMenu, sOption, sBuffer);
            }
            else
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason_None", client);
                AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);
            }
        }
    }

    if (g_GagType[target] > bNot)
    {
        Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Gag", client);
        Format(sOption, sizeof(sOption), "2 %d %d %b %b", userid, index, viewMute, viewGag);
        AddMenuItem(hMenu, sOption, sBuffer);

        if (viewGag)
        {
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Admin", client, g_sGagAdminName[target]);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            decl String:sGagTemp[192], String:_sGagTime[192];
            Format(sGagTemp, sizeof(sGagTemp), "%T", "ListMenu_Option_Duration", client);

            switch (g_GagType[target])
            {
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Perm", client);
                case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Time", client, g_iGagLength[target]);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Temp", client);
                default:    Format(sBuffer, sizeof(sBuffer), "error");
            }

            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            FormatTime(_sGagTime, sizeof(_sGagTime), NULL_STRING, g_iGagTime[target]);
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Issue", client, _sGagTime);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            Format(sGagTemp, sizeof(sGagTemp), "%T", "ListMenu_Option_Expire", client);

            switch (g_GagType[target])
            {
                case bTime:
                {
                    FormatTime(_sGagTime, sizeof(_sGagTime), NULL_STRING, (g_iGagTime[target] + g_iGagLength[target] * 60));
                    Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Time", client, _sGagTime);
                }
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Perm", client);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Temp_Reconnect", client);
                default: Format(sBuffer, sizeof(sBuffer), "error");
            }

            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            if (strlen(g_sGagReason[target]) > 0)
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason", client);
                Format(sOption, sizeof(sOption), "3 %d %d %b %b", userid, index, viewMute, viewGag);
                AddMenuItem(hMenu, sOption, sBuffer);
            }
            else
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason_None", client);
                AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);
            }
        }
    }

    g_iPeskyPanels[client][curIndex] = index;
    g_iPeskyPanels[client][curTarget] = target;
    g_iPeskyPanels[client][viewingGag] = viewGag;
    g_iPeskyPanels[client][viewingMute] = viewMute;
    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuListTarget(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack)
                AdminMenu_List(param1, g_iPeskyPanels[param1][curIndex]);
        }
        case MenuAction_Select:
        {
            decl String:sOption[64], String:sTemp[5][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 5, 8);

            new target = GetClientOfUserId(StringToInt(sTemp[1]));
            if (param1 == target || Bool_ValidMenuTarget(param1, target))
            {
                switch(StringToInt(sTemp[0]))
                {
                    case 0:
                        AdminMenu_ListTarget(param1, target, StringToInt(sTemp[2]), !(StringToInt(sTemp[3])), 0);
                    case 1, 3:
                        AdminMenu_ListTargetReason(param1, target, g_iPeskyPanels[param1][viewingMute], g_iPeskyPanels[param1][viewingGag]);
                    case 2:
                        AdminMenu_ListTarget(param1, target, StringToInt(sTemp[2]), 0, !(StringToInt(sTemp[4])));
                }
            }
            else
                AdminMenu_List(param1, StringToInt(sTemp[2]));

        }
    }
}

AdminMenu_ListTargetReason(client, target, showMute, showGag)
{
    decl String:sTemp[192], String:sBuffer[192];
    new Handle:hPanel = CreatePanel();
    SetPanelTitle(hPanel, g_sName[target]);
    DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);

    if (showMute)
    {
        Format(sTemp, sizeof(sTemp), "%T", "ReasonPanel_Punishment_Mute", client);
        switch (g_MuteType[target])
        {
            case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Perm", client);
            case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Time", client, g_iMuteLength[target]);
            case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Temp", client);
            default:    Format(sBuffer, sizeof(sBuffer), "error");
        }
        DrawPanelText(hPanel, sBuffer);

        Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Reason", client, g_sMuteReason[target]);
        DrawPanelText(hPanel, sBuffer);
    }
    else if (showGag)
    {
        Format(sTemp, sizeof(sTemp), "%T", "ReasonPanel_Punishment_Gag", client);
        switch (g_GagType[target])
        {
            case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Perm", client);
            case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Time", client, g_iGagLength[target]);
            case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Temp", client);
            default:    Format(sBuffer, sizeof(sBuffer), "error");
        }
        DrawPanelText(hPanel, sBuffer);

        Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Reason", client, g_sGagReason[target]);
        DrawPanelText(hPanel, sBuffer);
    }

    DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);
    SetPanelCurrentKey(hPanel, 10);
    Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Back", client);
    DrawPanelItem(hPanel, sBuffer);
    SendPanelToClient(hPanel, client, PanelHandler_ListTargetReason, MENU_TIME_FOREVER);
    CloseHandle(hPanel);
}

public PanelHandler_ListTargetReason(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        AdminMenu_ListTarget(param1,g_iPeskyPanels[param1][curTarget],
                                    g_iPeskyPanels[param1][curIndex],
                                    g_iPeskyPanels[param1][viewingMute],
                                    g_iPeskyPanels[param1][viewingGag]);
    }
}

// SQL CALLBACKS //

public GotDatabase(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    #if defined DEBUG
        PrintToServer("GotDatabase(data: %d, lock: %d, g_h: %d, hndl: %d)", data, g_iConnectLock, g_hDatabase, hndl);
    #endif

    // If this happens to be an old connection request, ignore it.
    if(data != g_iConnectLock || g_hDatabase)
    {
        if(hndl)
            CloseHandle(hndl);
        return;
    }

    g_iConnectLock   = 0;
    g_DatabaseState  = DatabaseState_Connected;
    g_hDatabase      = hndl;

    // See if the connection is valid.  If not, don't un-mark the caches
    // as needing rebuilding, in case the next connection request works.
    if(!g_hDatabase)
    {
        LogError("Connecting to database failed: %s", error);
        return;
    }

    // Set character set to UTF-8 in the database
    if (GetFeatureStatus(FeatureType_Native, "SQL_SetCharset") == FeatureStatus_Available)
    {
        SQL_SetCharset(g_hDatabase, "utf8");
    }
    else
    {
        decl String:query[128];
        FormatEx(query, sizeof(query), "SET NAMES 'UTF8'");
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Set encoding. QUERY: %s", query);
        #endif
        SQL_TQuery(g_hDatabase, Query_ErrorCheck, query);
    }

    // Process queue
    SQL_TQuery(SQLiteDB, Query_ProcessQueue,
       "SELECT  id, steam_id, time, start_time, reason, name, admin_id, admin_ip, type \
        FROM    queue2");

    // Force recheck players
    ForcePlayersRecheck();
}

public Query_AddBlockInsert(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (DB_Conn_Lost(hndl) || error[0])
    {
        LogError("Query_AddBlockInsert failed: %s", error);

        ResetPack(data);
        new length = ReadPackCell(data);
        new type = ReadPackCell(data);
        new String:reason[256], String:name[MAX_NAME_LENGTH], String:auth[64], String:adminAuth[32], String:adminIp[20];
        ReadPackString(data, name, sizeof(name));
        ReadPackString(data, auth, sizeof(auth));
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, adminAuth, sizeof(adminAuth));
        ReadPackString(data, adminIp, sizeof(adminIp));

        InsertTempBlock(length, type, name, auth, reason, adminAuth, adminIp);
    }
    CloseHandle(data);
}

public Query_UnBlockSelect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    decl String:adminAuth[30], String:targetAuth[30], String:reason[256];

    ResetPack(data);
    new adminUserID  = ReadPackCell(data);
    new targetUserID = ReadPackCell(data);
    new type         = ReadPackCell(data);    // not in use unless DEBUG
    ReadPackString(data, adminAuth,  sizeof(adminAuth));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    ReadPackString(data, reason,      sizeof(reason));

    new admin  = GetClientOfUserId(adminUserID);
    new target = GetClientOfUserId(targetUserID);

    #if defined DEBUG
        PrintToServer("Query_UnBlockSelect(adminUID: %d/%d, targetUID: %d/%d, type: %d, adminAuth: %s, targetAuth: %s, reason: %s)",
            adminUserID, admin, targetUserID, target, type, adminAuth, targetAuth, reason);
    #endif

    new String:targetName[MAX_NAME_LENGTH];
    strcopy(targetName, MAX_NAME_LENGTH, target && IsClientInGame(target) ? g_sName[target] : targetAuth);        //FIXME

    new bool:hasErrors = false;
    // If error is not an empty string the query failed
    if (DB_Conn_Lost(hndl) || error[0] != '\0')
    {
        LogError("Query_UnBlockSelect failed: %s", error);
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%T", PREFIX, "Unblock Select Failed", admin, targetAuth);
            PrintToConsole(admin, "%s%T", PREFIX, "Unblock Select Failed", admin, targetAuth);
        }
        else
        {
            PrintToServer("%s%T", PREFIX, "Unblock Select Failed", LANG_SERVER, targetAuth);
        }
        hasErrors = true;
    }

    // If there was no results then a ban does not exist for that id
    if (!DB_Conn_Lost(hndl) && !SQL_GetRowCount(hndl))
    {
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "No blocks found", targetAuth);
            PrintToConsole(admin, "%s%t", PREFIX, "No blocks found", targetAuth);
        }
        else
        {
            PrintToServer("%s%T", PREFIX, "No blocks found", LANG_SERVER, targetAuth);
        }
        hasErrors = true;
    }

    if (hasErrors)
    {
        #if defined DEBUG
            PrintToServer("Calling TempUnBlock from Query_UnBlockSelect");
        #endif

        TempUnBlock(data);    // Datapack closed inside.
        return;
    }
    else
    {
        new bool:b_success = false;
        // Get the values from the founded blocks.
        while(SQL_MoreRows(hndl))
        {
            // Oh noes! What happened?!
            if (!SQL_FetchRow(hndl))
                continue;

            new bid       = SQL_FetchInt(hndl, 0);
            new iAID      = SQL_FetchInt(hndl, 1);
            new cAID      = SQL_FetchInt(hndl, 2);
            new cImmunity = SQL_FetchInt(hndl, 3);
            new cType     = SQL_FetchInt(hndl, 4);

            #if defined DEBUG
                PrintToServer("Fetched from DB: bid %d, iAID: %d, cAID: %d, cImmunity: %d, cType: %d", bid, iAID, cAID, cImmunity, cType);
                // WHO WE ARE?
                PrintToServer("WHO WE ARE CHECKING!");
                if (iAID == cAID)
                    PrintToServer("we are block author");
                if (!admin)
                    PrintToServer("we are console (possibly)");
                if (AdmHasFlag(admin))
                    PrintToServer("we have special flag");
                if (GetAdmImmunity(admin) > cImmunity)
                    PrintToServer("we have %d immunity and block has %d. we cool", GetAdmImmunity(admin), cImmunity);
            #endif

            // Checking - has we access to unblock?
            if (iAID == cAID || (!admin && StrEqual(adminAuth, "STEAM_ID_SERVER")) || AdmHasFlag(admin) || (DisUBImCheck == 0 && (GetAdmImmunity(admin) > cImmunity)))
            {
                // Ok! we have rights to unblock
                b_success = true;
                // UnMute/UnGag, Show & log activity
                if (target && IsClientInGame(target))
                {
                    switch(cType)
                    {
                        case TYPE_MUTE:
                        {
                            PerformUnMute(target);
                            LogAction(admin, target, "\"%L\" unmuted \"%L\" (reason \"%s\")", admin, target, reason);
                        }
                        //-------------------------------------------------------------------------------------------------
                        case TYPE_GAG:
                        {
                            PerformUnGag(target);
                            LogAction(admin, target, "\"%L\" ungagged \"%L\" (reason \"%s\")", admin, target, reason);
                        }
                    }
                }

                new Handle:dataPack = CreateDataPack();
                WritePackCell(dataPack, adminUserID);
                WritePackCell(dataPack, cType);
                WritePackString(dataPack, g_sName[target]);
                WritePackString(dataPack, targetAuth);

                new String:unbanReason[sizeof(reason) * 2 + 1];
                SQL_EscapeString(g_hDatabase, reason, unbanReason, sizeof(unbanReason));

                decl String:query[2048];
                Format(query, sizeof(query),
                   "UPDATE  %s_comms \
                    SET     RemovedBy = %d, \
                            RemoveType = 'U', \
                            RemovedOn = UNIX_TIMESTAMP(), \
                            ureason = '%s' \
                    WHERE   bid = %d",
                    DatabasePrefix, iAID, unbanReason, bid);
                #if defined LOG_QUERIES
                    LogToFile(logQuery, "Query_UnBlockSelect. QUERY: %s", query);
                #endif
                SQL_TQuery(g_hDatabase, Query_UnBlockUpdate, query, dataPack);
            }
            else
            {
                // sorry, we don't have permission to unblock!
                #if defined DEBUG
                    PrintToServer("No permissions to unblock in Query_UnBlockSelect");
                #endif
                switch(cType)
                {
                    case TYPE_MUTE:
                    {
                        if (admin && IsClientInGame(admin))
                        {
                            PrintToChat(admin, "%s%t", PREFIX, "No permission unmute", targetName);
                            PrintToConsole(admin, "%s%t", PREFIX, "No permission unmute", targetName);
                        }
                        LogAction(admin, target, "\"%L\" tried (and didn't have permission) to unmute %s (reason \"%s\")", admin, targetAuth, reason);
                    }
                    //-------------------------------------------------------------------------------------------------
                    case TYPE_GAG:
                    {
                        if (admin && IsClientInGame(admin))
                        {
                            PrintToChat(admin, "%s%t", PREFIX, "No permission ungag", targetName);
                            PrintToConsole(admin, "%s%t", PREFIX, "No permission ungag", targetName);
                        }
                        LogAction(admin, target, "\"%L\" tried (and didn't have permission) to ungag %s (reason \"%s\")", admin, targetAuth, reason);
                    }
                }
            }
        }

        if (b_success && target && IsClientInGame(target))
        {
            #if defined DEBUG
                PrintToServer("Showing activity to server in Query_UnBlockSelect");
            #endif
            ShowActivityToServer(admin, type, _, _, g_sName[target], _);

            if (type == TYPE_UNSILENCE)
            {
                // check result for possible combination with temp and time punishments (temp was skipped in code above)
                SetPackPosition(data, 16);
                if (g_MuteType[target] > bNot)
                {
                    WritePackCell(data, TYPE_UNMUTE);
                    TempUnBlock(data);
                    data = INVALID_HANDLE;
                }
                else if (g_GagType[target] > bNot)
                {
                    WritePackCell(data, TYPE_UNGAG);
                    TempUnBlock(data);
                    data = INVALID_HANDLE;
                }
            }
        }
    }
    if (data != INVALID_HANDLE)
        CloseHandle(data);
}

public Query_UnBlockUpdate(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    new admin, type;
    new String:targetName[MAX_NAME_LENGTH], String:targetAuth[30];

    ResetPack(data);
    admin = GetClientOfUserId(ReadPackCell(data));
    type  =                   ReadPackCell(data);
    ReadPackString(data, targetName, sizeof(targetName));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    CloseHandle(data);

    if (DB_Conn_Lost(hndl) || error[0] != '\0')
    {
        LogError("Query_UnBlockUpdate failed: %s", error);
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "Unblock insert failed");
            PrintToConsole(admin, "%s%t", PREFIX, "Unblock insert failed");
        }
        return;
    }

    switch(type)
    {
        case TYPE_MUTE:
        {
            LogAction(admin, -1, "\"%L\" removed mute for %s from DB", admin, targetAuth);
            if (admin && IsClientInGame(admin))
            {
                PrintToChat(admin, "%s%t", PREFIX, "successfully unmuted", targetName);
                PrintToConsole(admin, "%s%t", PREFIX, "successfully unmuted", targetName);
            }
            else
            {
                PrintToServer("%s%T", PREFIX, "successfully unmuted", LANG_SERVER, targetName);
            }
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_GAG:
        {
            LogAction(admin, -1, "\"%L\" removed gag for %s from DB", admin, targetAuth);
            if (admin && IsClientInGame(admin)){
                PrintToChat(admin, "%s%t", PREFIX, "successfully ungagged", targetName);
                PrintToConsole(admin, "%s%t", PREFIX, "successfully ungagged", targetName);
            }
            else
            {
                PrintToServer("%s%T", PREFIX, "successfully ungagged", LANG_SERVER, targetName);
            }
        }
    }
}

// ProcessQueueCallback is called as the result of selecting all the rows from the queue table
public Query_ProcessQueue(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (hndl == INVALID_HANDLE || error[0])
    {
        LogError("Query_ProcessQueue failed: %s", error);
        return;
    }

    decl String:auth[64];
    new String:name[MAX_NAME_LENGTH];
    decl String:reason[256];
    decl String:adminAuth[64], String:adminIp[20];
    decl String:query[4096];

    while(SQL_MoreRows(hndl))
    {
        // Oh noes! What happened?!
        if (!SQL_FetchRow(hndl))
            continue;

        new String:sAuthEscaped[sizeof(auth) * 2 + 1];
        new String:banName[MAX_NAME_LENGTH * 2  + 1];
        new String:banReason[sizeof(reason) * 2 + 1];
        new String:sAdmAuthEscaped[sizeof(adminAuth) * 2 + 1];
        new String:sAdmAuthYZEscaped[sizeof(adminAuth) * 2 + 1];

        // if we get to here then there are rows in the queue pending processing
        //steam_id TEXT, time INTEGER, start_time INTEGER, reason TEXT, name TEXT, admin_id TEXT, admin_ip TEXT, type INTEGER
        new id =        SQL_FetchInt(hndl, 0);
        SQL_FetchString(hndl, 1, auth, sizeof(auth));
        new time =      SQL_FetchInt(hndl, 2);
        new startTime = SQL_FetchInt(hndl, 3);
        SQL_FetchString(hndl, 4, reason, sizeof(reason));
        SQL_FetchString(hndl, 5, name, sizeof(name));
        SQL_FetchString(hndl, 6, adminAuth, sizeof(adminAuth));
        SQL_FetchString(hndl, 7, adminIp, sizeof(adminIp));
        new type =      SQL_FetchInt(hndl, 8);

        if (DB_Connect()) {
            SQL_EscapeString(g_hDatabase, auth,         sAuthEscaped,      sizeof(sAuthEscaped));
            SQL_EscapeString(g_hDatabase, name,         banName,           sizeof(banName));
            SQL_EscapeString(g_hDatabase, reason,       banReason,         sizeof(banReason));
            SQL_EscapeString(g_hDatabase, adminAuth,    sAdmAuthEscaped,   sizeof(sAdmAuthEscaped));
            SQL_EscapeString(g_hDatabase, adminAuth[8], sAdmAuthYZEscaped, sizeof(sAdmAuthYZEscaped));
        }
        else
            continue;
        // all blocks should be entered into db!

        FormatEx(query, sizeof(query),
               "INSERT INTO     %s_comms (authid, name, created, ends, length, reason, aid, adminIp, sid, type) \
                VALUES         ('%s', '%s', %d, %d, %d, '%s', \
                                IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0'), \
                                '%s', %d, %d)",
                DatabasePrefix, sAuthEscaped, banName, startTime, (startTime + (time*60)), (time*60), banReason, DatabasePrefix, sAdmAuthEscaped, sAdmAuthYZEscaped, adminIp, serverID, type);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Query_ProcessQueue. QUERY: %s", query);
        #endif
        SQL_TQuery(g_hDatabase, Query_AddBlockFromQueue, query, id);
    }
}

public Query_AddBlockFromQueue(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    decl String:query[512];
    if (error[0] == '\0')
    {
        // The insert was successful so delete the record from the queue
        FormatEx(query, sizeof(query),
           "DELETE FROM queue2 \
            WHERE       id = %d",
            data);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Query_AddBlockFromQueue. QUERY: %s", query);
        #endif
        SQL_TQuery(SQLiteDB, Query_ErrorCheck, query);
    }
}

public Query_ErrorCheck(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (DB_Conn_Lost(hndl) || error[0])
        LogError("%T (%s)", "Failed to query database", LANG_SERVER, error);
}

public Query_VerifyBlock(Handle:owner, Handle:hndl, const String:error[], any:userid)
{
    decl String:clientAuth[64];
    new client = GetClientOfUserId(userid);

    #if defined DEBUG
        PrintToServer("Query_VerifyBlock(userid: %d, client: %d)", userid, client);
    #endif

    if (!client)
        return;

    /* Failure happen. Do retry with delay */
    if (DB_Conn_Lost(hndl))
    {
        LogError("Query_VerifyBlock failed: %s", error);
        if (g_hPlayerRecheck[client] == INVALID_HANDLE)
            g_hPlayerRecheck[client] = CreateTimer(RetryTime, ClientRecheck, userid);
        return;
    }

    GetClientAuthId(client, AuthId_Steam2, clientAuth, sizeof(clientAuth));

    //SELECT (c.ends - UNIX_TIMESTAMP()) as remaining, c.length, c.type, c.created, c.reason, a.user,
    //IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, c.aid, c.sid, c.authid
    //FROM %s_comms c LEFT JOIN %s_admins a ON a.aid=c.aid LEFT JOIN %s_srvgroups g ON g.name = a.srv_group
    //WHERE c.authid REGEXP '^STEAM_[0-9]:%s$' AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL",
    if (SQL_GetRowCount(hndl) > 0)
    {
        while(SQL_FetchRow(hndl))
        {
            if (NotApplyToThisServer(SQL_FetchInt(hndl, 8)))
                continue;

            new String:sReason[256], String:sAdmName[MAX_NAME_LENGTH], String:sAdmAuth[64];
            new remaining_time = SQL_FetchInt(hndl, 0);
            new length =         SQL_FetchInt(hndl, 1);
            new type =           SQL_FetchInt(hndl, 2);
            new time =           SQL_FetchInt(hndl, 3);
            SQL_FetchString(hndl, 4, sReason, sizeof(sReason));
            SQL_FetchString(hndl, 5, sAdmName, sizeof(sAdmName));
            new immunity =       SQL_FetchInt(hndl, 6);
            new aid =            SQL_FetchInt(hndl, 7);
            SQL_FetchString(hndl, 9, sAdmAuth, sizeof(sAdmAuth));

            // Block from CONSOLE (aid=0) and we have `console immunity` value in config
            if (!aid && ConsoleImmunity > immunity)
                immunity = ConsoleImmunity;

            #if defined DEBUG
                PrintToServer("Fetched from DB: remaining %d, length %d, type %d", remaining_time, length, type);
            #endif

            switch(type)
            {
                case TYPE_MUTE:
                {
                    if (g_MuteType[client] < bTime)
                    {
                        PerformMute(client, time, length / 60, sAdmName, sAdmAuth, immunity, sReason, remaining_time);
                        PrintToChat(client, "%s%t", PREFIX, "Muted on connect");
                    }
                }
                case TYPE_GAG:
                {
                    if (g_GagType[client] < bTime)
                    {
                        PerformGag(client, time, length / 60, sAdmName, sAdmAuth, immunity, sReason, remaining_time);
                        PrintToChat(client, "%s%t", PREFIX, "Gagged on connect");
                    }
                }
            }
        }
    }

    g_bPlayerStatus[client] = true;
}

// TIMER CALL BACKS //

public Action:ClientRecheck(Handle:timer, any:userid)
{
    #if defined DEBUG
        PrintToServer("ClientRecheck(userid: %d)", userid);
    #endif

    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    if (IsClientConnected(client))
        OnClientPostAdminCheck(client);

    g_hPlayerRecheck[client] =  INVALID_HANDLE;
}

public Action:Timer_MuteExpire(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    #if defined DEBUG
        decl String:clientAuth[64];
        GetClientAuthId(client, AuthId_Steam2, clientAuth,sizeof(clientAuth));
        PrintToServer("Mute expired for %s", clientAuth);
    #endif

    PrintToChat(client, "%s%t", PREFIX, "Mute expired");

    g_hMuteExpireTimer[client] = INVALID_HANDLE;
    MarkClientAsUnMuted(client);
    if (IsClientInGame(client))
        BaseComm_SetClientMute(client, false);
}

public Action:Timer_GagExpire(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    #if defined DEBUG
        decl String:clientAuth[64];
        GetClientAuthId(client, AuthId_Steam2, clientAuth,sizeof(clientAuth));
        PrintToServer("Gag expired for %s", clientAuth);
    #endif

    PrintToChat(client, "%s%t", PREFIX, "Gag expired");

    g_hGagExpireTimer[client] = INVALID_HANDLE;
    MarkClientAsUnGagged(client);
    if (IsClientInGame(client))
        BaseComm_SetClientGag(client, false);
}

public Action:Timer_StopWait(Handle:timer, any:data)
{
    g_DatabaseState = DatabaseState_None;
    DB_Connect();
}

// PARSER //

static InitializeConfigParser()
{
    if (ConfigParser == INVALID_HANDLE)
    {
        ConfigParser = SMC_CreateParser();
        SMC_SetReaders(ConfigParser, ReadConfig_NewSection, ReadConfig_KeyValue, ReadConfig_EndSection);
    }
}

static InternalReadConfig(const String:path[])
{
    ConfigState = ConfigStateNone;

    new SMCError:err = SMC_ParseFile(ConfigParser, path);

    if (err != SMCError_Okay)
    {
        decl String:buffer[64];
        if (SMC_GetErrorString(err, buffer, sizeof(buffer)))
        {
            PrintToServer(buffer);
        }
        else
        {
            PrintToServer("Fatal parse error");
        }
    }
}

public SMCResult:ReadConfig_NewSection(Handle:smc, const String:name[], bool:opt_quotes)
{
    if (name[0])
    {
        if (strcmp("Config", name, false) == 0)
        {
            ConfigState = ConfigStateConfig;
        }
        else if (strcmp("CommsReasons", name, false) == 0)
        {
            ConfigState = ConfigStateReasons;
        }
        else if (strcmp("CommsTimes", name, false) == 0)
        {
            ConfigState = ConfigStateTimes;
        }
        else if (strcmp("ServersWhiteList", name, false) == 0)
        {
            ConfigState = ConfigStateServers;
        }
    }
    return SMCParse_Continue;
}

public SMCResult:ReadConfig_KeyValue(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
{
    if (!key[0])
        return SMCParse_Continue;

    switch(ConfigState)
    {
        case ConfigStateConfig:
        {
            if (strcmp("DatabasePrefix", key, false) == 0)
            {
                strcopy(DatabasePrefix, sizeof(DatabasePrefix), value);

                if (DatabasePrefix[0] == '\0')
                {
                    DatabasePrefix = "sb";
                }
            }
            else if (strcmp("RetryTime", key, false) == 0)
            {
                RetryTime    = StringToFloat(value);
                if (RetryTime < 15.0)
                {
                    RetryTime = 15.0;
                }
                else if (RetryTime > 60.0)
                {
                    RetryTime = 60.0;
                }
            }
            else if (strcmp("ServerID", key, false) == 0)
            {
                if (!StringToIntEx(value, serverID) || serverID < 1)
                {
                    serverID = 0;
                }
            }
            else if (strcmp("DefaultTime", key, false) == 0)
            {
                DefaultTime    = StringToInt(value);
                if (DefaultTime < 0)
                {
                    DefaultTime = -1;
                }
                if (DefaultTime == 0)
                {
                    DefaultTime = 30;
                }
            }
            else if (strcmp("DisableUnblockImmunityCheck", key, false) == 0)
            {
                DisUBImCheck = StringToInt(value);
                if (DisUBImCheck != 1)
                {
                    DisUBImCheck = 0;
                }
            }
            else if (strcmp("ConsoleImmunity", key, false) == 0)
            {
                ConsoleImmunity = StringToInt(value);
                if (ConsoleImmunity < 0 || ConsoleImmunity > 100)
                {
                    ConsoleImmunity = 0;
                }
            }
            else if (strcmp("MaxLength", key, false) == 0)
            {
                ConfigMaxLength = StringToInt(value);
            }
            else if (strcmp("OnlyWhiteListServers", key, false) == 0)
            {
                ConfigWhiteListOnly = StringToInt(value);
                if (ConfigWhiteListOnly != 1)
                {
                    ConfigWhiteListOnly = 0;
                }
            }
        }
        case ConfigStateReasons:
        {
            Format(g_sReasonKey[iNumReasons], REASON_SIZE, "%s", key);
            Format(g_sReasonDisplays[iNumReasons], DISPLAY_SIZE, "%s", value);
            #if defined DEBUG
                PrintToServer("Loaded reason. index %d, key \"%s\", display_text \"%s\"", iNumReasons, g_sReasonKey[iNumReasons], g_sReasonDisplays[iNumReasons]);
            #endif
            iNumReasons++;
        }
        case ConfigStateTimes:
        {
            Format(g_sTimeDisplays[iNumTimes], DISPLAY_SIZE, "%s", value);
            g_iTimeMinutes[iNumTimes] = StringToInt(key);
            #if defined DEBUG
                PrintToServer("Loaded time. index %d, time %d minutes, display_text \"%s\"", iNumTimes, g_iTimeMinutes[iNumTimes] , g_sTimeDisplays[iNumTimes]);
            #endif
            iNumTimes++;
        }
        case ConfigStateServers:
        {
            if (strcmp("id", key, false) == 0)
            {
                new srvID = StringToInt(value);
                if (srvID >= 0)
                {
                    PushArrayCell(g_hServersWhiteList, srvID);
                    #if defined DEBUG
                        PrintToServer("Loaded white list server id %d", srvID);
                    #endif
                }
            }
        }
    }
    return SMCParse_Continue;
}

public SMCResult:ReadConfig_EndSection(Handle:smc)
{
    return SMCParse_Continue;
}

// STOCK FUNCTIONS //
stock setGag(client, length, const String:clientAuth[])
{
    if (g_GagType[client] == bNot)
    {
        PerformGag(client, _, length / 60, _, _, _, _);
        PrintToChat(client, "%s%t", PREFIX, "Gagged on connect");
        LogMessage("%s is gagged from web", clientAuth);
    }
}

stock setMute(client, length, const String:clientAuth[])
{
    if (g_MuteType[client] == bNot)
    {
        PerformMute(client, _, length / 60, _, _, _, _);
        PrintToChat(client, "%s%t", PREFIX, "Muted on connect");
        LogMessage("%s is muted from web", clientAuth);
    }
}

stock bool:DB_Connect()
{
    #if defined DEBUG
        PrintToServer("DB_Connect(handle %d, state %d, lock %d)", g_hDatabase, g_DatabaseState, g_iConnectLock);
    #endif

    if (g_hDatabase)
    {
        return true;
    }

    if (g_DatabaseState == DatabaseState_Wait) // 100500 connections in a minute is bad idea..
    {
        return false;
    }

    if(g_DatabaseState != DatabaseState_Connecting)
    {
        g_DatabaseState = DatabaseState_Connecting;
        g_iConnectLock  = ++g_iSequence;
        // Connect using the "sourcebans" section, or the "default" section if "sourcebans" does not exist
        SQL_TConnect(GotDatabase, DATABASE, g_iConnectLock);
    }

    return false;
}

stock bool:DB_Conn_Lost(Handle:hndl)
{
    if (hndl == INVALID_HANDLE)
    {
        if (g_hDatabase != INVALID_HANDLE)
        {
            LogError("Lost connection to DB. Reconnect after delay.");
            CloseHandle(g_hDatabase);
            g_hDatabase = INVALID_HANDLE;
        }
        if (g_DatabaseState != DatabaseState_Wait)
        {
            g_DatabaseState = DatabaseState_Wait;
            CreateTimer(RetryTime, Timer_StopWait, _, TIMER_FLAG_NO_MAPCHANGE);
        }
        return true;
    }
    else
    {
        return false;
    }
}

stock InitializeBackupDB()
{
    decl String:error[255];
    SQLiteDB = SQLite_UseDatabase("sourcecomms-queue", error, sizeof(error));
    if (SQLiteDB == INVALID_HANDLE)
    {
        SetFailState(error);
    }

    SQL_TQuery(SQLiteDB, Query_ErrorCheck,
       "CREATE TABLE IF NOT EXISTS queue2 ( \
            id INTEGER PRIMARY KEY, \
            steam_id TEXT, \
            time INTEGER, \
            start_time INTEGER, \
            reason TEXT, \
            name TEXT, \
            admin_id TEXT, \
            admin_ip TEXT, \
            type INTEGER)");
}

stock CreateBlock(client, targetId = 0, length = -1, type, const String:sReason[] = "", const String:sArgs[] = "")
{
    #if defined DEBUG
        PrintToServer("CreateBlock(admin: %d, target: %d, length: %d, type: %d, reason: %s, args: %s)", client, targetId, length, type, sReason, sArgs);
    #endif

    decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH], String:reason[256];
    new bool:skipped = false;

    // checking args
    if (targetId)
    {
        target_list[0] = targetId;
        target_count = 1;
        tn_is_ml = false;
        strcopy(target_name, sizeof(target_name), g_sName[targetId]);
        strcopy(reason,      sizeof(reason),      sReason);
    }
    else if (strlen(sArgs))
    {
        new String:sArg[3][192];

        if (ExplodeString(sArgs, "\"", sArg, 3, 192, true) == 3 && strlen(sArg[0]) == 0)    // exploding by quotes
        {
            new String:sTempArg[2][192];
            TrimString(sArg[2]);
            sArg[0] = sArg[1];        // target name
            ExplodeString(sArg[2], " ", sTempArg, 2, 192, true); // get length and reason
            sArg[1] = sTempArg[0];    // lenght
            sArg[2] = sTempArg[1];    // reason
        }
        else
        {
            ExplodeString(sArgs, " ", sArg, 3, 192, true);    // exploding by spaces
        }

        // Get the target, find target returns a message on failure so we do not
        if ((target_count = ProcessTargetString(
                sArg[0],
                client,
                target_list,
                MAXPLAYERS,
                COMMAND_FILTER_NO_BOTS,
                target_name,
                sizeof(target_name),
                tn_is_ml)) <= 0)
        {
            ReplyToTargetError(client, target_count);
            return;
        }

        // Get the block length
        if (!StringToIntEx(sArg[1], length))    // not valid number in second argument
        {
            length = DefaultTime;
            Format(reason, sizeof(reason), "%s %s", sArg[1], sArg[2]);
        }
        else
        {
            strcopy(reason, sizeof(reason), sArg[2]);
        }

        // Strip spaces and quotes from reason
        TrimString(reason);
        StripQuotes(reason);

        if (!IsAllowedBlockLength(client, length, target_count))
        {
            ReplyToCommand(client, "%s%t", PREFIX, "no access");
            return;
        }
    }
    else
    {
        return;
    }

    new admImmunity = GetAdmImmunity(client);
    decl String:adminAuth[64];

    if (client && IsClientInGame(client))
    {
        GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
    }

    for (new i = 0; i < target_count; i++)
    {
        new target = target_list[i];

        #if defined DEBUG
            decl String:auth[64];
            GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth));
            PrintToServer("Processing block for %s", auth);
        #endif

        if (!g_bPlayerStatus[target])
        {
            // The target has not been blocks verify. It must be completed before you can block anyone.
            ReplyToCommand(client, "%s%t", PREFIX, "Player Comms Not Verified");
            skipped = true;
            continue; // skip
        }

        switch(type)
        {
            case TYPE_MUTE:
            {
                if (!BaseComm_IsClientMuted(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not muted. Mute him, creating unmute timer and add record to DB", auth);
                    #endif

                    PerformMute(target, _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" muted \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already muted", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already muted", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_GAG:
            {
                if (!BaseComm_IsClientGagged(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not gagged. Gag him, creating ungag timer and add record to DB", auth);
                    #endif

                    PerformGag(target, _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" gagged \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already gagged", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already gagged", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_SILENCE:
            {
                if (!BaseComm_IsClientGagged(target) && !BaseComm_IsClientMuted(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not silenced. Silence him, creating ungag & unmute timers and add records to DB", auth);
                    #endif

                    PerformMute(target, _, length, g_sName[client], adminAuth, admImmunity, reason);
                    PerformGag(target,  _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" silenced \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already gagged or/and muted", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already silenced", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
        }
    }
    if (target_count == 1 && !skipped)
        SavePunishment(client, target_list[0], type, length, reason);
    if (target_count > 1 || !skipped)
        ShowActivityToServer(client, type, length, reason, target_name, tn_is_ml);

    return;
}

stock ProcessUnBlock(client, targetId = 0, type, String:sReason[] = "", const String:sArgs[] = "")
{
    #if defined DEBUG
        PrintToServer("ProcessUnBlock(admin: %d, target: %d, type: %d, reason: %s, args: %s)", client, targetId, type, sReason, sArgs);
    #endif

    decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH], String:reason[256];

    if(targetId)
    {
        target_list[0] = targetId;
        target_count = 1;
        tn_is_ml = false;
        strcopy(target_name, sizeof(target_name), g_sName[targetId]);
        strcopy(reason,      sizeof(reason),      sReason);
    }
    else
    {
        new String:sBuffer[256], String:sArg[3][192];
        GetCmdArgString(sBuffer, sizeof(sBuffer));

        if (ExplodeString(sBuffer, "\"", sArg, 3, 192, true) == 3 && strlen(sArg[0]) == 0)
        {
            TrimString(sArg[2]);
            sArg[0] = sArg[1];  // target name
            sArg[1] = sArg[2];  // reason; sArg[2] - not in use
        }
        else
        {
            ExplodeString(sBuffer, " ", sArg, 2, 192, true);
        }
        strcopy(reason, sizeof(reason), sArg[1]);
        // Strip spaces and quotes from reason
        TrimString(reason);
        StripQuotes(reason);

        // Get the target, find target returns a message on failure so we do not
        if ((target_count = ProcessTargetString(
                sArg[0],
                client,
                target_list,
                MAXPLAYERS,
                COMMAND_FILTER_NO_BOTS,
                target_name,
                sizeof(target_name),
                tn_is_ml)) <= 0)
        {
            ReplyToTargetError(client, target_count);
            return;
        }
    }

    decl String:adminAuth[64];
    decl String:targetAuth[64];

    if (client && IsClientInGame(client))
    {
        GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
    }

    if (target_count > 1)
    {
        #if defined DEBUG
            PrintToServer("ProcessUnBlock - targets_count > 1");
        #endif

        for (new i = 0; i < target_count; i++)
        {
            new target = target_list[i];

            if (IsClientInGame(target))
                GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
            else
                continue;

            new Handle:dataPack = CreateDataPack();
            WritePackCell(dataPack, GetClientUserId2(client));
            WritePackCell(dataPack, GetClientUserId(target));
            WritePackCell(dataPack, type);
            WritePackString(dataPack, adminAuth);
            WritePackString(dataPack, targetAuth);    // not in use in this case
            WritePackString(dataPack, reason);

            TempUnBlock(dataPack);
        }

        #if defined DEBUG
            PrintToServer("Showing activity to server in ProcessUnBlock for targets_count > 1");
        #endif
        ShowActivityToServer(client, type + TYPE_TEMP_SHIFT, _, _, target_name, tn_is_ml);
    }
    else
    {
        decl String:typeWHERE[100];
        new bool:dontCheckDB = false;
        new target = target_list[0];

        if (IsClientInGame(target))
        {
            GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
        }
        else
        {
            return;
        }

        switch(type)
        {
            case TYPE_UNMUTE:
            {
                if (!BaseComm_IsClientMuted(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not muted");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "c.type = '%d'", TYPE_MUTE);
                    if (g_MuteType[target] == bSess)
                        dontCheckDB = true;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNGAG:
            {
                if (!BaseComm_IsClientGagged(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not gagged");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "c.type = '%d'", TYPE_GAG);
                    if (g_GagType[target] == bSess)
                        dontCheckDB = true;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNSILENCE:
            {
                if (!BaseComm_IsClientMuted(target) || !BaseComm_IsClientGagged(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not silenced");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "(c.type = '%d' OR c.type = '%d')", TYPE_MUTE, TYPE_GAG);
                    if (g_MuteType[target] == bSess && g_GagType[target] == bSess)
                        dontCheckDB = true;
                }
            }
        }

        // Pack everything into a data pack so we can retain it
        new Handle:dataPack = CreateDataPack();
        WritePackCell(dataPack, GetClientUserId2(client));
        WritePackCell(dataPack, GetClientUserId(target));
        WritePackCell(dataPack, type);
        WritePackString(dataPack, adminAuth);
        WritePackString(dataPack, targetAuth);
        WritePackString(dataPack, reason);

        // Check current player status. If player has temporary punishment - don't get info from DB
        if (!dontCheckDB && DB_Connect())
        {
            new String:sAdminAuthEscaped[sizeof(adminAuth) * 2 + 1];
            new String:sAdminAuthYZEscaped[sizeof(adminAuth) * 2 + 1];
            new String:sTargetAuthEscaped[sizeof(targetAuth) * 2 + 1];
            new String:sTargetAuthYZEscaped[sizeof(targetAuth) * 2 + 1];

            SQL_EscapeString(g_hDatabase, adminAuth,     sAdminAuthEscaped,    sizeof(sAdminAuthEscaped));
            SQL_EscapeString(g_hDatabase, adminAuth[8],  sAdminAuthYZEscaped,  sizeof(sAdminAuthYZEscaped));
            SQL_EscapeString(g_hDatabase, targetAuth,    sTargetAuthEscaped,   sizeof(sTargetAuthEscaped));
            SQL_EscapeString(g_hDatabase, targetAuth[8], sTargetAuthYZEscaped, sizeof(sTargetAuthYZEscaped));

            decl String:query[4096];
            Format(query, sizeof(query),
               "SELECT      c.bid, \
                            IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0') as iaid, \
                            c.aid, \
                            IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, \
                            c.type \
                FROM        %s_comms     AS c \
                LEFT JOIN   %s_admins    AS a ON a.aid = c.aid \
                LEFT JOIN   %s_srvgroups AS g ON g.name = a.srv_group \
                WHERE       RemoveType IS NULL \
                              AND (c.authid = '%s' OR c.authid REGEXP '^STEAM_[0-9]:%s$') \
                              AND (length = '0' OR ends > UNIX_TIMESTAMP()) \
                              AND %s",
                DatabasePrefix, sAdminAuthEscaped, sAdminAuthYZEscaped, DatabasePrefix, DatabasePrefix, DatabasePrefix, sTargetAuthEscaped, sTargetAuthYZEscaped, typeWHERE);

            #if defined LOG_QUERIES
                LogToFile(logQuery, "ProcessUnBlock. QUERY: %s", query);
            #endif

            SQL_TQuery(g_hDatabase, Query_UnBlockSelect, query, dataPack);
        }
        else
        {
            #if defined DEBUG
                PrintToServer("Calling TempUnBlock from ProcessUnBlock");
            #endif

            if (TempUnBlock(dataPack))
                ShowActivityToServer(client, type + TYPE_TEMP_SHIFT, _, _, g_sName[target], _);
        }
    }
}

stock bool:TempUnBlock(Handle:data)
{
    decl String:adminAuth[30], String:targetAuth[30], String:reason[256];
    ResetPack(data);
    new adminUserID  = ReadPackCell(data);
    new targetUserID = ReadPackCell(data);
    new type         = ReadPackCell(data);
    ReadPackString(data, adminAuth,  sizeof(adminAuth));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    ReadPackString(data, reason,     sizeof(reason));
    CloseHandle(data);    // Need to close datapack

    #if defined DEBUG
        PrintToServer("TempUnBlock(adminUID: %d, targetUID: %d, type: %d, adminAuth: %s, targetAuth: %s, reason: %s)", adminUserID, targetUserID, type, adminAuth, targetAuth, reason);
    #endif

    new admin  = GetClientOfUserId(adminUserID);
    new target = GetClientOfUserId(targetUserID);
    if (!target)
        return false; // target has gone away

    new AdmImmunity = GetAdmImmunity(admin);
    new bool:AdmImCheck = ( DisUBImCheck == 0
                            &&  (  ( type == TYPE_MUTE    && AdmImmunity > g_iMuteLevel[target] )
                                || ( type == TYPE_GAG     && AdmImmunity > g_iGagLevel[target]  )
                                || ( type == TYPE_SILENCE && AdmImmunity > g_iMuteLevel[target]
                                                          && AdmImmunity > g_iGagLevel[target])
                                )
                          );

    #if defined DEBUG
        PrintToServer("WHO WE ARE CHECKING!");
        if (!admin)
            PrintToServer("we are console (possibly)");
        if (AdmHasFlag(admin))
            PrintToServer("we have special flag");
    #endif

    // Check access for unblock without db changes (temporary unblock)
    new bool:bHasPermission = (!admin && StrEqual(adminAuth, "STEAM_ID_SERVER")) || AdmHasFlag(admin) || AdmImCheck;
    // can, if we are console or have special flag. else - deep checking by issuer authid
    if (!bHasPermission) {
        switch(type)
        {
            case TYPE_UNMUTE:
            {
                bHasPermission = StrEqual(adminAuth, g_sMuteAdminAuth[target]);
            }
            case TYPE_UNGAG:
            {
                bHasPermission = StrEqual(adminAuth, g_sGagAdminAuth[target]);
            }
            case TYPE_UNSILENCE:
            {
                bHasPermission = StrEqual(adminAuth, g_sMuteAdminAuth[target]) && StrEqual(adminAuth, g_sGagAdminAuth[target]);
            }
        }
    }

    if (bHasPermission)
    {
        switch(type)
        {
            case TYPE_UNMUTE:
            {
                PerformUnMute(target);
                LogAction(admin, target, "\"%L\" temporary unmuted \"%L\" (reason \"%s\")", admin, target, reason);
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNGAG:
            {
                PerformUnGag(target);
                LogAction(admin, target, "\"%L\" temporary ungagged \"%L\" (reason \"%s\")", admin, target, reason);
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNSILENCE:
            {
                PerformUnMute(target);
                PerformUnGag(target);
                LogAction(admin, target, "\"%L\" temporary unsilenced \"%L\" (reason \"%s\")", admin, target, reason);
            }
            default:
            {
                return false;
            }
        }
        return true;
    }
    else
    {
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "No db error unlock perm");
            PrintToConsole(admin, "%s%t", PREFIX, "No db error unlock perm");
        }
        return false;
    }
}

stock InsertTempBlock(length, type, const String:name[], const String:auth[], const String:reason[], const String:adminAuth[], const String:adminIp[])
{
    LogMessage("Saving punishment for %s into queue", auth);

    new String:banName[MAX_NAME_LENGTH * 2 + 1];
    new String:banReason[256 * 2 + 1];
    new String:sAuthEscaped[64 * 2 + 1];
    new String:sAdminAuthEscaped[64 * 2 + 1];
    decl String:sQuery[4096], String:sQueryVal[2048];
    new String:sQueryMute[2048], String:sQueryGag[2048];

    // escaping everything
    SQL_EscapeString(SQLiteDB, name,      banName,           sizeof(banName));
    SQL_EscapeString(SQLiteDB, reason,    banReason,         sizeof(banReason));
    SQL_EscapeString(SQLiteDB, auth,      sAuthEscaped,      sizeof(sAuthEscaped));
    SQL_EscapeString(SQLiteDB, adminAuth, sAdminAuthEscaped, sizeof(sAdminAuthEscaped));

    // steam_id time start_time reason name admin_id admin_ip
    FormatEx(sQueryVal, sizeof(sQueryVal),
        "'%s', %d, %d, '%s', '%s', '%s', '%s'",
        sAuthEscaped, length, GetTime(), banReason, banName, sAdminAuthEscaped, adminIp);

    switch (type)
    {
        case TYPE_MUTE: FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, type);
        case TYPE_GAG:  FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, type);
        case TYPE_SILENCE:
        {
            FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, TYPE_MUTE);
            FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, TYPE_GAG);
        }
    }

    FormatEx(sQuery, sizeof(sQuery),
        "INSERT INTO queue2 (steam_id, time, start_time, reason, name, admin_id, admin_ip, type) VALUES %s%s%s",
        sQueryMute, type == TYPE_SILENCE ? ", " : "", sQueryGag);

    #if defined LOG_QUERIES
        LogToFile(logQuery, "InsertTempBlock. QUERY: %s", sQuery);
    #endif

    SQL_TQuery(SQLiteDB, Query_ErrorCheck, sQuery);
}

stock ServerInfo()
{
    decl pieces[4];
    new longip = GetConVarInt(CvarHostIp);
    pieces[0] = (longip >> 24) & 0x000000FF;
    pieces[1] = (longip >> 16) & 0x000000FF;
    pieces[2] = (longip >> 8) & 0x000000FF;
    pieces[3] = longip & 0x000000FF;
    FormatEx(ServerIp, sizeof(ServerIp), "%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
    GetConVarString(CvarPort, ServerPort, sizeof(ServerPort));
}

stock ReadConfig()
{
    InitializeConfigParser();

    if (ConfigParser == INVALID_HANDLE)
    {
        return;
    }

    decl String:ConfigFile1[PLATFORM_MAX_PATH], String:ConfigFile2[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, ConfigFile1, sizeof(ConfigFile1), "configs/sourcebans/sourcebans.cfg");
    BuildPath(Path_SM, ConfigFile2, sizeof(ConfigFile2), "configs/sourcebans/sourcecomms.cfg");

    if (FileExists(ConfigFile1))
    {
        PrintToServer("%sLoading configs/sourcebans/sourcebans.cfg config file", PREFIX);
        InternalReadConfig(ConfigFile1);
    }
    else
    {
        SetFailState("FATAL *** ERROR *** can't find %s", ConfigFile1);
    }
    if (FileExists(ConfigFile2))
    {
        PrintToServer("%sLoading configs/sourcecomms.cfg config file", PREFIX);
        PrintToServer("%sLoading configs/sourcecomms.cfg config file", PREFIX);
        iNumReasons = 0;
        iNumTimes = 0;
        InternalReadConfig(ConfigFile2);
        if (iNumReasons)
            iNumReasons--;
        if (iNumTimes)
            iNumTimes--;
        if (serverID == 0)
        {
            LogError("You must set valid `ServerID` value in sourcebans.cfg!");
            if (ConfigWhiteListOnly)
            {
                LogError("ServersWhiteList feature disabled!");
                ConfigWhiteListOnly = 0;
            }
        }
    }
    else
    {
        SetFailState("FATAL *** ERROR *** can't find %s", ConfigFile2);
    }
    #if defined DEBUG
        PrintToServer("Loaded DefaultTime value: %d", DefaultTime);
        PrintToServer("Loaded DisableUnblockImmunityCheck value: %d", DisUBImCheck);
    #endif
}

// some more

AdminMenu_GetPunishPhrase(client, target, String:name[], length)
{
    decl String:Buffer[192];
    if (g_MuteType[target] > bNot && g_GagType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Silenced", client, name);
    else if (g_MuteType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Muted", client, name);
    else if (g_GagType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Gagged", client, name);
    else
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_None", client, name);

    strcopy(name, length, Buffer);
}

bool:Bool_ValidMenuTarget(client, target)
{
    if (target <= 0)
    {
        if (client)
            PrintToChat(client, "%s%t", PREFIX, "AdminMenu_Not_Available");
        else
            ReplyToCommand(client, "%s%t", PREFIX, "AdminMenu_Not_Available");

        return false;
    }
    else if (!CanUserTarget(client, target))
    {
        if (client)
            PrintToChat(client, "%s%t", PREFIX, "Command_Target_Not_Targetable");
        else
            ReplyToCommand(client, "%s%t", PREFIX, "Command_Target_Not_Targetable");

        return false;
    }

    return true;
}

stock bool:IsAllowedBlockLength(admin, length, target_count = 1)
{
    if (target_count == 1)
    {
        if (!ConfigMaxLength)
            return true;    // Restriction disabled
        if (!admin)
            return true;    // all allowed for console
        if (AdmHasFlag(admin))
            return true;    // all allowed for admins with special flag
        if (!length || length > ConfigMaxLength)
            return false;
        else
            return true;
    }
    else
    {
        if (length < 0)
            return true;    // session punishments allowed for mass-tergeting
        if (!length)
            return false;
        if (length > MAX_TIME_MULTI)
            return false;
        if (length > DefaultTime)
            return false;
        else
            return true;
    }
}

stock bool:AdmHasFlag(admin)
{
    return admin && CheckCommandAccess(admin, "", UNBLOCK_FLAG, true);
}

stock _:GetAdmImmunity(admin)
{
    if (admin > 0 && GetUserAdmin(admin) != INVALID_ADMIN_ID)
        return GetAdminImmunityLevel(GetUserAdmin(admin));
    else
        return 0;
}

stock _:GetClientUserId2(client)
{
    if (client)
        return GetClientUserId(client);
    else
        return 0;    // for CONSOLE
}

stock ForcePlayersRecheck()
{
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i) && g_hPlayerRecheck[i] == INVALID_HANDLE)
        {
            #if defined DEBUG
            {
                decl String:clientAuth[64];
                GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
                PrintToServer("Creating Recheck timer for %s", clientAuth);
            }
            #endif
            g_hPlayerRecheck[i] = CreateTimer(float(i), ClientRecheck, GetClientUserId(i));
        }
    }
}

stock bool:NotApplyToThisServer(srvID)
{
    if (ConfigWhiteListOnly && FindValueInArray(g_hServersWhiteList, srvID) == -1)
        return true;
    else
        return false;
}

stock MarkClientAsUnMuted(target)
{
    g_MuteType[target]          = bNot;
    g_iMuteTime[target]         = 0;
    g_iMuteLength[target]       = 0;
    g_iMuteLevel[target]        = -1;
    g_sMuteAdminName[target][0] = '\0';
    g_sMuteReason[target][0]    = '\0';
    g_sMuteAdminAuth[target][0] = '\0';
}

stock MarkClientAsUnGagged(target)
{
    g_GagType[target]          = bNot;
    g_iGagTime[target]         = 0;
    g_iGagLength[target]       = 0;
    g_iGagLevel[target]        = -1;
    g_sGagAdminName[target][0] = '\0';
    g_sGagReason[target][0]    = '\0';
    g_sGagAdminAuth[target][0] = '\0';
}

stock MarkClientAsMuted(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "")
{
    if (time)
        g_iMuteTime[target] = time;
    else
        g_iMuteTime[target] = GetTime();

    g_iMuteLength[target] = length;
    g_iMuteLevel[target]  = adminImmunity ? adminImmunity : ConsoleImmunity;
    strcopy(g_sMuteAdminName[target], sizeof(g_sMuteAdminName[]), adminName);
    strcopy(g_sMuteReason[target],    sizeof(g_sMuteReason[]),    reason);
    strcopy(g_sMuteAdminAuth[target], sizeof(g_sMuteAdminAuth[]), adminAuth);

    if (length > 0)
        g_MuteType[target] = bTime;
    else if (length == 0)
        g_MuteType[target] = bPerm;
    else
        g_MuteType[target] = bSess;
}

stock MarkClientAsGagged(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "")
{
    if (time)
        g_iGagTime[target] = time;
    else
        g_iGagTime[target] = GetTime();

    g_iGagLength[target] = length;
    g_iGagLevel[target]  = adminImmunity ? adminImmunity : ConsoleImmunity;
    strcopy(g_sGagAdminName[target], sizeof(g_sGagAdminName[]), adminName);
    strcopy(g_sGagReason[target],    sizeof(g_sGagReason[]),    reason);
    strcopy(g_sGagAdminAuth[target], sizeof(g_sGagAdminAuth[]), adminAuth);

    if (length > 0)
        g_GagType[target] = bTime;
    else if (length == 0)
        g_GagType[target] = bPerm;
    else
        g_GagType[target] = bSess;
}

stock CloseMuteExpireTimer(target)
{
    if (g_hMuteExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hMuteExpireTimer[target]))
        g_hMuteExpireTimer[target] = INVALID_HANDLE;
}

stock CloseGagExpireTimer(target)
{
    if (g_hGagExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hGagExpireTimer[target]))
        g_hGagExpireTimer[target] = INVALID_HANDLE;
}

stock CreateMuteExpireTimer(target, remainingTime = 0)
{
    if (g_iMuteLength[target] > 0)
    {
        if (remainingTime)
            g_hMuteExpireTimer[target] = CreateTimer(float(remainingTime),              Timer_MuteExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
        else
            g_hMuteExpireTimer[target] = CreateTimer(float(g_iMuteLength[target] * 60), Timer_MuteExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
    }
}

stock CreateGagExpireTimer(target, remainingTime = 0)
{
    if (g_iGagLength[target] > 0)
    {
        if (remainingTime)
            g_hGagExpireTimer[target] = CreateTimer(float(remainingTime),             Timer_GagExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
        else
            g_hGagExpireTimer[target] = CreateTimer(float(g_iGagLength[target] * 60), Timer_GagExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
    }
}

stock PerformUnMute(target)
{
    MarkClientAsUnMuted(target);
    BaseComm_SetClientMute(target, false);
    CloseMuteExpireTimer(target);
}

stock PerformUnGag(target)
{
    MarkClientAsUnGagged(target);
    BaseComm_SetClientGag(target, false);
    CloseGagExpireTimer(target);
}

stock PerformMute(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "", remaining_time = 0)
{
    MarkClientAsMuted(target, time, length, adminName, adminAuth, adminImmunity, reason);
    BaseComm_SetClientMute(target, true);
    CreateMuteExpireTimer(target, remaining_time);
}

stock PerformGag(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "", remaining_time = 0)
{
    MarkClientAsGagged(target, time, length, adminName, adminAuth, adminImmunity, reason);
    BaseComm_SetClientGag(target, true);
    CreateGagExpireTimer(target, remaining_time);
}

stock SavePunishment(admin = 0, target, type, length = -1 , const String:reason[] = "")
{
    if (type < TYPE_MUTE || type > TYPE_SILENCE)
        return;

    // target information
    new String:targetAuth[64];
    if (IsClientInGame(target))
    {
        GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
    }
    else
    {
        return;
    }

    new String:adminIp[24];
    new String:adminAuth[64];
    if (admin && IsClientInGame(admin))
    {
        GetClientIP(admin, adminIp, sizeof(adminIp));
        GetClientAuthId(admin, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
        strcopy(adminIp, sizeof(adminIp), ServerIp);
    }

    new String:sName[MAX_NAME_LENGTH];
    strcopy(sName, sizeof(sName), g_sName[target]);

    if (DB_Connect())
    {
        // Accepts length in minutes, writes to db in seconds! In all over places in plugin - length is in minutes.
        new String:banName[MAX_NAME_LENGTH * 2 + 1];
        new String:banReason[256 * 2 + 1];
        new String:sAuthidEscaped[64 * 2 + 1];
        new String:sAdminAuthIdEscaped[64 * 2 + 1];
        new String:sAdminAuthIdYZEscaped[64 * 2 + 1];
        decl String:sQuery[4096], String:sQueryAdm[512], String:sQueryVal[1024];
        new String:sQueryMute[1024], String:sQueryGag[1024];

        // escaping everything
        SQL_EscapeString(g_hDatabase, sName,        banName,               sizeof(banName));
        SQL_EscapeString(g_hDatabase, reason,       banReason,             sizeof(banReason));
        SQL_EscapeString(g_hDatabase, targetAuth,   sAuthidEscaped,        sizeof(sAuthidEscaped));
        SQL_EscapeString(g_hDatabase, adminAuth,    sAdminAuthIdEscaped,   sizeof(sAdminAuthIdEscaped));
        SQL_EscapeString(g_hDatabase, adminAuth[8], sAdminAuthIdYZEscaped, sizeof(sAdminAuthIdYZEscaped));

        // bid    authid    name    created ends lenght reason aid adminip    sid    removedBy removedType removedon type ureason
        FormatEx(sQueryAdm, sizeof(sQueryAdm),
            "IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), 0)",
            DatabasePrefix, sAdminAuthIdEscaped, sAdminAuthIdYZEscaped);

        // authid name, created, ends, length, reason, aid, adminIp, sid
        FormatEx(sQueryVal, sizeof(sQueryVal),
            "'%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', %s, '%s', %d",
            sAuthidEscaped, banName, length*60, length*60, banReason, sQueryAdm, adminIp, serverID);

        switch (type)
        {
            case TYPE_GAG:  FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, type);
            case TYPE_MUTE: FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, type);
            case TYPE_SILENCE:
            {
                FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, TYPE_MUTE);
                FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, TYPE_GAG);
            }
        }

        // litle magic - one query for all actions (mute, gag or silence)
        FormatEx(sQuery, sizeof(sQuery),
            "INSERT INTO %s_comms (authid, name, created, ends, length, reason, aid, adminIp, sid, type) VALUES %s%s%s",
            DatabasePrefix, sQueryMute, type == TYPE_SILENCE ? ", " : "", sQueryGag);

        #if defined LOG_QUERIES
            LogToFile(logQuery, "SavePunishment. QUERY: %s", sQuery);
        #endif

        // all data cached before calling asynchronous functions
        new Handle:dataPack = CreateDataPack();
        WritePackCell(dataPack, length);
        WritePackCell(dataPack, type);
        WritePackString(dataPack, sName);
        WritePackString(dataPack, targetAuth);
        WritePackString(dataPack, reason);
        WritePackString(dataPack, adminAuth);
        WritePackString(dataPack, adminIp);

        SQL_TQuery(g_hDatabase, Query_AddBlockInsert, sQuery, dataPack, DBPrio_High);
    }
    else
        InsertTempBlock(length, type, sName, targetAuth, reason, adminAuth, adminIp);
}

stock ShowActivityToServer(admin, type, length = 0, String:reason[] = "", String:targetName[], bool:ml = false)
{
    #if defined DEBUG
        PrintToServer("ShowActivityToServer(admin: %d, type: %d, length: %d, reason: %s, name: %s, ml: %b",
            admin, type, length, reason, targetName, ml);
    #endif

    new String:actionName[32], String:translationName[64];
    switch(type)
    {
        case TYPE_MUTE:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Muted");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permamuted");
            else    // temp block
                strcopy(actionName, sizeof(actionName), "Temp muted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_GAG:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Gagged");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permagagged");
            else    //temp block
                strcopy(actionName, sizeof(actionName), "Temp gagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_SILENCE:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Silenced");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permasilenced");
            else    //temp block
                strcopy(actionName, sizeof(actionName), "Temp silenced");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_UNMUTE:
        {
            strcopy(actionName, sizeof(actionName), "Unmuted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_UNGAG:
        {
            strcopy(actionName, sizeof(actionName), "Ungagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNMUTE:
        {
            strcopy(actionName, sizeof(actionName), "Temp unmuted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNGAG:
        {
            strcopy(actionName, sizeof(actionName), "Temp ungagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNSILENCE:
        {
            strcopy(actionName, sizeof(actionName), "Temp unsilenced");
        }
        //-------------------------------------------------------------------------------------------------
        default:
        {
            return;
        }
    }

    Format(translationName, sizeof(translationName), "%s %s", actionName, reason[0] == '\0' ? "player" : "player reason");
    #if defined DEBUG
        PrintToServer("translation name: %s", translationName);
    #endif

    if (length > 0)
    {
        if (ml)
            ShowActivity2(admin, PREFIX, "%t", translationName,       targetName, length, reason);
        else
            ShowActivity2(admin, PREFIX, "%t", translationName, "_s", targetName, length, reason);
    }
    else
    {
        if (ml)
            ShowActivity2(admin, PREFIX, "%t", translationName,       targetName,         reason);
        else
            ShowActivity2(admin, PREFIX, "%t", translationName, "_s", targetName,         reason);
    }
}

// Natives //
public Native_SetClientMute(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    new bool:muteState = GetNativeCell(2);
    new muteLength = GetNativeCell(3);
    if (muteState && muteLength == 0)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Permanent mute is not allowed!");
    }

    new bool:bSaveToDB = GetNativeCell(4);
    if (!muteState && bSaveToDB)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Removing punishments from DB is not allowed!");
    }

    decl String:sReason[256];
    GetNativeString(5, sReason, sizeof(sReason));

    if (muteState)
    {
        if (g_MuteType[target] > bNot)
        {
            return false;
        }

        PerformMute(target, _, muteLength, _, _, _, sReason);

        if (bSaveToDB)
            SavePunishment(_, target, TYPE_MUTE, muteLength, sReason);
    }
    else
    {
        if (g_MuteType[target] == bNot)
        {
            return false;
        }

        PerformUnMute(target);
    }

    return true;
}

public Native_SetClientGag(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    new bool:gagState = GetNativeCell(2);
    new gagLength = GetNativeCell(3);
    if (gagState && gagLength == 0)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Permanent gag is not allowed!");
    }

    new bool:bSaveToDB = GetNativeCell(4);
    if (!gagState && bSaveToDB)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Removing punishments from DB is not allowed!");
    }

    decl String:sReason[256];
    GetNativeString(5, sReason, sizeof(sReason));

    if (gagState)
    {
        if (g_GagType[target] > bNot)
        {
            return false;
        }

        PerformGag(target, _, gagLength, _, _, _, sReason);

        if (bSaveToDB)
            SavePunishment(_, target, TYPE_GAG, gagLength, sReason);
    }
    else
    {
        if (g_GagType[target] == bNot)
        {
            return false;
        }

        PerformUnGag(target);
    }

    return true;
}

public Native_GetClientMuteType(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    return bType:g_MuteType[target];
}

public Native_GetClientGagType(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    return bType:g_GagType[target];
}
//Yarr!

// *************************************************************************
//  This file is part of SourceBans: Reloaded.
//
//  Copyright (C) 2014-2015 Sarabveer Singh <sarabveer@sarabveer.me>
//
//  SourceBans: Reloaded is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans: Reloaded 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 Affero General Public License
//  along with SourceBans: Reloaded. If not, see <http://www.gnu.org/licenses/>.
//
//  This file incorporates work covered by the following copyright(s):
//
//   SourceComms 0.9.266
//   Copyright (C) 2013-2014 Alexandr Duplishchev
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1883705> - <https://github.com/d-ai/SourceComms>
//
// *************************************************************************

#pragma semicolon 1

#include <sourcemod>
#include <basecomm>
#include <sourcecomms>

#undef REQUIRE_PLUGIN
#include <adminmenu>

#define UNBLOCK_FLAG ADMFLAG_CUSTOM2
#define DATABASE "sourcebans"

// #define DEBUG
// #define LOG_QUERIES

// Do not edit below this line //
//-----------------------------//

#define PLUGIN_VERSION "SBR-1.5.2F-R2-dev"
#define PREFIX "\x04[SourceComms]\x01 "

#define MAX_TIME_MULTI 30       // maximum mass-target punishment length

#define NOW 0
#define TYPE_TEMP_SHIFT 10

#define TYPE_MUTE 1
#define TYPE_GAG 2
#define TYPE_SILENCE 3
#define TYPE_UNMUTE 4
#define TYPE_UNGAG 5
#define TYPE_UNSILENCE 6
#define TYPE_TEMP_UNMUTE 14     // TYPE_TEMP_SHIFT + TYPE_UNMUTE
#define TYPE_TEMP_UNGAG 15      // TYPE_TEMP_SHIFT + TYPE_UNGAG
#define TYPE_TEMP_UNSILENCE 16  // TYPE_TEMP_SHIFT + TYPE_UNSILENCE

#define MAX_REASONS 32
#define DISPLAY_SIZE 64
#define REASON_SIZE 192

new iNumReasons;
new String:g_sReasonDisplays[MAX_REASONS][DISPLAY_SIZE], String:g_sReasonKey[MAX_REASONS][REASON_SIZE];

#define MAX_TIMES 32
new iNumTimes, g_iTimeMinutes[MAX_TIMES];
new String:g_sTimeDisplays[MAX_TIMES][DISPLAY_SIZE];

enum State /* ConfigState */
{
    ConfigStateNone = 0,
    ConfigStateConfig,
    ConfigStateReasons,
    ConfigStateTimes,
    ConfigStateServers,
}
enum DatabaseState /* Database connection state */
{
    DatabaseState_None = 0,
    DatabaseState_Wait,
    DatabaseState_Connecting,
    DatabaseState_Connected,
}

new DatabaseState:g_DatabaseState;
new g_iConnectLock = 0;
new g_iSequence    = 0;

new State:ConfigState;
new Handle:ConfigParser;

new Handle:hTopMenu = INVALID_HANDLE;

/* Cvar handle*/
new Handle:CvarHostIp;
new Handle:CvarPort;

new String:ServerIp[24];
new String:ServerPort[7];

/* Database handle */
new Handle:g_hDatabase;
new Handle:SQLiteDB;

new String:DatabasePrefix[10] = "sb";

/* Timer handles */
new Handle:g_hPlayerRecheck[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:g_hGagExpireTimer[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:g_hMuteExpireTimer[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};

/* Log Stuff */
#if defined LOG_QUERIES
    new String:logQuery[256];
#endif

new Float:RetryTime     = 15.0;
new DefaultTime         = 30;
new DisUBImCheck        = 0;
new ConsoleImmunity     = 0;
new ConfigMaxLength     = 0;
new ConfigWhiteListOnly = 0;
new serverID = 0;

/* List menu */
enum PeskyPanels
{
    curTarget,
    curIndex,
    viewingMute,
    viewingGag,
    viewingList,
}
new g_iPeskyPanels[MAXPLAYERS + 1][PeskyPanels];

new bool:g_bPlayerStatus[MAXPLAYERS + 1];   // Player block check status
new String:g_sName[MAXPLAYERS + 1][MAX_NAME_LENGTH];

new bType:g_MuteType[MAXPLAYERS + 1];
new g_iMuteTime[MAXPLAYERS + 1];
new g_iMuteLength[MAXPLAYERS + 1];  // in sec
new g_iMuteLevel[MAXPLAYERS + 1];   // immunity level of admin
new String:g_sMuteAdminName[MAXPLAYERS + 1][MAX_NAME_LENGTH];
new String:g_sMuteReason[MAXPLAYERS + 1][256];
new String:g_sMuteAdminAuth[MAXPLAYERS + 1][64];

new bType:g_GagType[MAXPLAYERS + 1];
new g_iGagTime[MAXPLAYERS + 1];
new g_iGagLength[MAXPLAYERS + 1]; // in sec
new g_iGagLevel[MAXPLAYERS + 1]; // immunity level of admin
new String:g_sGagAdminName[MAXPLAYERS + 1][MAX_NAME_LENGTH];
new String:g_sGagReason[MAXPLAYERS + 1][256];
new String:g_sGagAdminAuth[MAXPLAYERS + 1][64];

new Handle:g_hServersWhiteList = INVALID_HANDLE;

public Plugin:myinfo =
{
    name = "SourceComms",
    author = "Alex, Sarabveer(VEER™)",
    description = "Advanced punishments management for the Source engine in SourceBans style",
    version = PLUGIN_VERSION,
    url = "https://forums.alliedmods.net/showthread.php?t=207176"
};

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    CreateNative("SourceComms_SetClientMute",       Native_SetClientMute);
    CreateNative("SourceComms_SetClientGag",        Native_SetClientGag);
    CreateNative("SourceComms_GetClientMuteType",   Native_GetClientMuteType);
    CreateNative("SourceComms_GetClientGagType",    Native_GetClientGagType);
    MarkNativeAsOptional("SQL_SetCharset");
    RegPluginLibrary("sourcecomms");
    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("common.phrases");
    LoadTranslations("sourcecomms.phrases");

    new Handle:hTemp = INVALID_HANDLE;
    if (LibraryExists("adminmenu") && ((hTemp = GetAdminTopMenu()) != INVALID_HANDLE))
        OnAdminMenuReady(hTemp);

    CvarHostIp = FindConVar("hostip");
    CvarPort = FindConVar("hostport");
    g_hServersWhiteList = CreateArray();

    CreateConVar("sourcecomms_version", PLUGIN_VERSION, _, FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
    AddCommandListener(CommandCallback,       "sm_gag");
    AddCommandListener(CommandCallback,       "sm_mute");
    AddCommandListener(CommandCallback,       "sm_silence");
    AddCommandListener(CommandCallback,       "sm_ungag");
    AddCommandListener(CommandCallback,       "sm_unmute");
    AddCommandListener(CommandCallback,       "sm_unsilence");
    RegServerCmd("sc_fw_block", FWBlock,      "Blocking player comms by command from sourceban web site", FCVAR_PLUGIN);
    RegServerCmd("sc_fw_ungag", FWUngag,      "Ungagging player by command from sourceban web site", FCVAR_PLUGIN);
    RegServerCmd("sc_fw_unmute",FWUnmute,     "Unmuting player by command from sourceban web site", FCVAR_PLUGIN);
    RegConsoleCmd("sm_comms",   CommandComms, "Shows current player communications status", FCVAR_PLUGIN);

    HookEvent("player_changename", Event_OnPlayerName, EventHookMode_Post);

    #if defined LOG_QUERIES
        BuildPath(Path_SM, logQuery, sizeof(logQuery), "logs/sourcecomms-q.log");
    #endif

    #if defined DEBUG
        PrintToServer("Sourcecomms plugin loading. Version %s", PLUGIN_VERSION);
    #endif

    // Catch config error
    if (!SQL_CheckConfig(DATABASE))
    {
        SetFailState("Database failure: could not find database conf  %s", DATABASE);
        return;
    }
    DB_Connect();
    InitializeBackupDB();

    ServerInfo();

}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "adminmenu"))
        hTopMenu = INVALID_HANDLE;
}

public OnMapStart()
{
    ReadConfig();
}

public OnMapEnd()
{
    // Clean up on map end just so we can start a fresh connection when we need it later.
    // Also it is necessary for using SQL_SetCharset
    if(g_hDatabase)
        CloseHandle(g_hDatabase);

    g_hDatabase = INVALID_HANDLE;
}

// CLIENT CONNECTION FUNCTIONS //

public OnClientDisconnect(client)
{
    if (g_hPlayerRecheck[client] != INVALID_HANDLE && CloseHandle(g_hPlayerRecheck[client]))
        g_hPlayerRecheck[client] = INVALID_HANDLE;

    CloseMuteExpireTimer(client);
    CloseGagExpireTimer(client);
}

public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
{
    g_bPlayerStatus[client] = false;
    return true;
}

public OnClientConnected(client)
{
    g_sName[client][0] = '\0';

    MarkClientAsUnMuted(client);
    MarkClientAsUnGagged(client);
}

public OnClientPostAdminCheck(client)
{
    decl String:clientAuth[64];
    GetClientAuthId(client, AuthId_Steam2, clientAuth, sizeof(clientAuth));
    GetClientName(client, g_sName[client], sizeof(g_sName[]));

    /* Do not check bots or check player with lan steamid. */
    if (clientAuth[0] == 'B' || clientAuth[9] == 'L' || !DB_Connect())
    {
        g_bPlayerStatus[client] = true;
        return;
    }

    if (client > 0 && IsClientInGame(client) && !IsFakeClient(client))
    {
        // if plugin was late loaded
        if (BaseComm_IsClientMuted(client))
        {
            MarkClientAsMuted(client);
        }
        if (BaseComm_IsClientGagged(client))
        {
            MarkClientAsGagged(client);
        }

        new String:sClAuthYZEscaped[sizeof(clientAuth) * 2 + 1];
        SQL_EscapeString(g_hDatabase, clientAuth[8], sClAuthYZEscaped, sizeof(sClAuthYZEscaped));

        decl String:Query[4096];
        FormatEx(Query, sizeof(Query),
           "SELECT      (c.ends - UNIX_TIMESTAMP()) AS remaining, \
                        c.length, c.type, c.created, c.reason, a.user, \
                        IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) AS immunity, \
                        c.aid, c.sid, a.authid \
            FROM        %s_comms     AS c \
            LEFT JOIN   %s_admins    AS a  ON a.aid = c.aid \
            LEFT JOIN   %s_srvgroups AS g  ON g.name = a.srv_group \
            WHERE       RemoveType IS NULL \
                          AND c.authid REGEXP '^STEAM_[0-9]:%s$' \
                          AND (length = '0' OR ends > UNIX_TIMESTAMP())",
            DatabasePrefix, DatabasePrefix, DatabasePrefix, sClAuthYZEscaped);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "OnClientPostAdminCheck for: %s. QUERY: %s", clientAuth, Query);
        #endif
        SQL_TQuery(g_hDatabase, Query_VerifyBlock, Query, GetClientUserId(client), DBPrio_High);
    }
}

// OTHER CLIENT CODE //

public Action:Event_OnPlayerName(Handle:event, const String:name[], bool:dontBroadcast)
{
    new client = GetClientOfUserId(GetEventInt(event, "userid"));
    if (client > 0 && IsClientInGame(client))
        GetEventString(event, "newname", g_sName[client], sizeof(g_sName[]));
}

public BaseComm_OnClientMute(client, bool:muteState)
{
    if (client > 0 && client <= MaxClients)
    {
        if (muteState)
        {
            if (g_MuteType[client] == bNot)
            {
                MarkClientAsMuted(client, _, _, _, _, _, "Muted through BaseComm natives");
                SavePunishment(_, client, TYPE_MUTE,  _, "Muted through BaseComm natives");
            }
        }
        else
        {
            if (g_MuteType[client] > bNot)
            {
                MarkClientAsUnMuted(client);
            }
        }
    }
}

public BaseComm_OnClientGag(client, bool:gagState)
{
    if (client > 0 && client <= MaxClients)
    {
        if (gagState)
        {
            if (g_GagType[client] == bNot)
            {
                MarkClientAsGagged(client, _, _, _, _, _, "Gagged through BaseComm natives");
                SavePunishment(_,  client, TYPE_GAG,   _, "Gagged through BaseComm natives");
            }
        }
        else
        {
            if (g_GagType[client] > bNot)
            {
                MarkClientAsUnGagged(client);
            }
        }
    }
}

// COMMAND CODE //

public Action:CommandComms(client, args)
{
    if (!client)
    {
        ReplyToCommand(client, "%s%t", PREFIX, "CommandComms_na");
        return Plugin_Continue;
    }

    if (g_MuteType[client] > bNot || g_GagType[client] > bNot)
        AdminMenu_ListTarget(client, client, 0);
    else
        ReplyToCommand(client,  "%s%t", PREFIX, "CommandComms_nb");

    return Plugin_Handled;
}

public Action:FWBlock(args)
{
    decl String:arg_string[256], String:sArg[3][64];
    GetCmdArgString(arg_string, sizeof(arg_string));

    decl type, length;
    if(ExplodeString(arg_string, " ", sArg, 3, 64) != 3 || !StringToIntEx(sArg[0], type) || type < 1 || type > 3 || !StringToIntEx(sArg[1], length))
    {
        LogError("Wrong usage of sc_fw_block");
        return Plugin_Stop;
    }

    LogMessage("Received block command from web: steam %s, type %d, length %d", sArg[2], type, length);

    decl String:clientAuth[64];
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[2], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for blocking from web", clientAuth);
                #endif

                switch (type) {
                    case TYPE_MUTE: setMute(i);
                    case TYPE_GAG: setGag(i);
                    case TYPE_SILENCE: { setMute(i); setGag(i); }
                }
                break;
            }
        }
    }

    return Plugin_Handled;
}

public Action:FWUngag(args)
{
    new String:arg_string[256], String:sArg[1][64];
    GetCmdArgString(arg_string, sizeof(arg_string));
    if(!ExplodeString(arg_string, " ", sArg, 1, 64))
    {
        LogError("Wrong usage of sc_fw_ungag");
        return Plugin_Stop;
    }

    LogMessage("Received ungag command from web: steam %s", sArg[0]);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            decl String:clientAuth[64];
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[0], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for ungagging from web", clientAuth);
                #endif

                if (g_GagType[i] > bNot)
                {
                    PerformUnGag(i);
                    PrintToChat(i, "%s%t", PREFIX, "FWUngag");
                    LogMessage("%s is ungagged from web", clientAuth);
                }
                else
                    LogError("Can't ungag %s from web, it isn't gagged", clientAuth);
                break;
            }
        }
    }
    return Plugin_Handled;
}

public Action:FWUnmute(args)
{
    new String:arg_string[256], String:sArg[1][64];
    GetCmdArgString(arg_string, sizeof(arg_string));
    if(!ExplodeString(arg_string, " ", sArg, 1, 64))
    {
        LogError("Wrong usage of sc_fw_ungag");
        return Plugin_Stop;
    }

    LogMessage("Received unmute command from web: steam %s", sArg[0]);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i))
        {
            decl String:clientAuth[64];
            GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
            if (strcmp(clientAuth, sArg[0], false) == 0)
            {
                #if defined DEBUG
                    PrintToServer("Catched %s for unmuting from web", clientAuth);
                #endif

                if (g_MuteType[i] > bNot)
                {
                    PerformUnMute(i);
                    PrintToChat(i, "%s%t", PREFIX, "FWUnmute");
                    LogMessage("%s is unmuted from web", clientAuth);
                }
                else
                    LogError("Can't unmute %s from web, it isn't muted", clientAuth);
                break;
            }
        }
    }
    return Plugin_Handled;
}

public Action:CommandCallback(client, const String:command[], args)
{
    if (client && !CheckCommandAccess(client, command, ADMFLAG_CHAT))
        return Plugin_Continue;

    new type;
    if (StrEqual(command, "sm_gag", false))
        type = TYPE_GAG;
    else if (StrEqual(command, "sm_mute", false))
        type = TYPE_MUTE;
    else if (StrEqual(command, "sm_ungag", false))
        type = TYPE_UNGAG;
    else if (StrEqual(command, "sm_unmute", false))
        type = TYPE_UNMUTE;
    else if (StrEqual(command, "sm_silence", false))
        type = TYPE_SILENCE;
    else if (StrEqual(command, "sm_unsilence", false))
        type = TYPE_UNSILENCE;
    else
        return Plugin_Stop;

    if (args < 1)
    {
        ReplyToCommand(client, "%sUsage: %s <#userid|name> %s", PREFIX, command, type <= TYPE_SILENCE ? "[time|0] [reason]" : "[reason]");
        if (type <= TYPE_SILENCE)
            ReplyToCommand(client, "%sUsage: %s <#userid|name> [reason]", PREFIX, command);
        return Plugin_Stop;
    }

    new String:sBuffer[256];
    GetCmdArgString(sBuffer, sizeof(sBuffer));

    if (type <= TYPE_SILENCE)
        CreateBlock(client, _, _, type, _, sBuffer);
    else
        ProcessUnBlock(client, _, type, _, sBuffer);

    return Plugin_Stop;
}

// MENU CODE //

public OnAdminMenuReady(Handle:topmenu)
{
    /* Block us from being called twice */
    if (topmenu == hTopMenu)
        return;

    /* Save the Handle */
    hTopMenu = topmenu;

    new TopMenuObject:MenuObject = AddToTopMenu(hTopMenu, "sourcecomm_cmds", TopMenuObject_Category, Handle_Commands, INVALID_TOPMENUOBJECT);
    if (MenuObject == INVALID_TOPMENUOBJECT)
        return;

    AddToTopMenu(hTopMenu, "sourcecomm_gag",       TopMenuObject_Item, Handle_MenuGag,       MenuObject, "sm_gag",       ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_ungag",     TopMenuObject_Item, Handle_MenuUnGag,     MenuObject, "sm_ungag",     ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_mute",      TopMenuObject_Item, Handle_MenuMute,      MenuObject, "sm_mute",      ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_unmute",    TopMenuObject_Item, Handle_MenuUnMute,    MenuObject, "sm_unmute",    ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_silence",   TopMenuObject_Item, Handle_MenuSilence,   MenuObject, "sm_silence",   ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_unsilence", TopMenuObject_Item, Handle_MenuUnSilence, MenuObject, "sm_unsilence", ADMFLAG_CHAT);
    AddToTopMenu(hTopMenu, "sourcecomm_list",      TopMenuObject_Item, Handle_MenuList,      MenuObject, "sm_commlist",  ADMFLAG_CHAT);
}

public Handle_Commands(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    switch(action)
    {
        case TopMenuAction_DisplayOption:
            Format(buffer, maxlength, "%T", "AdminMenu_Main", param1);
        case TopMenuAction_DisplayTitle:
            Format(buffer, maxlength, "%T", "AdminMenu_Select_Main", param1);
    }
}

public Handle_MenuGag(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Gag", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_GAG);
}

public Handle_MenuUnGag(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnGag", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNGAG);
}

public Handle_MenuMute(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Mute", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_MUTE);
}

public Handle_MenuUnMute(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnMute", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNMUTE);
}

public Handle_MenuSilence(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_Silence", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_SILENCE);
}

public Handle_MenuUnSilence(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_UnSilence", param1);
    else if (action == TopMenuAction_SelectOption)
        AdminMenu_Target(param1, TYPE_UNSILENCE);
}

public Handle_MenuList(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength)
{
    if (action == TopMenuAction_DisplayOption)
        Format(buffer, maxlength, "%T", "AdminMenu_List", param1);
    else if (action == TopMenuAction_SelectOption)
    {
        g_iPeskyPanels[param1][viewingList] = false;
        AdminMenu_List(param1, 0);
    }
}

AdminMenu_Target(client, type)
{
    decl String:Title[192], String:Option[32];
    switch(type)
    {
        case TYPE_GAG:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Gag", client);
        case TYPE_MUTE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Mute", client);
        case TYPE_SILENCE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Silence", client);
        case TYPE_UNGAG:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Ungag", client);
        case TYPE_UNMUTE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Unmute", client);
        case TYPE_UNSILENCE:
            Format(Title, sizeof(Title), "%T", "AdminMenu_Select_Unsilence", client);
    }

    new Handle:hMenu = CreateMenu(MenuHandler_MenuTarget);    // Common menu - players list. Almost full for blocking, and almost empty for unblocking
    SetMenuTitle(hMenu, Title);
    SetMenuExitBackButton(hMenu, true);

    new iClients;
    if (type <= 3)    // Mute, gag, silence
    {
        for (new i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i) && !IsFakeClient(i))
            {
                switch(type)
                {
                    case TYPE_MUTE:
                        if (g_MuteType[i] > bNot)
                            continue;
                    case TYPE_GAG:
                        if (g_GagType[i] > bNot)
                            continue;
                    case TYPE_SILENCE:
                        if (g_MuteType[i] > bNot || g_GagType[i] > bNot)
                            continue;
                }
                iClients++;
                strcopy(Title, sizeof(Title), g_sName[i]);
                AdminMenu_GetPunishPhrase(client, i, Title, sizeof(Title));
                Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
            }
        }
    }
    else        // UnMute, ungag, unsilence
    {
        for (new i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i) && !IsFakeClient(i))
            {
                switch(type)
                {
                    case TYPE_UNMUTE:
                    {
                        if (g_MuteType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                    case TYPE_UNGAG:
                    {
                        if (g_GagType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                    case TYPE_UNSILENCE:
                    {
                        if (g_MuteType[i] > bNot && g_GagType[i] > bNot)
                        {
                            iClients++;
                            strcopy(Title, sizeof(Title), g_sName[i]);
                            Format(Option, sizeof(Option), "%d %d", GetClientUserId(i), type);
                            AddMenuItem(hMenu, Option, Title, (CanUserTarget(client, i) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED));
                        }
                    }
                }
            }
        }
    }
    if (!iClients)
    {
        switch(type)
        {
            case TYPE_UNMUTE:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Mute_Empty", client);
            case TYPE_UNGAG:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Gag_Empty", client);
            case TYPE_UNSILENCE:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Silence_Empty", client);
            default:
                Format(Title, sizeof(Title), "%T", "AdminMenu_Option_Empty", client);
        }
        AddMenuItem(hMenu, "0", Title, ITEMDRAW_DISABLED);
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuTarget(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:Option[32], String:Temp[2][8];
            GetMenuItem(menu, param2, Option, sizeof(Option));
            ExplodeString(Option, " ", Temp, 2, 8);
            new target = GetClientOfUserId(StringToInt(Temp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(Temp[1]);
                if (type <= TYPE_SILENCE)
                    AdminMenu_Duration(param1, target, type);
                else
                    ProcessUnBlock(param1, target, type);
            }
        }
    }
}

AdminMenu_Duration(client, target, type)
{
    new Handle:hMenu = CreateMenu(MenuHandler_MenuDuration);
    decl String:sBuffer[192], String:sTemp[64];
    Format(sBuffer, sizeof(sBuffer), "%T", "AdminMenu_Title_Durations", client);
    SetMenuTitle(hMenu, sBuffer);
    SetMenuExitBackButton(hMenu, true);

    for (new i = 0; i <= iNumTimes; i++)
    {
        if (IsAllowedBlockLength(client, g_iTimeMinutes[i]))
        {
            Format(sTemp, sizeof(sTemp), "%d %d %d", GetClientUserId(target), type, i);    // TargetID TYPE_BLOCK index_of_Time
            AddMenuItem(hMenu, sTemp, g_sTimeDisplays[i]);
        }
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuDuration(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[32], String:sTemp[3][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 3, 8);
            // TargetID TYPE_BLOCK index_of_Time
            new target = GetClientOfUserId(StringToInt(sTemp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(sTemp[1]);
                new lengthIndex = StringToInt(sTemp[2]);

                if (iNumReasons) // we have reasons to show
                    AdminMenu_Reason(param1, target, type, lengthIndex);
                else
                    CreateBlock(param1, target, g_iTimeMinutes[lengthIndex], type);
            }
        }
    }
}

AdminMenu_Reason(client, target, type, lengthIndex)
{
    new Handle:hMenu = CreateMenu(MenuHandler_MenuReason);
    decl String:sBuffer[192], String:sTemp[64];
    Format(sBuffer, sizeof(sBuffer), "%T", "AdminMenu_Title_Reasons", client);
    SetMenuTitle(hMenu, sBuffer);
    SetMenuExitBackButton(hMenu, true);

    for (new i = 0; i <= iNumReasons; i++)
    {
        Format(sTemp, sizeof(sTemp), "%d %d %d %d", GetClientUserId(target), type, i, lengthIndex);    // TargetID TYPE_BLOCK ReasonIndex LenghtIndex
        AddMenuItem(hMenu, sTemp, g_sReasonDisplays[i]);
    }

    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuReason(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[64], String:sTemp[4][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 4, 8);
            // TargetID TYPE_BLOCK ReasonIndex LenghtIndex
            new target = GetClientOfUserId(StringToInt(sTemp[0]));

            if (Bool_ValidMenuTarget(param1, target))
            {
                new type = StringToInt(sTemp[1]);
                new reasonIndex = StringToInt(sTemp[2]);
                new lengthIndex = StringToInt(sTemp[3]);
                new length;
                if (lengthIndex >= 0 && lengthIndex <= iNumTimes)
                    length = g_iTimeMinutes[lengthIndex];
                else
                {
                    length = DefaultTime;
                    LogError("Wrong length index in menu - using default time");
                }

                CreateBlock(param1, target, length, type, g_sReasonKey[reasonIndex]);
            }
        }
    }
}

AdminMenu_List(client, index)
{
    decl String:sTitle[192], String:sOption[32];
    Format(sTitle, sizeof(sTitle), "%T", "AdminMenu_Select_List", client);
    new iClients, Handle:hMenu = CreateMenu(MenuHandler_MenuList);
    SetMenuTitle(hMenu, sTitle);
    if (!g_iPeskyPanels[client][viewingList])
        SetMenuExitBackButton(hMenu, true);

    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && !IsFakeClient(i) && (g_MuteType[i] > bNot || g_GagType[i] > bNot))
        {
            iClients++;
            strcopy(sTitle, sizeof(sTitle), g_sName[i]);
            AdminMenu_GetPunishPhrase(client, i, sTitle, sizeof(sTitle));
            Format(sOption, sizeof(sOption), "%d", GetClientUserId(i));
            AddMenuItem(hMenu, sOption, sTitle);
        }
    }

    if (!iClients)
    {
        Format(sTitle, sizeof(sTitle), "%T", "ListMenu_Option_Empty", client);
        AddMenuItem(hMenu, "0", sTitle, ITEMDRAW_DISABLED);
    }

    DisplayMenuAtItem(hMenu, client, index, MENU_TIME_FOREVER);
}

public MenuHandler_MenuList(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (!g_iPeskyPanels[param1][viewingList])
                if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                    DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
        }
        case MenuAction_Select:
        {
            decl String:sOption[32];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            new target = GetClientOfUserId(StringToInt(sOption));

            if (Bool_ValidMenuTarget(param1, target))
                AdminMenu_ListTarget(param1, target, GetMenuSelectionPosition());
            else
                AdminMenu_List(param1, GetMenuSelectionPosition());
        }
    }
}

AdminMenu_ListTarget(client, target, index, viewMute = 0, viewGag = 0)
{
    new userid = GetClientUserId(target), Handle:hMenu = CreateMenu(MenuHandler_MenuListTarget);
    decl String:sBuffer[192], String:sOption[32];
    SetMenuTitle(hMenu, g_sName[target]);
    SetMenuPagination(hMenu, MENU_NO_PAGINATION);
    SetMenuExitButton(hMenu, true);
    SetMenuExitBackButton(hMenu, false);

    if (g_MuteType[target] > bNot)
    {
        Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Mute", client);
        Format(sOption, sizeof(sOption), "0 %d %d %b %b", userid, index, viewMute, viewGag);
        AddMenuItem(hMenu, sOption, sBuffer);

        if (viewMute)
        {
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Admin", client, g_sMuteAdminName[target]);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            decl String:sMuteTemp[192], String:_sMuteTime[192];
            Format(sMuteTemp, sizeof(sMuteTemp), "%T", "ListMenu_Option_Duration", client);
            switch (g_MuteType[target])
            {
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Perm", client);
                case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Time", client, g_iMuteLength[target]);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Duration_Temp", client);
                default:    Format(sBuffer, sizeof(sBuffer), "error");
            }
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            FormatTime(_sMuteTime, sizeof(_sMuteTime), NULL_STRING, g_iMuteTime[target]);
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Issue", client, _sMuteTime);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            Format(sMuteTemp, sizeof(sMuteTemp), "%T", "ListMenu_Option_Expire", client);
            switch (g_MuteType[target])
            {
                case bTime:
                {
                    FormatTime(_sMuteTime, sizeof(_sMuteTime), NULL_STRING, (g_iMuteTime[target] + g_iMuteLength[target] * 60));
                    Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Time", client, _sMuteTime);
                }
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Perm", client);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sMuteTemp, "ListMenu_Option_Expire_Temp_Reconnect", client);
                default: Format(sBuffer, sizeof(sBuffer), "error");
            }
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            if (strlen(g_sMuteReason[target]) > 0)
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason", client);
                Format(sOption, sizeof(sOption), "1 %d %d %b %b", userid, index, viewMute, viewGag);
                AddMenuItem(hMenu, sOption, sBuffer);
            }
            else
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason_None", client);
                AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);
            }
        }
    }

    if (g_GagType[target] > bNot)
    {
        Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Gag", client);
        Format(sOption, sizeof(sOption), "2 %d %d %b %b", userid, index, viewMute, viewGag);
        AddMenuItem(hMenu, sOption, sBuffer);

        if (viewGag)
        {
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Admin", client, g_sGagAdminName[target]);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            decl String:sGagTemp[192], String:_sGagTime[192];
            Format(sGagTemp, sizeof(sGagTemp), "%T", "ListMenu_Option_Duration", client);

            switch (g_GagType[target])
            {
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Perm", client);
                case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Time", client, g_iGagLength[target]);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Duration_Temp", client);
                default:    Format(sBuffer, sizeof(sBuffer), "error");
            }

            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            FormatTime(_sGagTime, sizeof(_sGagTime), NULL_STRING, g_iGagTime[target]);
            Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Issue", client, _sGagTime);
            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            Format(sGagTemp, sizeof(sGagTemp), "%T", "ListMenu_Option_Expire", client);

            switch (g_GagType[target])
            {
                case bTime:
                {
                    FormatTime(_sGagTime, sizeof(_sGagTime), NULL_STRING, (g_iGagTime[target] + g_iGagLength[target] * 60));
                    Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Time", client, _sGagTime);
                }
                case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Perm", client);
                case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sGagTemp, "ListMenu_Option_Expire_Temp_Reconnect", client);
                default: Format(sBuffer, sizeof(sBuffer), "error");
            }

            AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);

            if (strlen(g_sGagReason[target]) > 0)
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason", client);
                Format(sOption, sizeof(sOption), "3 %d %d %b %b", userid, index, viewMute, viewGag);
                AddMenuItem(hMenu, sOption, sBuffer);
            }
            else
            {
                Format(sBuffer, sizeof(sBuffer), "%T", "ListMenu_Option_Reason_None", client);
                AddMenuItem(hMenu, "", sBuffer, ITEMDRAW_DISABLED);
            }
        }
    }

    g_iPeskyPanels[client][curIndex] = index;
    g_iPeskyPanels[client][curTarget] = target;
    g_iPeskyPanels[client][viewingGag] = viewGag;
    g_iPeskyPanels[client][viewingMute] = viewMute;
    DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
}

public MenuHandler_MenuListTarget(Handle:menu, MenuAction:action, param1, param2)
{
    switch(action)
    {
        case MenuAction_End:
            CloseHandle(menu);
        case MenuAction_Cancel:
        {
            if (param2 == MenuCancel_ExitBack)
                AdminMenu_List(param1, g_iPeskyPanels[param1][curIndex]);
        }
        case MenuAction_Select:
        {
            decl String:sOption[64], String:sTemp[5][8];
            GetMenuItem(menu, param2, sOption, sizeof(sOption));
            ExplodeString(sOption, " ", sTemp, 5, 8);

            new target = GetClientOfUserId(StringToInt(sTemp[1]));
            if (param1 == target || Bool_ValidMenuTarget(param1, target))
            {
                switch(StringToInt(sTemp[0]))
                {
                    case 0:
                        AdminMenu_ListTarget(param1, target, StringToInt(sTemp[2]), !(StringToInt(sTemp[3])), 0);
                    case 1, 3:
                        AdminMenu_ListTargetReason(param1, target, g_iPeskyPanels[param1][viewingMute], g_iPeskyPanels[param1][viewingGag]);
                    case 2:
                        AdminMenu_ListTarget(param1, target, StringToInt(sTemp[2]), 0, !(StringToInt(sTemp[4])));
                }
            }
            else
                AdminMenu_List(param1, StringToInt(sTemp[2]));

        }
    }
}

AdminMenu_ListTargetReason(client, target, showMute, showGag)
{
    decl String:sTemp[192], String:sBuffer[192];
    new Handle:hPanel = CreatePanel();
    SetPanelTitle(hPanel, g_sName[target]);
    DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);

    if (showMute)
    {
        Format(sTemp, sizeof(sTemp), "%T", "ReasonPanel_Punishment_Mute", client);
        switch (g_MuteType[target])
        {
            case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Perm", client);
            case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Time", client, g_iMuteLength[target]);
            case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Temp", client);
            default:    Format(sBuffer, sizeof(sBuffer), "error");
        }
        DrawPanelText(hPanel, sBuffer);

        Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Reason", client, g_sMuteReason[target]);
        DrawPanelText(hPanel, sBuffer);
    }
    else if (showGag)
    {
        Format(sTemp, sizeof(sTemp), "%T", "ReasonPanel_Punishment_Gag", client);
        switch (g_GagType[target])
        {
            case bPerm: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Perm", client);
            case bTime: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Time", client, g_iGagLength[target]);
            case bSess: Format(sBuffer, sizeof(sBuffer), "%s%T", sTemp, "ReasonPanel_Temp", client);
            default:    Format(sBuffer, sizeof(sBuffer), "error");
        }
        DrawPanelText(hPanel, sBuffer);

        Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Reason", client, g_sGagReason[target]);
        DrawPanelText(hPanel, sBuffer);
    }

    DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);
    SetPanelCurrentKey(hPanel, 10);
    Format(sBuffer, sizeof(sBuffer), "%T", "ReasonPanel_Back", client);
    DrawPanelItem(hPanel, sBuffer);
    SendPanelToClient(hPanel, client, PanelHandler_ListTargetReason, MENU_TIME_FOREVER);
    CloseHandle(hPanel);
}

public PanelHandler_ListTargetReason(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        AdminMenu_ListTarget(param1,g_iPeskyPanels[param1][curTarget],
                                    g_iPeskyPanels[param1][curIndex],
                                    g_iPeskyPanels[param1][viewingMute],
                                    g_iPeskyPanels[param1][viewingGag]);
    }
}

// SQL CALLBACKS //

public GotDatabase(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    #if defined DEBUG
        PrintToServer("GotDatabase(data: %d, lock: %d, g_h: %d, hndl: %d)", data, g_iConnectLock, g_hDatabase, hndl);
    #endif

    // If this happens to be an old connection request, ignore it.
    if(data != g_iConnectLock || g_hDatabase)
    {
        if(hndl)
            CloseHandle(hndl);
        return;
    }

    g_iConnectLock   = 0;
    g_DatabaseState  = DatabaseState_Connected;
    g_hDatabase      = hndl;

    // See if the connection is valid.  If not, don't un-mark the caches
    // as needing rebuilding, in case the next connection request works.
    if(!g_hDatabase)
    {
        LogError("Connecting to database failed: %s", error);
        return;
    }

    // Set character set to UTF-8 in the database
    if (GetFeatureStatus(FeatureType_Native, "SQL_SetCharset") == FeatureStatus_Available)
    {
        SQL_SetCharset(g_hDatabase, "utf8");
    }
    else
    {
        decl String:query[128];
        FormatEx(query, sizeof(query), "SET NAMES 'UTF8'");
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Set encoding. QUERY: %s", query);
        #endif
        SQL_TQuery(g_hDatabase, Query_ErrorCheck, query);
    }

    // Process queue
    SQL_TQuery(SQLiteDB, Query_ProcessQueue,
       "SELECT  id, steam_id, time, start_time, reason, name, admin_id, admin_ip, type \
        FROM    queue2");

    // Force recheck players
    ForcePlayersRecheck();
}

public Query_AddBlockInsert(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (DB_Conn_Lost(hndl) || error[0])
    {
        LogError("Query_AddBlockInsert failed: %s", error);

        ResetPack(data);
        new length = ReadPackCell(data);
        new type = ReadPackCell(data);
        new String:reason[256], String:name[MAX_NAME_LENGTH], String:auth[64], String:adminAuth[32], String:adminIp[20];
        ReadPackString(data, name, sizeof(name));
        ReadPackString(data, auth, sizeof(auth));
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, adminAuth, sizeof(adminAuth));
        ReadPackString(data, adminIp, sizeof(adminIp));

        InsertTempBlock(length, type, name, auth, reason, adminAuth, adminIp);
    }
    CloseHandle(data);
}

public Query_UnBlockSelect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    decl String:adminAuth[30], String:targetAuth[30], String:reason[256];

    ResetPack(data);
    new adminUserID  = ReadPackCell(data);
    new targetUserID = ReadPackCell(data);
    new type         = ReadPackCell(data);    // not in use unless DEBUG
    ReadPackString(data, adminAuth,  sizeof(adminAuth));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    ReadPackString(data, reason,      sizeof(reason));

    new admin  = GetClientOfUserId(adminUserID);
    new target = GetClientOfUserId(targetUserID);

    #if defined DEBUG
        PrintToServer("Query_UnBlockSelect(adminUID: %d/%d, targetUID: %d/%d, type: %d, adminAuth: %s, targetAuth: %s, reason: %s)",
            adminUserID, admin, targetUserID, target, type, adminAuth, targetAuth, reason);
    #endif

    new String:targetName[MAX_NAME_LENGTH];
    strcopy(targetName, MAX_NAME_LENGTH, target && IsClientInGame(target) ? g_sName[target] : targetAuth);        //FIXME

    new bool:hasErrors = false;
    // If error is not an empty string the query failed
    if (DB_Conn_Lost(hndl) || error[0] != '\0')
    {
        LogError("Query_UnBlockSelect failed: %s", error);
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%T", PREFIX, "Unblock Select Failed", admin, targetAuth);
            PrintToConsole(admin, "%s%T", PREFIX, "Unblock Select Failed", admin, targetAuth);
        }
        else
        {
            PrintToServer("%s%T", PREFIX, "Unblock Select Failed", LANG_SERVER, targetAuth);
        }
        hasErrors = true;
    }

    // If there was no results then a ban does not exist for that id
    if (!DB_Conn_Lost(hndl) && !SQL_GetRowCount(hndl))
    {
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "No blocks found", targetAuth);
            PrintToConsole(admin, "%s%t", PREFIX, "No blocks found", targetAuth);
        }
        else
        {
            PrintToServer("%s%T", PREFIX, "No blocks found", LANG_SERVER, targetAuth);
        }
        hasErrors = true;
    }

    if (hasErrors)
    {
        #if defined DEBUG
            PrintToServer("Calling TempUnBlock from Query_UnBlockSelect");
        #endif

        TempUnBlock(data);    // Datapack closed inside.
        return;
    }
    else
    {
        new bool:b_success = false;
        // Get the values from the founded blocks.
        while(SQL_MoreRows(hndl))
        {
            // Oh noes! What happened?!
            if (!SQL_FetchRow(hndl))
                continue;

            new bid       = SQL_FetchInt(hndl, 0);
            new iAID      = SQL_FetchInt(hndl, 1);
            new cAID      = SQL_FetchInt(hndl, 2);
            new cImmunity = SQL_FetchInt(hndl, 3);
            new cType     = SQL_FetchInt(hndl, 4);

            #if defined DEBUG
                PrintToServer("Fetched from DB: bid %d, iAID: %d, cAID: %d, cImmunity: %d, cType: %d", bid, iAID, cAID, cImmunity, cType);
                // WHO WE ARE?
                PrintToServer("WHO WE ARE CHECKING!");
                if (iAID == cAID)
                    PrintToServer("we are block author");
                if (!admin)
                    PrintToServer("we are console (possibly)");
                if (AdmHasFlag(admin))
                    PrintToServer("we have special flag");
                if (GetAdmImmunity(admin) > cImmunity)
                    PrintToServer("we have %d immunity and block has %d. we cool", GetAdmImmunity(admin), cImmunity);
            #endif

            // Checking - has we access to unblock?
            if (iAID == cAID || (!admin && StrEqual(adminAuth, "STEAM_ID_SERVER")) || AdmHasFlag(admin) || (DisUBImCheck == 0 && (GetAdmImmunity(admin) > cImmunity)))
            {
                // Ok! we have rights to unblock
                b_success = true;
                // UnMute/UnGag, Show & log activity
                if (target && IsClientInGame(target))
                {
                    switch(cType)
                    {
                        case TYPE_MUTE:
                        {
                            PerformUnMute(target);
                            LogAction(admin, target, "\"%L\" unmuted \"%L\" (reason \"%s\")", admin, target, reason);
                        }
                        //-------------------------------------------------------------------------------------------------
                        case TYPE_GAG:
                        {
                            PerformUnGag(target);
                            LogAction(admin, target, "\"%L\" ungagged \"%L\" (reason \"%s\")", admin, target, reason);
                        }
                    }
                }

                new Handle:dataPack = CreateDataPack();
                WritePackCell(dataPack, adminUserID);
                WritePackCell(dataPack, cType);
                WritePackString(dataPack, g_sName[target]);
                WritePackString(dataPack, targetAuth);

                new String:unbanReason[sizeof(reason) * 2 + 1];
                SQL_EscapeString(g_hDatabase, reason, unbanReason, sizeof(unbanReason));

                decl String:query[2048];
                Format(query, sizeof(query),
                   "UPDATE  %s_comms \
                    SET     RemovedBy = %d, \
                            RemoveType = 'U', \
                            RemovedOn = UNIX_TIMESTAMP(), \
                            ureason = '%s' \
                    WHERE   bid = %d",
                    DatabasePrefix, iAID, unbanReason, bid);
                #if defined LOG_QUERIES
                    LogToFile(logQuery, "Query_UnBlockSelect. QUERY: %s", query);
                #endif
                SQL_TQuery(g_hDatabase, Query_UnBlockUpdate, query, dataPack);
            }
            else
            {
                // sorry, we don't have permission to unblock!
                #if defined DEBUG
                    PrintToServer("No permissions to unblock in Query_UnBlockSelect");
                #endif
                switch(cType)
                {
                    case TYPE_MUTE:
                    {
                        if (admin && IsClientInGame(admin))
                        {
                            PrintToChat(admin, "%s%t", PREFIX, "No permission unmute", targetName);
                            PrintToConsole(admin, "%s%t", PREFIX, "No permission unmute", targetName);
                        }
                        LogAction(admin, target, "\"%L\" tried (and didn't have permission) to unmute %s (reason \"%s\")", admin, targetAuth, reason);
                    }
                    //-------------------------------------------------------------------------------------------------
                    case TYPE_GAG:
                    {
                        if (admin && IsClientInGame(admin))
                        {
                            PrintToChat(admin, "%s%t", PREFIX, "No permission ungag", targetName);
                            PrintToConsole(admin, "%s%t", PREFIX, "No permission ungag", targetName);
                        }
                        LogAction(admin, target, "\"%L\" tried (and didn't have permission) to ungag %s (reason \"%s\")", admin, targetAuth, reason);
                    }
                }
            }
        }

        if (b_success && target && IsClientInGame(target))
        {
            #if defined DEBUG
                PrintToServer("Showing activity to server in Query_UnBlockSelect");
            #endif
            ShowActivityToServer(admin, type, _, _, g_sName[target], _);

            if (type == TYPE_UNSILENCE)
            {
                // check result for possible combination with temp and time punishments (temp was skipped in code above)
                SetPackPosition(data, 16);
                if (g_MuteType[target] > bNot)
                {
                    WritePackCell(data, TYPE_UNMUTE);
                    TempUnBlock(data);
                    data = INVALID_HANDLE;
                }
                else if (g_GagType[target] > bNot)
                {
                    WritePackCell(data, TYPE_UNGAG);
                    TempUnBlock(data);
                    data = INVALID_HANDLE;
                }
            }
        }
    }
    if (data != INVALID_HANDLE)
        CloseHandle(data);
}

public Query_UnBlockUpdate(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    new admin, type;
    new String:targetName[MAX_NAME_LENGTH], String:targetAuth[30];

    ResetPack(data);
    admin = GetClientOfUserId(ReadPackCell(data));
    type  =                   ReadPackCell(data);
    ReadPackString(data, targetName, sizeof(targetName));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    CloseHandle(data);

    if (DB_Conn_Lost(hndl) || error[0] != '\0')
    {
        LogError("Query_UnBlockUpdate failed: %s", error);
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "Unblock insert failed");
            PrintToConsole(admin, "%s%t", PREFIX, "Unblock insert failed");
        }
        return;
    }

    switch(type)
    {
        case TYPE_MUTE:
        {
            LogAction(admin, -1, "\"%L\" removed mute for %s from DB", admin, targetAuth);
            if (admin && IsClientInGame(admin))
            {
                PrintToChat(admin, "%s%t", PREFIX, "successfully unmuted", targetName);
                PrintToConsole(admin, "%s%t", PREFIX, "successfully unmuted", targetName);
            }
            else
            {
                PrintToServer("%s%T", PREFIX, "successfully unmuted", LANG_SERVER, targetName);
            }
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_GAG:
        {
            LogAction(admin, -1, "\"%L\" removed gag for %s from DB", admin, targetAuth);
            if (admin && IsClientInGame(admin)){
                PrintToChat(admin, "%s%t", PREFIX, "successfully ungagged", targetName);
                PrintToConsole(admin, "%s%t", PREFIX, "successfully ungagged", targetName);
            }
            else
            {
                PrintToServer("%s%T", PREFIX, "successfully ungagged", LANG_SERVER, targetName);
            }
        }
    }
}

// ProcessQueueCallback is called as the result of selecting all the rows from the queue table
public Query_ProcessQueue(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (hndl == INVALID_HANDLE || error[0])
    {
        LogError("Query_ProcessQueue failed: %s", error);
        return;
    }

    decl String:auth[64];
    new String:name[MAX_NAME_LENGTH];
    decl String:reason[256];
    decl String:adminAuth[64], String:adminIp[20];
    decl String:query[4096];

    while(SQL_MoreRows(hndl))
    {
        // Oh noes! What happened?!
        if (!SQL_FetchRow(hndl))
            continue;

        new String:sAuthEscaped[sizeof(auth) * 2 + 1];
        new String:banName[MAX_NAME_LENGTH * 2  + 1];
        new String:banReason[sizeof(reason) * 2 + 1];
        new String:sAdmAuthEscaped[sizeof(adminAuth) * 2 + 1];
        new String:sAdmAuthYZEscaped[sizeof(adminAuth) * 2 + 1];

        // if we get to here then there are rows in the queue pending processing
        //steam_id TEXT, time INTEGER, start_time INTEGER, reason TEXT, name TEXT, admin_id TEXT, admin_ip TEXT, type INTEGER
        new id =        SQL_FetchInt(hndl, 0);
        SQL_FetchString(hndl, 1, auth, sizeof(auth));
        new time =      SQL_FetchInt(hndl, 2);
        new startTime = SQL_FetchInt(hndl, 3);
        SQL_FetchString(hndl, 4, reason, sizeof(reason));
        SQL_FetchString(hndl, 5, name, sizeof(name));
        SQL_FetchString(hndl, 6, adminAuth, sizeof(adminAuth));
        SQL_FetchString(hndl, 7, adminIp, sizeof(adminIp));
        new type =      SQL_FetchInt(hndl, 8);

        if (DB_Connect()) {
            SQL_EscapeString(g_hDatabase, auth,         sAuthEscaped,      sizeof(sAuthEscaped));
            SQL_EscapeString(g_hDatabase, name,         banName,           sizeof(banName));
            SQL_EscapeString(g_hDatabase, reason,       banReason,         sizeof(banReason));
            SQL_EscapeString(g_hDatabase, adminAuth,    sAdmAuthEscaped,   sizeof(sAdmAuthEscaped));
            SQL_EscapeString(g_hDatabase, adminAuth[8], sAdmAuthYZEscaped, sizeof(sAdmAuthYZEscaped));
        }
        else
            continue;
        // all blocks should be entered into db!

        FormatEx(query, sizeof(query),
               "INSERT INTO     %s_comms (authid, name, created, ends, length, reason, aid, adminIp, sid, type) \
                VALUES         ('%s', '%s', %d, %d, %d, '%s', \
                                IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0'), \
                                '%s', %d, %d)",
                DatabasePrefix, sAuthEscaped, banName, startTime, (startTime + (time*60)), (time*60), banReason, DatabasePrefix, sAdmAuthEscaped, sAdmAuthYZEscaped, adminIp, serverID, type);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Query_ProcessQueue. QUERY: %s", query);
        #endif
        SQL_TQuery(g_hDatabase, Query_AddBlockFromQueue, query, id);
    }
}

public Query_AddBlockFromQueue(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    decl String:query[512];
    if (error[0] == '\0')
    {
        // The insert was successful so delete the record from the queue
        FormatEx(query, sizeof(query),
           "DELETE FROM queue2 \
            WHERE       id = %d",
            data);
        #if defined LOG_QUERIES
            LogToFile(logQuery, "Query_AddBlockFromQueue. QUERY: %s", query);
        #endif
        SQL_TQuery(SQLiteDB, Query_ErrorCheck, query);
    }
}

public Query_ErrorCheck(Handle:owner, Handle:hndl, const String:error[], any:data)
{
    if (DB_Conn_Lost(hndl) || error[0])
        LogError("%T (%s)", "Failed to query database", LANG_SERVER, error);
}

public Query_VerifyBlock(Handle:owner, Handle:hndl, const String:error[], any:userid)
{
    decl String:clientAuth[64];
    new client = GetClientOfUserId(userid);

    #if defined DEBUG
        PrintToServer("Query_VerifyBlock(userid: %d, client: %d)", userid, client);
    #endif

    if (!client)
        return;

    /* Failure happen. Do retry with delay */
    if (DB_Conn_Lost(hndl))
    {
        LogError("Query_VerifyBlock failed: %s", error);
        if (g_hPlayerRecheck[client] == INVALID_HANDLE)
            g_hPlayerRecheck[client] = CreateTimer(RetryTime, ClientRecheck, userid);
        return;
    }

    GetClientAuthId(client, AuthId_Steam2, clientAuth, sizeof(clientAuth));

    //SELECT (c.ends - UNIX_TIMESTAMP()) as remaining, c.length, c.type, c.created, c.reason, a.user,
    //IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, c.aid, c.sid, c.authid
    //FROM %s_comms c LEFT JOIN %s_admins a ON a.aid=c.aid LEFT JOIN %s_srvgroups g ON g.name = a.srv_group
    //WHERE c.authid REGEXP '^STEAM_[0-9]:%s$' AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL",
    if (SQL_GetRowCount(hndl) > 0)
    {
        while(SQL_FetchRow(hndl))
        {
            if (NotApplyToThisServer(SQL_FetchInt(hndl, 8)))
                continue;

            new String:sReason[256], String:sAdmName[MAX_NAME_LENGTH], String:sAdmAuth[64];
            new remaining_time = SQL_FetchInt(hndl, 0);
            new length =         SQL_FetchInt(hndl, 1);
            new type =           SQL_FetchInt(hndl, 2);
            new time =           SQL_FetchInt(hndl, 3);
            SQL_FetchString(hndl, 4, sReason, sizeof(sReason));
            SQL_FetchString(hndl, 5, sAdmName, sizeof(sAdmName));
            new immunity =       SQL_FetchInt(hndl, 6);
            new aid =            SQL_FetchInt(hndl, 7);
            SQL_FetchString(hndl, 9, sAdmAuth, sizeof(sAdmAuth));

            // Block from CONSOLE (aid=0) and we have `console immunity` value in config
            if (!aid && ConsoleImmunity > immunity)
                immunity = ConsoleImmunity;

            #if defined DEBUG
                PrintToServer("Fetched from DB: remaining %d, length %d, type %d", remaining_time, length, type);
            #endif

            switch(type)
            {
                case TYPE_MUTE:
                {
                    if (g_MuteType[client] < bTime)
                    {
                        PerformMute(client, time, length / 60, sAdmName, sAdmAuth, immunity, sReason, remaining_time);
                        PrintToChat(client, "%s%t", PREFIX, "Muted on connect");
                    }
                }
                case TYPE_GAG:
                {
                    if (g_GagType[client] < bTime)
                    {
                        PerformGag(client, time, length / 60, sAdmName, sAdmAuth, immunity, sReason, remaining_time);
                        PrintToChat(client, "%s%t", PREFIX, "Gagged on connect");
                    }
                }
            }
        }
    }

    g_bPlayerStatus[client] = true;
}

// TIMER CALL BACKS //

public Action:ClientRecheck(Handle:timer, any:userid)
{
    #if defined DEBUG
        PrintToServer("ClientRecheck(userid: %d)", userid);
    #endif

    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    if (IsClientConnected(client))
        OnClientPostAdminCheck(client);

    g_hPlayerRecheck[client] =  INVALID_HANDLE;
}

public Action:Timer_MuteExpire(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    #if defined DEBUG
        decl String:clientAuth[64];
        GetClientAuthId(client, AuthId_Steam2, clientAuth,sizeof(clientAuth));
        PrintToServer("Mute expired for %s", clientAuth);
    #endif

    PrintToChat(client, "%s%t", PREFIX, "Mute expired");

    g_hMuteExpireTimer[client] = INVALID_HANDLE;
    MarkClientAsUnMuted(client);
    if (IsClientInGame(client))
        BaseComm_SetClientMute(client, false);
}

public Action:Timer_GagExpire(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (!client)
        return;

    #if defined DEBUG
        decl String:clientAuth[64];
        GetClientAuthId(client, AuthId_Steam2, clientAuth,sizeof(clientAuth));
        PrintToServer("Gag expired for %s", clientAuth);
    #endif

    PrintToChat(client, "%s%t", PREFIX, "Gag expired");

    g_hGagExpireTimer[client] = INVALID_HANDLE;
    MarkClientAsUnGagged(client);
    if (IsClientInGame(client))
        BaseComm_SetClientGag(client, false);
}

public Action:Timer_StopWait(Handle:timer, any:data)
{
    g_DatabaseState = DatabaseState_None;
    DB_Connect();
}

// PARSER //

static InitializeConfigParser()
{
    if (ConfigParser == INVALID_HANDLE)
    {
        ConfigParser = SMC_CreateParser();
        SMC_SetReaders(ConfigParser, ReadConfig_NewSection, ReadConfig_KeyValue, ReadConfig_EndSection);
    }
}

static InternalReadConfig(const String:path[])
{
    ConfigState = ConfigStateNone;

    new SMCError:err = SMC_ParseFile(ConfigParser, path);

    if (err != SMCError_Okay)
    {
        decl String:buffer[64];
        if (SMC_GetErrorString(err, buffer, sizeof(buffer)))
        {
            PrintToServer(buffer);
        }
        else
        {
            PrintToServer("Fatal parse error");
        }
    }
}

public SMCResult:ReadConfig_NewSection(Handle:smc, const String:name[], bool:opt_quotes)
{
    if (name[0])
    {
        if (strcmp("Config", name, false) == 0)
        {
            ConfigState = ConfigStateConfig;
        }
        else if (strcmp("CommsReasons", name, false) == 0)
        {
            ConfigState = ConfigStateReasons;
        }
        else if (strcmp("CommsTimes", name, false) == 0)
        {
            ConfigState = ConfigStateTimes;
        }
        else if (strcmp("ServersWhiteList", name, false) == 0)
        {
            ConfigState = ConfigStateServers;
        }
    }
    return SMCParse_Continue;
}

public SMCResult:ReadConfig_KeyValue(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
{
    if (!key[0])
        return SMCParse_Continue;

    switch(ConfigState)
    {
        case ConfigStateConfig:
        {
            if (strcmp("DatabasePrefix", key, false) == 0)
            {
                strcopy(DatabasePrefix, sizeof(DatabasePrefix), value);

                if (DatabasePrefix[0] == '\0')
                {
                    DatabasePrefix = "sb";
                }
            }
            else if (strcmp("RetryTime", key, false) == 0)
            {
                RetryTime    = StringToFloat(value);
                if (RetryTime < 15.0)
                {
                    RetryTime = 15.0;
                }
                else if (RetryTime > 60.0)
                {
                    RetryTime = 60.0;
                }
            }
            else if (strcmp("ServerID", key, false) == 0)
            {
                if (!StringToIntEx(value, serverID) || serverID < 1)
                {
                    serverID = 0;
                }
            }
            else if (strcmp("DefaultTime", key, false) == 0)
            {
                DefaultTime    = StringToInt(value);
                if (DefaultTime < 0)
                {
                    DefaultTime = -1;
                }
                if (DefaultTime == 0)
                {
                    DefaultTime = 30;
                }
            }
            else if (strcmp("DisableUnblockImmunityCheck", key, false) == 0)
            {
                DisUBImCheck = StringToInt(value);
                if (DisUBImCheck != 1)
                {
                    DisUBImCheck = 0;
                }
            }
            else if (strcmp("ConsoleImmunity", key, false) == 0)
            {
                ConsoleImmunity = StringToInt(value);
                if (ConsoleImmunity < 0 || ConsoleImmunity > 100)
                {
                    ConsoleImmunity = 0;
                }
            }
            else if (strcmp("MaxLength", key, false) == 0)
            {
                ConfigMaxLength = StringToInt(value);
            }
            else if (strcmp("OnlyWhiteListServers", key, false) == 0)
            {
                ConfigWhiteListOnly = StringToInt(value);
                if (ConfigWhiteListOnly != 1)
                {
                    ConfigWhiteListOnly = 0;
                }
            }
        }
        case ConfigStateReasons:
        {
            Format(g_sReasonKey[iNumReasons], REASON_SIZE, "%s", key);
            Format(g_sReasonDisplays[iNumReasons], DISPLAY_SIZE, "%s", value);
            #if defined DEBUG
                PrintToServer("Loaded reason. index %d, key \"%s\", display_text \"%s\"", iNumReasons, g_sReasonKey[iNumReasons], g_sReasonDisplays[iNumReasons]);
            #endif
            iNumReasons++;
        }
        case ConfigStateTimes:
        {
            Format(g_sTimeDisplays[iNumTimes], DISPLAY_SIZE, "%s", value);
            g_iTimeMinutes[iNumTimes] = StringToInt(key);
            #if defined DEBUG
                PrintToServer("Loaded time. index %d, time %d minutes, display_text \"%s\"", iNumTimes, g_iTimeMinutes[iNumTimes] , g_sTimeDisplays[iNumTimes]);
            #endif
            iNumTimes++;
        }
        case ConfigStateServers:
        {
            if (strcmp("id", key, false) == 0)
            {
                new srvID = StringToInt(value);
                if (srvID >= 0)
                {
                    PushArrayCell(g_hServersWhiteList, srvID);
                    #if defined DEBUG
                        PrintToServer("Loaded white list server id %d", srvID);
                    #endif
                }
            }
        }
    }
    return SMCParse_Continue;
}

public SMCResult:ReadConfig_EndSection(Handle:smc)
{
    return SMCParse_Continue;
}

// STOCK FUNCTIONS //

stock setGag(client)
{
    if (g_GagType[client] == bNot)
    {
        PerformGag(client, _, length / 60, _, _, _, _);
        PrintToChat(client, "%s%t", PREFIX, "Gagged on connect");
        LogMessage("%s is gagged from web", clientAuth);
    }
}

stock setMute(client)
{
    if (g_MuteType[client] == bNot)
    {
        PerformMute(client, _, length / 60, _, _, _, _);
        PrintToChat(client, "%s%t", PREFIX, "Muted on connect");
        LogMessage("%s is muted from web", clientAuth);
    }
}

stock bool:DB_Connect()
{
    #if defined DEBUG
        PrintToServer("DB_Connect(handle %d, state %d, lock %d)", g_hDatabase, g_DatabaseState, g_iConnectLock);
    #endif

    if (g_hDatabase)
    {
        return true;
    }

    if (g_DatabaseState == DatabaseState_Wait) // 100500 connections in a minute is bad idea..
    {
        return false;
    }

    if(g_DatabaseState != DatabaseState_Connecting)
    {
        g_DatabaseState = DatabaseState_Connecting;
        g_iConnectLock  = ++g_iSequence;
        // Connect using the "sourcebans" section, or the "default" section if "sourcebans" does not exist
        SQL_TConnect(GotDatabase, DATABASE, g_iConnectLock);
    }

    return false;
}

stock bool:DB_Conn_Lost(Handle:hndl)
{
    if (hndl == INVALID_HANDLE)
    {
        if (g_hDatabase != INVALID_HANDLE)
        {
            LogError("Lost connection to DB. Reconnect after delay.");
            CloseHandle(g_hDatabase);
            g_hDatabase = INVALID_HANDLE;
        }
        if (g_DatabaseState != DatabaseState_Wait)
        {
            g_DatabaseState = DatabaseState_Wait;
            CreateTimer(RetryTime, Timer_StopWait, _, TIMER_FLAG_NO_MAPCHANGE);
        }
        return true;
    }
    else
    {
        return false;
    }
}

stock InitializeBackupDB()
{
    decl String:error[255];
    SQLiteDB = SQLite_UseDatabase("sourcecomms-queue", error, sizeof(error));
    if (SQLiteDB == INVALID_HANDLE)
    {
        SetFailState(error);
    }

    SQL_TQuery(SQLiteDB, Query_ErrorCheck,
       "CREATE TABLE IF NOT EXISTS queue2 ( \
            id INTEGER PRIMARY KEY, \
            steam_id TEXT, \
            time INTEGER, \
            start_time INTEGER, \
            reason TEXT, \
            name TEXT, \
            admin_id TEXT, \
            admin_ip TEXT, \
            type INTEGER)");
}

stock CreateBlock(client, targetId = 0, length = -1, type, const String:sReason[] = "", const String:sArgs[] = "")
{
    #if defined DEBUG
        PrintToServer("CreateBlock(admin: %d, target: %d, length: %d, type: %d, reason: %s, args: %s)", client, targetId, length, type, sReason, sArgs);
    #endif

    decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH], String:reason[256];
    new bool:skipped = false;

    // checking args
    if (targetId)
    {
        target_list[0] = targetId;
        target_count = 1;
        tn_is_ml = false;
        strcopy(target_name, sizeof(target_name), g_sName[targetId]);
        strcopy(reason,      sizeof(reason),      sReason);
    }
    else if (strlen(sArgs))
    {
        new String:sArg[3][192];

        if (ExplodeString(sArgs, "\"", sArg, 3, 192, true) == 3 && strlen(sArg[0]) == 0)    // exploding by quotes
        {
            new String:sTempArg[2][192];
            TrimString(sArg[2]);
            sArg[0] = sArg[1];        // target name
            ExplodeString(sArg[2], " ", sTempArg, 2, 192, true); // get length and reason
            sArg[1] = sTempArg[0];    // lenght
            sArg[2] = sTempArg[1];    // reason
        }
        else
        {
            ExplodeString(sArgs, " ", sArg, 3, 192, true);    // exploding by spaces
        }

        // Get the target, find target returns a message on failure so we do not
        if ((target_count = ProcessTargetString(
                sArg[0],
                client,
                target_list,
                MAXPLAYERS,
                COMMAND_FILTER_NO_BOTS,
                target_name,
                sizeof(target_name),
                tn_is_ml)) <= 0)
        {
            ReplyToTargetError(client, target_count);
            return;
        }

        // Get the block length
        if (!StringToIntEx(sArg[1], length))    // not valid number in second argument
        {
            length = DefaultTime;
            Format(reason, sizeof(reason), "%s %s", sArg[1], sArg[2]);
        }
        else
        {
            strcopy(reason, sizeof(reason), sArg[2]);
        }

        // Strip spaces and quotes from reason
        TrimString(reason);
        StripQuotes(reason);

        if (!IsAllowedBlockLength(client, length, target_count))
        {
            ReplyToCommand(client, "%s%t", PREFIX, "no access");
            return;
        }
    }
    else
    {
        return;
    }

    new admImmunity = GetAdmImmunity(client);
    decl String:adminAuth[64];

    if (client && IsClientInGame(client))
    {
        GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
    }

    for (new i = 0; i < target_count; i++)
    {
        new target = target_list[i];

        #if defined DEBUG
            decl String:auth[64];
            GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth));
            PrintToServer("Processing block for %s", auth);
        #endif

        if (!g_bPlayerStatus[target])
        {
            // The target has not been blocks verify. It must be completed before you can block anyone.
            ReplyToCommand(client, "%s%t", PREFIX, "Player Comms Not Verified");
            skipped = true;
            continue; // skip
        }

        switch(type)
        {
            case TYPE_MUTE:
            {
                if (!BaseComm_IsClientMuted(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not muted. Mute him, creating unmute timer and add record to DB", auth);
                    #endif

                    PerformMute(target, _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" muted \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already muted", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already muted", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_GAG:
            {
                if (!BaseComm_IsClientGagged(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not gagged. Gag him, creating ungag timer and add record to DB", auth);
                    #endif

                    PerformGag(target, _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" gagged \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already gagged", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already gagged", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_SILENCE:
            {
                if (!BaseComm_IsClientGagged(target) && !BaseComm_IsClientMuted(target))
                {
                    #if defined DEBUG
                        PrintToServer("%s not silenced. Silence him, creating ungag & unmute timers and add records to DB", auth);
                    #endif

                    PerformMute(target, _, length, g_sName[client], adminAuth, admImmunity, reason);
                    PerformGag(target,  _, length, g_sName[client], adminAuth, admImmunity, reason);

                    LogAction(client, target, "\"%L\" silenced \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, length, reason);
                }
                else
                {
                    #if defined DEBUG
                        PrintToServer("%s already gagged or/and muted", auth);
                    #endif

                    ReplyToCommand(client, "%s%t", PREFIX, "Player already silenced", g_sName[target]);

                    skipped = true;
                    continue;
                }
            }
        }
    }
    if (target_count == 1 && !skipped)
        SavePunishment(client, target_list[0], type, length, reason);
    if (target_count > 1 || !skipped)
        ShowActivityToServer(client, type, length, reason, target_name, tn_is_ml);

    return;
}

stock ProcessUnBlock(client, targetId = 0, type, String:sReason[] = "", const String:sArgs[] = "")
{
    #if defined DEBUG
        PrintToServer("ProcessUnBlock(admin: %d, target: %d, type: %d, reason: %s, args: %s)", client, targetId, type, sReason, sArgs);
    #endif

    decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH], String:reason[256];

    if(targetId)
    {
        target_list[0] = targetId;
        target_count = 1;
        tn_is_ml = false;
        strcopy(target_name, sizeof(target_name), g_sName[targetId]);
        strcopy(reason,      sizeof(reason),      sReason);
    }
    else
    {
        new String:sBuffer[256], String:sArg[3][192];
        GetCmdArgString(sBuffer, sizeof(sBuffer));

        if (ExplodeString(sBuffer, "\"", sArg, 3, 192, true) == 3 && strlen(sArg[0]) == 0)
        {
            TrimString(sArg[2]);
            sArg[0] = sArg[1];  // target name
            sArg[1] = sArg[2];  // reason; sArg[2] - not in use
        }
        else
        {
            ExplodeString(sBuffer, " ", sArg, 2, 192, true);
        }
        strcopy(reason, sizeof(reason), sArg[1]);
        // Strip spaces and quotes from reason
        TrimString(reason);
        StripQuotes(reason);

        // Get the target, find target returns a message on failure so we do not
        if ((target_count = ProcessTargetString(
                sArg[0],
                client,
                target_list,
                MAXPLAYERS,
                COMMAND_FILTER_NO_BOTS,
                target_name,
                sizeof(target_name),
                tn_is_ml)) <= 0)
        {
            ReplyToTargetError(client, target_count);
            return;
        }
    }

    decl String:adminAuth[64];
    decl String:targetAuth[64];

    if (client && IsClientInGame(client))
    {
        GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
    }

    if (target_count > 1)
    {
        #if defined DEBUG
            PrintToServer("ProcessUnBlock - targets_count > 1");
        #endif

        for (new i = 0; i < target_count; i++)
        {
            new target = target_list[i];

            if (IsClientInGame(target))
                GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
            else
                continue;

            new Handle:dataPack = CreateDataPack();
            WritePackCell(dataPack, GetClientUserId2(client));
            WritePackCell(dataPack, GetClientUserId(target));
            WritePackCell(dataPack, type);
            WritePackString(dataPack, adminAuth);
            WritePackString(dataPack, targetAuth);    // not in use in this case
            WritePackString(dataPack, reason);

            TempUnBlock(dataPack);
        }

        #if defined DEBUG
            PrintToServer("Showing activity to server in ProcessUnBlock for targets_count > 1");
        #endif
        ShowActivityToServer(client, type + TYPE_TEMP_SHIFT, _, _, target_name, tn_is_ml);
    }
    else
    {
        decl String:typeWHERE[100];
        new bool:dontCheckDB = false;
        new target = target_list[0];

        if (IsClientInGame(target))
        {
            GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
        }
        else
        {
            return;
        }

        switch(type)
        {
            case TYPE_UNMUTE:
            {
                if (!BaseComm_IsClientMuted(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not muted");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "c.type = '%d'", TYPE_MUTE);
                    if (g_MuteType[target] == bSess)
                        dontCheckDB = true;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNGAG:
            {
                if (!BaseComm_IsClientGagged(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not gagged");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "c.type = '%d'", TYPE_GAG);
                    if (g_GagType[target] == bSess)
                        dontCheckDB = true;
                }
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNSILENCE:
            {
                if (!BaseComm_IsClientMuted(target) || !BaseComm_IsClientGagged(target))
                {
                    ReplyToCommand(client, "%s%t", PREFIX, "Player not silenced");
                    return;
                }
                else
                {
                    FormatEx(typeWHERE, sizeof(typeWHERE), "(c.type = '%d' OR c.type = '%d')", TYPE_MUTE, TYPE_GAG);
                    if (g_MuteType[target] == bSess && g_GagType[target] == bSess)
                        dontCheckDB = true;
                }
            }
        }

        // Pack everything into a data pack so we can retain it
        new Handle:dataPack = CreateDataPack();
        WritePackCell(dataPack, GetClientUserId2(client));
        WritePackCell(dataPack, GetClientUserId(target));
        WritePackCell(dataPack, type);
        WritePackString(dataPack, adminAuth);
        WritePackString(dataPack, targetAuth);
        WritePackString(dataPack, reason);

        // Check current player status. If player has temporary punishment - don't get info from DB
        if (!dontCheckDB && DB_Connect())
        {
            new String:sAdminAuthEscaped[sizeof(adminAuth) * 2 + 1];
            new String:sAdminAuthYZEscaped[sizeof(adminAuth) * 2 + 1];
            new String:sTargetAuthEscaped[sizeof(targetAuth) * 2 + 1];
            new String:sTargetAuthYZEscaped[sizeof(targetAuth) * 2 + 1];

            SQL_EscapeString(g_hDatabase, adminAuth,     sAdminAuthEscaped,    sizeof(sAdminAuthEscaped));
            SQL_EscapeString(g_hDatabase, adminAuth[8],  sAdminAuthYZEscaped,  sizeof(sAdminAuthYZEscaped));
            SQL_EscapeString(g_hDatabase, targetAuth,    sTargetAuthEscaped,   sizeof(sTargetAuthEscaped));
            SQL_EscapeString(g_hDatabase, targetAuth[8], sTargetAuthYZEscaped, sizeof(sTargetAuthYZEscaped));

            decl String:query[4096];
            Format(query, sizeof(query),
               "SELECT      c.bid, \
                            IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '0') as iaid, \
                            c.aid, \
                            IF (a.immunity>=g.immunity, a.immunity, IFNULL(g.immunity,0)) as immunity, \
                            c.type \
                FROM        %s_comms     AS c \
                LEFT JOIN   %s_admins    AS a ON a.aid = c.aid \
                LEFT JOIN   %s_srvgroups AS g ON g.name = a.srv_group \
                WHERE       RemoveType IS NULL \
                              AND (c.authid = '%s' OR c.authid REGEXP '^STEAM_[0-9]:%s$') \
                              AND (length = '0' OR ends > UNIX_TIMESTAMP()) \
                              AND %s",
                DatabasePrefix, sAdminAuthEscaped, sAdminAuthYZEscaped, DatabasePrefix, DatabasePrefix, DatabasePrefix, sTargetAuthEscaped, sTargetAuthYZEscaped, typeWHERE);

            #if defined LOG_QUERIES
                LogToFile(logQuery, "ProcessUnBlock. QUERY: %s", query);
            #endif

            SQL_TQuery(g_hDatabase, Query_UnBlockSelect, query, dataPack);
        }
        else
        {
            #if defined DEBUG
                PrintToServer("Calling TempUnBlock from ProcessUnBlock");
            #endif

            if (TempUnBlock(dataPack))
                ShowActivityToServer(client, type + TYPE_TEMP_SHIFT, _, _, g_sName[target], _);
        }
    }
}

stock bool:TempUnBlock(Handle:data)
{
    decl String:adminAuth[30], String:targetAuth[30], String:reason[256];
    ResetPack(data);
    new adminUserID  = ReadPackCell(data);
    new targetUserID = ReadPackCell(data);
    new type         = ReadPackCell(data);
    ReadPackString(data, adminAuth,  sizeof(adminAuth));
    ReadPackString(data, targetAuth, sizeof(targetAuth));
    ReadPackString(data, reason,     sizeof(reason));
    CloseHandle(data);    // Need to close datapack

    #if defined DEBUG
        PrintToServer("TempUnBlock(adminUID: %d, targetUID: %d, type: %d, adminAuth: %s, targetAuth: %s, reason: %s)", adminUserID, targetUserID, type, adminAuth, targetAuth, reason);
    #endif

    new admin  = GetClientOfUserId(adminUserID);
    new target = GetClientOfUserId(targetUserID);
    if (!target)
        return false; // target has gone away

    new AdmImmunity = GetAdmImmunity(admin);
    new bool:AdmImCheck = ( DisUBImCheck == 0
                            &&  (  ( type == TYPE_MUTE    && AdmImmunity > g_iMuteLevel[target] )
                                || ( type == TYPE_GAG     && AdmImmunity > g_iGagLevel[target]  )
                                || ( type == TYPE_SILENCE && AdmImmunity > g_iMuteLevel[target]
                                                          && AdmImmunity > g_iGagLevel[target])
                                )
                          );

    #if defined DEBUG
        PrintToServer("WHO WE ARE CHECKING!");
        if (!admin)
            PrintToServer("we are console (possibly)");
        if (AdmHasFlag(admin))
            PrintToServer("we have special flag");
    #endif

    // Check access for unblock without db changes (temporary unblock)
    new bool:bHasPermission = (!admin && StrEqual(adminAuth, "STEAM_ID_SERVER")) || AdmHasFlag(admin) || AdmImCheck;
    // can, if we are console or have special flag. else - deep checking by issuer authid
    if (!bHasPermission) {
        switch(type)
        {
            case TYPE_UNMUTE:
            {
                bHasPermission = StrEqual(adminAuth, g_sMuteAdminAuth[target]);
            }
            case TYPE_UNGAG:
            {
                bHasPermission = StrEqual(adminAuth, g_sGagAdminAuth[target]);
            }
            case TYPE_UNSILENCE:
            {
                bHasPermission = StrEqual(adminAuth, g_sMuteAdminAuth[target]) && StrEqual(adminAuth, g_sGagAdminAuth[target]);
            }
        }
    }

    if (bHasPermission)
    {
        switch(type)
        {
            case TYPE_UNMUTE:
            {
                PerformUnMute(target);
                LogAction(admin, target, "\"%L\" temporary unmuted \"%L\" (reason \"%s\")", admin, target, reason);
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNGAG:
            {
                PerformUnGag(target);
                LogAction(admin, target, "\"%L\" temporary ungagged \"%L\" (reason \"%s\")", admin, target, reason);
            }
            //-------------------------------------------------------------------------------------------------
            case TYPE_UNSILENCE:
            {
                PerformUnMute(target);
                PerformUnGag(target);
                LogAction(admin, target, "\"%L\" temporary unsilenced \"%L\" (reason \"%s\")", admin, target, reason);
            }
            default:
            {
                return false;
            }
        }
        return true;
    }
    else
    {
        if (admin && IsClientInGame(admin))
        {
            PrintToChat(admin, "%s%t", PREFIX, "No db error unlock perm");
            PrintToConsole(admin, "%s%t", PREFIX, "No db error unlock perm");
        }
        return false;
    }
}

stock InsertTempBlock(length, type, const String:name[], const String:auth[], const String:reason[], const String:adminAuth[], const String:adminIp[])
{
    LogMessage("Saving punishment for %s into queue", auth);

    new String:banName[MAX_NAME_LENGTH * 2 + 1];
    new String:banReason[256 * 2 + 1];
    new String:sAuthEscaped[64 * 2 + 1];
    new String:sAdminAuthEscaped[64 * 2 + 1];
    decl String:sQuery[4096], String:sQueryVal[2048];
    new String:sQueryMute[2048], String:sQueryGag[2048];

    // escaping everything
    SQL_EscapeString(SQLiteDB, name,      banName,           sizeof(banName));
    SQL_EscapeString(SQLiteDB, reason,    banReason,         sizeof(banReason));
    SQL_EscapeString(SQLiteDB, auth,      sAuthEscaped,      sizeof(sAuthEscaped));
    SQL_EscapeString(SQLiteDB, adminAuth, sAdminAuthEscaped, sizeof(sAdminAuthEscaped));

    // steam_id time start_time reason name admin_id admin_ip
    FormatEx(sQueryVal, sizeof(sQueryVal),
        "'%s', %d, %d, '%s', '%s', '%s', '%s'",
        sAuthEscaped, length, GetTime(), banReason, banName, sAdminAuthEscaped, adminIp);

    switch (type)
    {
        case TYPE_MUTE: FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, type);
        case TYPE_GAG:  FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, type);
        case TYPE_SILENCE:
        {
            FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, TYPE_MUTE);
            FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, TYPE_GAG);
        }
    }

    FormatEx(sQuery, sizeof(sQuery),
        "INSERT INTO queue2 (steam_id, time, start_time, reason, name, admin_id, admin_ip, type) VALUES %s%s%s",
        sQueryMute, type == TYPE_SILENCE ? ", " : "", sQueryGag);

    #if defined LOG_QUERIES
        LogToFile(logQuery, "InsertTempBlock. QUERY: %s", sQuery);
    #endif

    SQL_TQuery(SQLiteDB, Query_ErrorCheck, sQuery);
}

stock ServerInfo()
{
    decl pieces[4];
    new longip = GetConVarInt(CvarHostIp);
    pieces[0] = (longip >> 24) & 0x000000FF;
    pieces[1] = (longip >> 16) & 0x000000FF;
    pieces[2] = (longip >> 8) & 0x000000FF;
    pieces[3] = longip & 0x000000FF;
    FormatEx(ServerIp, sizeof(ServerIp), "%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
    GetConVarString(CvarPort, ServerPort, sizeof(ServerPort));
}

stock ReadConfig()
{
    InitializeConfigParser();

    if (ConfigParser == INVALID_HANDLE)
    {
        return;
    }

    decl String:ConfigFile1[PLATFORM_MAX_PATH], String:ConfigFile2[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, ConfigFile1, sizeof(ConfigFile1), "configs/sourcebans/sourcebans.cfg");
    BuildPath(Path_SM, ConfigFile2, sizeof(ConfigFile2), "configs/sourcebans/sourcecomms.cfg");

    if (FileExists(ConfigFile1))
    {
        PrintToServer("%sLoading configs/sourcebans/sourcebans.cfg config file", PREFIX);
        InternalReadConfig(ConfigFile1);
    }
    else
    {
        SetFailState("FATAL *** ERROR *** can't find %s", ConfigFile1);
    }
    if (FileExists(ConfigFile2))
    {
        PrintToServer("%sLoading configs/sourcecomms.cfg config file", PREFIX);
        PrintToServer("%sLoading configs/sourcecomms.cfg config file", PREFIX);
        iNumReasons = 0;
        iNumTimes = 0;
        InternalReadConfig(ConfigFile2);
        if (iNumReasons)
            iNumReasons--;
        if (iNumTimes)
            iNumTimes--;
        if (serverID == 0)
        {
            LogError("You must set valid `ServerID` value in sourcebans.cfg!");
            if (ConfigWhiteListOnly)
            {
                LogError("ServersWhiteList feature disabled!");
                ConfigWhiteListOnly = 0;
            }
        }
    }
    else
    {
        SetFailState("FATAL *** ERROR *** can't find %s", ConfigFile2);
    }
    #if defined DEBUG
        PrintToServer("Loaded DefaultTime value: %d", DefaultTime);
        PrintToServer("Loaded DisableUnblockImmunityCheck value: %d", DisUBImCheck);
    #endif
}

// some more

AdminMenu_GetPunishPhrase(client, target, String:name[], length)
{
    decl String:Buffer[192];
    if (g_MuteType[target] > bNot && g_GagType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Silenced", client, name);
    else if (g_MuteType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Muted", client, name);
    else if (g_GagType[target] > bNot)
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_Gagged", client, name);
    else
        Format(Buffer, sizeof(Buffer), "%T", "AdminMenu_Display_None", client, name);

    strcopy(name, length, Buffer);
}

bool:Bool_ValidMenuTarget(client, target)
{
    if (target <= 0)
    {
        if (client)
            PrintToChat(client, "%s%t", PREFIX, "AdminMenu_Not_Available");
        else
            ReplyToCommand(client, "%s%t", PREFIX, "AdminMenu_Not_Available");

        return false;
    }
    else if (!CanUserTarget(client, target))
    {
        if (client)
            PrintToChat(client, "%s%t", PREFIX, "Command_Target_Not_Targetable");
        else
            ReplyToCommand(client, "%s%t", PREFIX, "Command_Target_Not_Targetable");

        return false;
    }

    return true;
}

stock bool:IsAllowedBlockLength(admin, length, target_count = 1)
{
    if (target_count == 1)
    {
        if (!ConfigMaxLength)
            return true;    // Restriction disabled
        if (!admin)
            return true;    // all allowed for console
        if (AdmHasFlag(admin))
            return true;    // all allowed for admins with special flag
        if (!length || length > ConfigMaxLength)
            return false;
        else
            return true;
    }
    else
    {
        if (length < 0)
            return true;    // session punishments allowed for mass-tergeting
        if (!length)
            return false;
        if (length > MAX_TIME_MULTI)
            return false;
        if (length > DefaultTime)
            return false;
        else
            return true;
    }
}

stock bool:AdmHasFlag(admin)
{
    return admin && CheckCommandAccess(admin, "", UNBLOCK_FLAG, true);
}

stock _:GetAdmImmunity(admin)
{
    if (admin > 0 && GetUserAdmin(admin) != INVALID_ADMIN_ID)
        return GetAdminImmunityLevel(GetUserAdmin(admin));
    else
        return 0;
}

stock _:GetClientUserId2(client)
{
    if (client)
        return GetClientUserId(client);
    else
        return 0;    // for CONSOLE
}

stock ForcePlayersRecheck()
{
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && IsClientAuthorized(i) && !IsFakeClient(i) && g_hPlayerRecheck[i] == INVALID_HANDLE)
        {
            #if defined DEBUG
            {
                decl String:clientAuth[64];
                GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth));
                PrintToServer("Creating Recheck timer for %s", clientAuth);
            }
            #endif
            g_hPlayerRecheck[i] = CreateTimer(float(i), ClientRecheck, GetClientUserId(i));
        }
    }
}

stock bool:NotApplyToThisServer(srvID)
{
    if (ConfigWhiteListOnly && FindValueInArray(g_hServersWhiteList, srvID) == -1)
        return true;
    else
        return false;
}

stock MarkClientAsUnMuted(target)
{
    g_MuteType[target]          = bNot;
    g_iMuteTime[target]         = 0;
    g_iMuteLength[target]       = 0;
    g_iMuteLevel[target]        = -1;
    g_sMuteAdminName[target][0] = '\0';
    g_sMuteReason[target][0]    = '\0';
    g_sMuteAdminAuth[target][0] = '\0';
}

stock MarkClientAsUnGagged(target)
{
    g_GagType[target]          = bNot;
    g_iGagTime[target]         = 0;
    g_iGagLength[target]       = 0;
    g_iGagLevel[target]        = -1;
    g_sGagAdminName[target][0] = '\0';
    g_sGagReason[target][0]    = '\0';
    g_sGagAdminAuth[target][0] = '\0';
}

stock MarkClientAsMuted(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "")
{
    if (time)
        g_iMuteTime[target] = time;
    else
        g_iMuteTime[target] = GetTime();

    g_iMuteLength[target] = length;
    g_iMuteLevel[target]  = adminImmunity ? adminImmunity : ConsoleImmunity;
    strcopy(g_sMuteAdminName[target], sizeof(g_sMuteAdminName[]), adminName);
    strcopy(g_sMuteReason[target],    sizeof(g_sMuteReason[]),    reason);
    strcopy(g_sMuteAdminAuth[target], sizeof(g_sMuteAdminAuth[]), adminAuth);

    if (length > 0)
        g_MuteType[target] = bTime;
    else if (length == 0)
        g_MuteType[target] = bPerm;
    else
        g_MuteType[target] = bSess;
}

stock MarkClientAsGagged(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "")
{
    if (time)
        g_iGagTime[target] = time;
    else
        g_iGagTime[target] = GetTime();

    g_iGagLength[target] = length;
    g_iGagLevel[target]  = adminImmunity ? adminImmunity : ConsoleImmunity;
    strcopy(g_sGagAdminName[target], sizeof(g_sGagAdminName[]), adminName);
    strcopy(g_sGagReason[target],    sizeof(g_sGagReason[]),    reason);
    strcopy(g_sGagAdminAuth[target], sizeof(g_sGagAdminAuth[]), adminAuth);

    if (length > 0)
        g_GagType[target] = bTime;
    else if (length == 0)
        g_GagType[target] = bPerm;
    else
        g_GagType[target] = bSess;
}

stock CloseMuteExpireTimer(target)
{
    if (g_hMuteExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hMuteExpireTimer[target]))
        g_hMuteExpireTimer[target] = INVALID_HANDLE;
}

stock CloseGagExpireTimer(target)
{
    if (g_hGagExpireTimer[target] != INVALID_HANDLE && CloseHandle(g_hGagExpireTimer[target]))
        g_hGagExpireTimer[target] = INVALID_HANDLE;
}

stock CreateMuteExpireTimer(target, remainingTime = 0)
{
    if (g_iMuteLength[target] > 0)
    {
        if (remainingTime)
            g_hMuteExpireTimer[target] = CreateTimer(float(remainingTime),              Timer_MuteExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
        else
            g_hMuteExpireTimer[target] = CreateTimer(float(g_iMuteLength[target] * 60), Timer_MuteExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
    }
}

stock CreateGagExpireTimer(target, remainingTime = 0)
{
    if (g_iGagLength[target] > 0)
    {
        if (remainingTime)
            g_hGagExpireTimer[target] = CreateTimer(float(remainingTime),             Timer_GagExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
        else
            g_hGagExpireTimer[target] = CreateTimer(float(g_iGagLength[target] * 60), Timer_GagExpire, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE);
    }
}

stock PerformUnMute(target)
{
    MarkClientAsUnMuted(target);
    BaseComm_SetClientMute(target, false);
    CloseMuteExpireTimer(target);
}

stock PerformUnGag(target)
{
    MarkClientAsUnGagged(target);
    BaseComm_SetClientGag(target, false);
    CloseGagExpireTimer(target);
}

stock PerformMute(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "", remaining_time = 0)
{
    MarkClientAsMuted(target, time, length, adminName, adminAuth, adminImmunity, reason);
    BaseComm_SetClientMute(target, true);
    CreateMuteExpireTimer(target, remaining_time);
}

stock PerformGag(target, time = NOW, length = -1, const String:adminName[] = "CONSOLE", const String:adminAuth[] = "STEAM_ID_SERVER", adminImmunity = 0, const String:reason[] = "", remaining_time = 0)
{
    MarkClientAsGagged(target, time, length, adminName, adminAuth, adminImmunity, reason);
    BaseComm_SetClientGag(target, true);
    CreateGagExpireTimer(target, remaining_time);
}

stock SavePunishment(admin = 0, target, type, length = -1 , const String:reason[] = "")
{
    if (type < TYPE_MUTE || type > TYPE_SILENCE)
        return;

    // target information
    new String:targetAuth[64];
    if (IsClientInGame(target))
    {
        GetClientAuthId(target, AuthId_Steam2, targetAuth, sizeof(targetAuth));
    }
    else
    {
        return;
    }

    new String:adminIp[24];
    new String:adminAuth[64];
    if (admin && IsClientInGame(admin))
    {
        GetClientIP(admin, adminIp, sizeof(adminIp));
        GetClientAuthId(admin, AuthId_Steam2, adminAuth, sizeof(adminAuth));
    }
    else
    {
        // setup dummy adminAuth and adminIp for server
        strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
        strcopy(adminIp, sizeof(adminIp), ServerIp);
    }

    new String:sName[MAX_NAME_LENGTH];
    strcopy(sName, sizeof(sName), g_sName[target]);

    if (DB_Connect())
    {
        // Accepts length in minutes, writes to db in seconds! In all over places in plugin - length is in minutes.
        new String:banName[MAX_NAME_LENGTH * 2 + 1];
        new String:banReason[256 * 2 + 1];
        new String:sAuthidEscaped[64 * 2 + 1];
        new String:sAdminAuthIdEscaped[64 * 2 + 1];
        new String:sAdminAuthIdYZEscaped[64 * 2 + 1];
        decl String:sQuery[4096], String:sQueryAdm[512], String:sQueryVal[1024];
        new String:sQueryMute[1024], String:sQueryGag[1024];

        // escaping everything
        SQL_EscapeString(g_hDatabase, sName,        banName,               sizeof(banName));
        SQL_EscapeString(g_hDatabase, reason,       banReason,             sizeof(banReason));
        SQL_EscapeString(g_hDatabase, targetAuth,   sAuthidEscaped,        sizeof(sAuthidEscaped));
        SQL_EscapeString(g_hDatabase, adminAuth,    sAdminAuthIdEscaped,   sizeof(sAdminAuthIdEscaped));
        SQL_EscapeString(g_hDatabase, adminAuth[8], sAdminAuthIdYZEscaped, sizeof(sAdminAuthIdYZEscaped));

        // bid    authid    name    created ends lenght reason aid adminip    sid    removedBy removedType removedon type ureason
        FormatEx(sQueryAdm, sizeof(sQueryAdm),
            "IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), 0)",
            DatabasePrefix, sAdminAuthIdEscaped, sAdminAuthIdYZEscaped);

        // authid name, created, ends, length, reason, aid, adminIp, sid
        FormatEx(sQueryVal, sizeof(sQueryVal),
            "'%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', %s, '%s', %d",
            sAuthidEscaped, banName, length*60, length*60, banReason, sQueryAdm, adminIp, serverID);

        switch (type)
        {
            case TYPE_GAG:  FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, type);
            case TYPE_MUTE: FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, type);
            case TYPE_SILENCE:
            {
                FormatEx(sQueryMute, sizeof(sQueryMute), "(%s, %d)", sQueryVal, TYPE_MUTE);
                FormatEx(sQueryGag, sizeof(sQueryGag), "(%s, %d)", sQueryVal, TYPE_GAG);
            }
        }

        // litle magic - one query for all actions (mute, gag or silence)
        FormatEx(sQuery, sizeof(sQuery),
            "INSERT INTO %s_comms (authid, name, created, ends, length, reason, aid, adminIp, sid, type) VALUES %s%s%s",
            DatabasePrefix, sQueryMute, type == TYPE_SILENCE ? ", " : "", sQueryGag);

        #if defined LOG_QUERIES
            LogToFile(logQuery, "SavePunishment. QUERY: %s", sQuery);
        #endif

        // all data cached before calling asynchronous functions
        new Handle:dataPack = CreateDataPack();
        WritePackCell(dataPack, length);
        WritePackCell(dataPack, type);
        WritePackString(dataPack, sName);
        WritePackString(dataPack, targetAuth);
        WritePackString(dataPack, reason);
        WritePackString(dataPack, adminAuth);
        WritePackString(dataPack, adminIp);

        SQL_TQuery(g_hDatabase, Query_AddBlockInsert, sQuery, dataPack, DBPrio_High);
    }
    else
        InsertTempBlock(length, type, sName, targetAuth, reason, adminAuth, adminIp);
}

stock ShowActivityToServer(admin, type, length = 0, String:reason[] = "", String:targetName[], bool:ml = false)
{
    #if defined DEBUG
        PrintToServer("ShowActivityToServer(admin: %d, type: %d, length: %d, reason: %s, name: %s, ml: %b",
            admin, type, length, reason, targetName, ml);
    #endif

    new String:actionName[32], String:translationName[64];
    switch(type)
    {
        case TYPE_MUTE:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Muted");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permamuted");
            else    // temp block
                strcopy(actionName, sizeof(actionName), "Temp muted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_GAG:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Gagged");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permagagged");
            else    //temp block
                strcopy(actionName, sizeof(actionName), "Temp gagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_SILENCE:
        {
            if (length > 0)
                strcopy(actionName, sizeof(actionName), "Silenced");
            else if (length == 0)
                strcopy(actionName, sizeof(actionName), "Permasilenced");
            else    //temp block
                strcopy(actionName, sizeof(actionName), "Temp silenced");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_UNMUTE:
        {
            strcopy(actionName, sizeof(actionName), "Unmuted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_UNGAG:
        {
            strcopy(actionName, sizeof(actionName), "Ungagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNMUTE:
        {
            strcopy(actionName, sizeof(actionName), "Temp unmuted");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNGAG:
        {
            strcopy(actionName, sizeof(actionName), "Temp ungagged");
        }
        //-------------------------------------------------------------------------------------------------
        case TYPE_TEMP_UNSILENCE:
        {
            strcopy(actionName, sizeof(actionName), "Temp unsilenced");
        }
        //-------------------------------------------------------------------------------------------------
        default:
        {
            return;
        }
    }

    Format(translationName, sizeof(translationName), "%s %s", actionName, reason[0] == '\0' ? "player" : "player reason");
    #if defined DEBUG
        PrintToServer("translation name: %s", translationName);
    #endif

    if (length > 0)
    {
        if (ml)
            ShowActivity2(admin, PREFIX, "%t", translationName,       targetName, length, reason);
        else
            ShowActivity2(admin, PREFIX, "%t", translationName, "_s", targetName, length, reason);
    }
    else
    {
        if (ml)
            ShowActivity2(admin, PREFIX, "%t", translationName,       targetName,         reason);
        else
            ShowActivity2(admin, PREFIX, "%t", translationName, "_s", targetName,         reason);
    }
}

// Natives //
public Native_SetClientMute(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    new bool:muteState = GetNativeCell(2);
    new muteLength = GetNativeCell(3);
    if (muteState && muteLength == 0)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Permanent mute is not allowed!");
    }

    new bool:bSaveToDB = GetNativeCell(4);
    if (!muteState && bSaveToDB)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Removing punishments from DB is not allowed!");
    }

    decl String:sReason[256];
    GetNativeString(5, sReason, sizeof(sReason));

    if (muteState)
    {
        if (g_MuteType[target] > bNot)
        {
            return false;
        }

        PerformMute(target, _, muteLength, _, _, _, sReason);

        if (bSaveToDB)
            SavePunishment(_, target, TYPE_MUTE, muteLength, sReason);
    }
    else
    {
        if (g_MuteType[target] == bNot)
        {
            return false;
        }

        PerformUnMute(target);
    }

    return true;
}

public Native_SetClientGag(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    new bool:gagState = GetNativeCell(2);
    new gagLength = GetNativeCell(3);
    if (gagState && gagLength == 0)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Permanent gag is not allowed!");
    }

    new bool:bSaveToDB = GetNativeCell(4);
    if (!gagState && bSaveToDB)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Removing punishments from DB is not allowed!");
    }

    decl String:sReason[256];
    GetNativeString(5, sReason, sizeof(sReason));

    if (gagState)
    {
        if (g_GagType[target] > bNot)
        {
            return false;
        }

        PerformGag(target, _, gagLength, _, _, _, sReason);

        if (bSaveToDB)
            SavePunishment(_, target, TYPE_GAG, gagLength, sReason);
    }
    else
    {
        if (g_GagType[target] == bNot)
        {
            return false;
        }

        PerformUnGag(target);
    }

    return true;
}

public Native_GetClientMuteType(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    return bType:g_MuteType[target];
}

public Native_GetClientGagType(Handle:hPlugin, numParams)
{
    new target = GetNativeCell(1);
    if (target < 1 || target > MaxClients)
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", target);
    }

    if (!IsClientInGame(target))
    {
        return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", target);
    }

    return bType:g_GagType[target];
}
//Yarr!