Jetpacking changes movetype

Jetpacking changes movetype

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * TODO: Describe this plugin
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <smlib/entities>
#include <jetpack_plus>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "Jetpack Plus",
    author = "CrimsonTautology",
    description = "TODO: description",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;

new bool:g_IsUsingJetpack[MAXPLAYERS+1] = {false, ...};

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    if (LibraryExists("jetpack_plus"))
    {
        strcopy(error, err_max, "Jetpack Plus already loaded, aborting.");
        return APLRes_Failure;
    }

    RegPluginLibrary("jetpack_plus");

    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("jetpack_plus.phrases");

    g_Cvar_Enabled = CreateConVar("sm_jetpack_enabled", "1", "Enabled");

    RegConsoleCmd("sm_test", Command_Test, "TODO: TEST");
}

public OnClientDisconnect(client)
{
    StopJetpack(client);
}

public OnGameFrame()
{
    if(AreJetpacksEnabled())
    {
        for (new client=1; client <= MaxClients; client++)
        {
            //For each client using a jetpack
            if(IsClientUsingJetpack(client))
            {
                JetpackStep(client);
            }

        }

    }
}

public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
{
    if(!AreJetpacksEnabled()) return Plugin_Continue;

    if(buttons & IN_JUMP && !IsClientUsingJetpack(client))
    {
        //TODO send player id, not client id
        CreateTimer(0.2, HeldJump, client);
    }

    return Plugin_Continue;
}

//Call back for when a player presses jump.  Checks if jump is still held down
public Action:HeldJump(Handle:timer, any:client)
{
    //TODO retrieve client id from player id
    if(!IsClientInGame(client)) return Plugin_Handled;

    if(GetClientButtons(client) & IN_JUMP)
    {
        StartJetpack(client);
    }

    return Plugin_Handled;
}

public Action:Command_Test(client, args)
{
    return Plugin_Handled;
}

bool:IsClientUsingJetpack(client)
{
    return g_IsUsingJetpack[client];
}

bool:AreJetpacksEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}

StartJetpack(client)
{
    SetEntityMoveType(client, MOVETYPE_FLY);
    g_IsUsingJetpack[client] = true;
}

StopJetpack(client)
{
    SetEntityMoveType(client, MOVETYPE_WALK);
    g_IsUsingJetpack[client] = false;
}

//Called each frame a client is using a jetpack
JetpackStep(client)
{
    if(IsPlayerAlive(client))
    {
        JetpackPush(client, 100.0);
    }
    else
    {
        StopJetpack(client);
    }
}

JetpackPush(client, Float:force)
{
    new Float:vec[3];
    Entity_GetLocalVelocity(client, vec);
    vec[2] += force;
    Entity_SetLocalVelocity(client, vec);
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * TODO: Describe this plugin
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <smlib/entities>
#include <jetpack_plus>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "Jetpack Plus",
    author = "CrimsonTautology",
    description = "TODO: description",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;

new bool:g_IsUsingJetpack[MAXPLAYERS+1] = {false, ...};

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    if (LibraryExists("jetpack_plus"))
    {
        strcopy(error, err_max, "Jetpack Plus already loaded, aborting.");
        return APLRes_Failure;
    }

    RegPluginLibrary("jetpack_plus");

    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("jetpack_plus.phrases");

    g_Cvar_Enabled = CreateConVar("sm_jetpack_enabled", "1", "Enabled");

    RegConsoleCmd("sm_test", Command_Test, "TODO: TEST");
}

public OnClientDisconnect(client)
{
    StopJetpack(client);
}

public OnGameFrame()
{
    if(AreJetpacksEnabled())
    {
        for (new client=1; client <= MaxClients; client++)
        {
            //For each client using a jetpack
            if(IsClientUsingJetpack(client))
            {
                JetpackStep(client);
            }

        }

    }
}

public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
{
    if(!AreJetpacksEnabled()) return Plugin_Continue;

    if(buttons & IN_JUMP && !IsClientUsingJetpack(client))
    {
        //TODO send player id, not client id
        CreateTimer(0.2, HeldJump, client);
    }

    return Plugin_Continue;
}

//Call back for when a player presses jump.  Checks if jump is still held down
public Action:HeldJump(Handle:timer, any:client)
{
    //TODO retrieve client id from player id
    if(!IsClientInGame(client)) return Plugin_Handled;

    if(GetClientButtons(client) & IN_JUMP)
    {
        StartJetpack(client);
    }

    return Plugin_Handled;
}

public Action:Command_Test(client, args)
{
    return Plugin_Handled;
}

bool:IsClientUsingJetpack(client)
{
    return g_IsUsingJetpack[client];
}

bool:AreJetpacksEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}

StartJetpack(client)
{
    g_IsUsingJetpack[client] = true;
}

StopJetpack(client)
{
    g_IsUsingJetpack[client] = false;
}

//Called each frame a client is using a jetpack
JetpackStep(client)
{
    if(IsPlayerAlive(client))
    {
        JetpackPush(client, 100.0);
    }
    else
    {
        StopJetpack(client);
    }
}

JetpackPush(client, Float:force)
{
    new Float:vec[3];
    Entity_GetLocalVelocity(client, vec);
    vec[2] += force;
    Entity_SetLocalVelocity(client, vec);
}


Fix map const

Fix map const

/*
enum struct MapData
{
    // mapdata.kv
    char m_FileName[128];
    char m_Description[64];
    int  m_Price;
    int  m_PricePartyBlock;
    int  m_FileSize;
    int  m_MinPlayers;
    int  m_MaxPlayers;
    int  m_MaxCooldown;
    bool m_NominateOnly;
    bool m_VipOnly;
    bool m_AdminOnly;
    bool m_CertainTimes[24]; // 0-23
    float m_RefundRatio;

    // mappool.kv
    int  m_CooldownLeft;
    int  m_RecentlyPlayed;
}
*/

static StringMap g_MapData;

public any Native_GetMapData(Handle plugin, int numParams)
{
    char map[128];
    GetNativeString(1, map, 128);
    MapData mapdata;
    bool r = g_MapData.GetArray(map, mapdata, typeofdata);
    SetNativeArray(2, mapdata, GetNativeCell(3));
    return r;
}

void Data_OnPluginStart()
{
    g_MapData = new StringMap();
}

void Data_OnAllPluginsLoaded()
{
    LoadMapData();
    LoadMapPool();
}

void Data_OnMapEnd()
{
    if (!g_bAllowCountdown)
        return;

    ArrayList maps = GetAllMapsName();

    char map[128]; MapData mapdata;
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        if (!g_MapData.GetArray(map, mapdata, typeofdata))
            continue;

        if (mapdata.m_CooldownLeft > 0)
        {
            mapdata.m_CooldownLeft--;
            g_MapData.SetArray(map, mapdata, typeofdata, true);
            SaveMapPool(map);
        }
    }
}

static void LoadMapData()
{
    char path[128];
    BuildPath(Path_SM, path, 128, "configs/mapdata.kv");

    KeyValues kv = new KeyValues("MapData");
    if (!FileExists(path))
    {
        SetAllMapsDefault(kv);
    }
    else
    {
        kv.ImportFromFile(path);
        LoadAllMapsData(kv);
    }

    kv.Rewind();
    kv.ExportToFile(path);

    delete kv;
}

static void SetAllMapsDefault(KeyValues kv)
{
    ArrayList maps = GetAllMapsName();

    LogMessage("Process SetAllMapsDefault with %d maps.", maps.Length);

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        kv.JumpToKey(map, true);
        kv.SetString("m_Description",  "null");
        kv.SetString("m_CertainTimes", "all");
        kv.SetNum("m_Price", 100);
        kv.SetNum("m_PricePartyBlock", 3000);
        kv.SetNum("m_MinPlayers", 0);
        kv.SetNum("m_MaxPlayers", 0);
        kv.SetNum("m_MaxCooldown", 100);
        kv.SetNum("m_NominateOnly", 0);
        kv.SetNum("m_VipOnly", 0);
        kv.SetNum("m_AdminOnly", 0);
        kv.Rewind();

        MapData mapdata;
        strcopy(mapdata.m_FileName,   128, map);
        strcopy(mapdata.m_Description, 32, "null");
        mapdata.m_Price = 100;
        mapdata.m_PricePartyBlock = 3000;
        mapdata.m_FileSize = GetMapFileSize(map);
        mapdata.m_MaxCooldown = 100;
        g_MapData.SetArray(map, mapdata, typeofdata, true);
    }

    delete maps;
}

static void LoadAllMapsData(KeyValues kv)
{
    ArrayList maps = GetAllMapsName();

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);
        kv.Rewind();

        if (!kv.JumpToKey(map, false))
        {
            // ?
            LogMessage("%s is missing in mapdata.", map);

            kv.JumpToKey(map, true);
            kv.SetString("m_Description",  "null");
            kv.SetString("m_CertainTimes", "all");
            kv.SetNum("m_Price", 100);
            kv.SetNum("m_PricePartyBlock", 3000);
            kv.SetNum("m_MinPlayers", 0);
            kv.SetNum("m_MaxPlayers", 0);
            kv.SetNum("m_MaxCooldown", 100);
            kv.SetNum("m_NominateOnly", 0);
            kv.SetNum("m_VipOnly", 0);
            kv.SetNum("m_AdminOnly", 0);
            kv.Rewind();

            MapData mapdata;
            strcopy(mapdata.m_FileName,   128, map);
            strcopy(mapdata.m_Description, 32, "null");
            mapdata.m_Price = 100;
            mapdata.m_PricePartyBlock = 3000;
            mapdata.m_FileSize = GetMapFileSize(map);
            mapdata.m_MaxCooldown = 100;
            g_MapData.SetArray(map, mapdata, typeofdata, true);

            continue;
        }

        MapData mapdata;
        strcopy(mapdata.m_FileName, 128, map);
        kv.GetString("m_Description", mapdata.m_Description, 32, "null");
        mapdata.m_Price           = kv.GetNum("m_Price", 100);
        mapdata.m_PricePartyBlock = kv.GetNum("m_PricePartyBlock", 3000);
        mapdata.m_FileSize        = GetMapFileSize(map);
        mapdata.m_MinPlayers      = kv.GetNum("m_MinPlayers", 0);
        mapdata.m_MaxPlayers      = kv.GetNum("m_MaxPlayers", 0);
        mapdata.m_MaxCooldown     = kv.GetNum("m_MaxCooldown", 100);
        mapdata.m_NominateOnly    = kv.GetNum("m_NominateOnly", 0) == 1;
        mapdata.m_VipOnly         = kv.GetNum("m_VipOnly", 0) == 1;
        mapdata.m_AdminOnly       = kv.GetNum("m_AdminOnly", 0) == 1;
        mapdata.m_RefundRatio     = kv.GetFloat("m_RefundRatio", -1.0);

        char m_CertainTimes[128];
        kv.GetString("m_CertainTimes", m_CertainTimes, 128, "all");
        if (strcmp(m_CertainTimes, "all") == 0)
        {
            // all time
            for (int i = 0; i < 24; i++)
                mapdata.m_CertainTimes[i] = true;
        }
        else
        {
            char o[24][4];
            int c = ExplodeString(m_CertainTimes, ",", o, 24, 4, false);
            for (int i = 0; i < c; i++) if (strlen(o[i]) > 0)
                mapdata.m_CertainTimes[StringToInt(o[i])] = true;
        }

        g_MapData.SetArray(map, mapdata, typeofdata, true);
    }

    delete maps;
}

bool GetDescEx(const char[] map, char[] desc, int maxLen, bool includeName, bool includeTag, bool includePrice = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetDescEx -> Failed to get map %s", map);
        return false;
    }

    strcopy(desc, maxLen, mapdata.m_Description);

    if (includeName)
        Format(desc, maxLen, "%s\n%s", map, desc);

    if (includeTag)
    {
        Format(desc, maxLen, "%s%s", mapdata.m_AdminOnly ? "[ADMIN] "   : "", desc);
        Format(desc, maxLen, "%s%s", mapdata.m_VipOnly   ? "[VIP]     " : "", desc);
    }

    if (includePrice)
    {
        // ?
        Format(desc, maxLen, "%s[%d]", desc, mapdata.m_Price);
    }

    return true;
}

float GetRefundRatio(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // fallback to default cvar
        return g_ConVars.Refunds.FloatValue;
    }

    if (mapdata.m_RefundRatio < 0.0)
        return g_ConVars.Refunds.FloatValue;

    if (mapdata.m_RefundRatio > 1.0)
        return 1.0;

    return mapdata.m_RefundRatio;
}

bool IsBigMap(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsBigMap -> Failed to get map %s", map);
        return GetMapFileSize(map) > 150;
    }

    return mapdata.m_FileSize > 150;
}

stock int GetRefundCredits(const char[] map)
{
    // disallow refund
    if (g_ConVars.Refunds.FloatValue <= 0.0)
        return 0;

    return RoundToCeil(float(GetPrice(map)) * GetRefundRatio(map));
}

int GetRefundCreditsByNomination(Nominations n)
{
    // disallow refund
    if (g_ConVars.Refunds.FloatValue <= 0.0)
        return 0;

    return RoundToCeil(float(n.m_Price) * GetRefundRatio(n.m_Map));
}

int GetPrice(const char[] map, bool recently = true, bool partyblock = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetPrice -> Failed to get map %s", map);
        return 100;
    }

    if (partyblock)
        return mapdata.m_PricePartyBlock;

    if (recently && g_ConVars.Recents.IntValue > -1)
    {
        int last = GetLastPlayed(map);
        int time = GetTime();
        int hour = (time - last) / 3600;
        if (hour < g_ConVars.Recents.IntValue) //
        {
            return RoundFloat(mapdata.m_Price *= (1.0 + g_ConVars.LtpMtpl.FloatValue));
        }
    }

    return mapdata.m_Price;
}

int GetMinPlayers(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetMinPlayers -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_MinPlayers;
}

int GetMaxPlayers(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetMaxPlayers -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_MaxPlayers;
}

bool IsNominateOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsNominateOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_NominateOnly;
}

bool IsAdminOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsAdminOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_AdminOnly;
}

bool IsVIPOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsVIPOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_VipOnly;
}

bool IsCertainTimes(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsCertainTimes -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_CertainTimes[GetTodayHours()];
}

bool SetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("SetLastPlayed -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_RecentlyPlayed = GetTime();
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

int GetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetLastPlayed -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_RecentlyPlayed;
}

int GetCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetCooldown -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_CooldownLeft;
}

bool SetCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("SetCooldown -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_CooldownLeft = mapdata.m_MaxCooldown;
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

bool ClearCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("ClearCooldown -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_CooldownLeft = 0;
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

static void LoadMapPool()
{
    char path[128];
    BuildPath(Path_SM, path, 128, "data/mappool.kv");

    KeyValues kv = new KeyValues("MapData");
    if (!FileExists(path))
    {
        delete kv;
        return;
    }
    if (!kv.ImportFromFile(path))
    {
        LogStackTrace("LoadMapPool -> failed to import keyvalues from %s", path);
        delete kv;
        return;
    }

    ArrayList maps = GetAllMapsName();

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);
        kv.Rewind();

        MapData mapdata;
        if (!g_MapData.GetArray(map, mapdata, typeofdata))
        {
            // ??
            continue;
        }

        if (kv.JumpToKey(map, false))
        {
            mapdata.m_CooldownLeft = kv.GetNum("m_CooldownLeft", 0);
            mapdata.m_RecentlyPlayed = kv.GetNum("m_RecentlyPlayed", 0);
            g_MapData.SetArray(map, mapdata, typeofdata, true);
        }
    }

    delete kv;
    delete maps;
}

static void SaveMapPool(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // ??
        LogStackTrace("SaveMapPool -> Failed to save %s", map);
        return;
    }

    char path[128];
    BuildPath(Path_SM, path, 128, "data/mappool.kv");

    KeyValues kv = new KeyValues("MapData");
    if (FileExists(path))
    {
        kv.ImportFromFile(path);
    }

    kv.JumpToKey(map, true);
    kv.SetNum("m_CooldownLeft", mapdata.m_CooldownLeft);
    kv.SetNum("m_RecentlyPlayed", mapdata.m_RecentlyPlayed);
    kv.Rewind();
    kv.ExportToFile(path);

    delete kv;

    Call_MapVotePoolChanged();
}

void ClearAllCooldown()
{
    ArrayList maps = GetAllMapsName();

    char map[128]; MapData mapdata;
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        if (!g_MapData.GetArray(map, mapdata, typeofdata))
            continue;

        if (mapdata.m_CooldownLeft > 0)
        {
            mapdata.m_CooldownLeft = 0;
            g_MapData.SetArray(map, mapdata, typeofdata, true);
            SaveMapPool(map);
        }
    }
}

void ClearMapCooldown(int client, const char[] map)
{
    char alter[128];
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (ClearCooldown(alter))
        {
            tChatAll("%t", "mcr clear map cd", alter);
            LogAction(client, -1, "%L -> Clear [%s] cooldown.", client, alter);
        }
    }
}

void ResetMapCooldown(int client, const char[] map)
{
    char alter[128];
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (SetCooldown(alter))
        {
            tChatAll("%t", "mcr reset map cd", alter);
            LogAction(client, -1, "%L -> Reset [%s] cooldown.", client, alter);
        }
    }
}

void DisplayCooldownList(int client)
{
    PrintToConsole(client, "============[MCR]============");
    char map[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, map, 128);

        if (g_MapData.GetArray(map, mapdata, typeofdata))
        {
            if (mapdata.m_CooldownLeft > 0)
            {
                PrintToConsole(client, "[%s/%s] -> %s", PadLeft(mapdata.m_CooldownLeft, 3, " "), PadLeft(mapdata.m_MaxCooldown, 3, " "), map);
            }
        }
    }
}

void GetCooldownMaps(ArrayList maps)
{
    char map[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, map, 128);

        if (g_MapData.GetArray(map, mapdata, typeofdata))
        {
            if (mapdata.m_CooldownLeft > 0)
            {
                maps.PushString(map);
            }
        }
    }
}

void DisplayMapAttributes(int client, const char[] map)
{
    char alter[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (g_MapData.GetArray(alter, mapdata, typeofdata))
        {
            PrintToConsole(client, "===========[%s]===========", alter);
            PrintToConsole(client, "m_FileName       : %s", mapdata.m_FileName);
            PrintToConsole(client, "m_Description    : %s", mapdata.m_Description);
            PrintToConsole(client, "m_Price          : %d", mapdata.m_Price);
            PrintToConsole(client, "m_PricePartyBlock: %d", mapdata.m_PricePartyBlock);
            PrintToConsole(client, "m_FileSize       : %d", mapdata.m_FileSize);
            PrintToConsole(client, "m_MinPlayers     : %d", mapdata.m_MinPlayers);
            PrintToConsole(client, "m_MinPlayers     : %d", mapdata.m_MaxPlayers);
            PrintToConsole(client, "m_MaxCooldown    : %d", mapdata.m_MaxCooldown);
            PrintToConsole(client, "m_NominateOnly   : %b", mapdata.m_NominateOnly);
            PrintToConsole(client, "m_AdminOnly      : %b", mapdata.m_AdminOnly);
            PrintToConsole(client, "m_VipOnly        : %b", mapdata.m_VipOnly);
            PrintToConsole(client, "m_CooldownLeft   : %d", mapdata.m_CooldownLeft);
            PrintToConsole(client, "m_RecentlyPlayed : %d", mapdata.m_RecentlyPlayed);
            PrintToConsole(client, "m_RefundRatio    : %f", mapdata.m_RefundRatio);
        }
    }
}

/*
enum struct MapData
{
    // mapdata.kv
    char m_FileName[128];
    char m_Description[64];
    int  m_Price;
    int  m_PricePartyBlock;
    int  m_FileSize;
    int  m_MinPlayers;
    int  m_MaxPlayers;
    int  m_MaxCooldown;
    bool m_NominateOnly;
    bool m_VipOnly;
    bool m_AdminOnly;
    bool m_CertainTimes[24]; // 0-23
    float m_RefundRatio;

    // mappool.kv
    int  m_CooldownLeft;
    int  m_RecentlyPlayed;
}
*/

static StringMap g_MapData;

public any Native_GetMapData(Handle plugin, int numParams)
{
    char map[128];
    GetNativeString(1, map, 128);
    MapData mapdata;
    bool r = g_MapData.GetArray(map, mapdata, typeofdata);
    SetNativeArray(2, mapdata, GetNativeCell(3));
    return r;
}

void Data_OnPluginStart()
{
    g_MapData = new StringMap();
}

void Data_OnAllPluginsLoaded()
{
    LoadMapData();
    LoadMapPool();
}

void Data_OnMapEnd()
{
    if (!g_bAllowCountdown)
        return;

    ArrayList maps = GetAllMapsName();

    char map[128]; MapData mapdata;
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        if (!g_MapData.GetArray(map, mapdata, typeofdata))
            continue;

        if (mapdata.m_CooldownLeft > 0)
        {
            mapdata.m_CooldownLeft--;
            g_MapData.SetArray(map, mapdata, typeofdata, true);
            SaveMapPool(map);
        }
    }
}

static void LoadMapData()
{
    char path[128];
    BuildPath(Path_SM, path, 128, "configs/mapdata.kv");

    KeyValues kv = new KeyValues("MapData");
    if (!FileExists(path))
    {
        SetAllMapsDefault(kv);
    }
    else
    {
        kv.ImportFromFile(path);
        LoadAllMapsData(kv);
    }

    kv.Rewind();
    kv.ExportToFile(path);

    delete kv;
}

static void SetAllMapsDefault(KeyValues kv)
{
    ArrayList maps = GetAllMapsName();

    LogMessage("Process SetAllMapsDefault with %d maps.", maps.Length);

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        kv.JumpToKey(map, true);
        kv.SetString("m_Description",  "null");
        kv.SetString("m_CertainTimes", "all");
        kv.SetNum("m_Price", 100);
        kv.SetNum("m_PricePartyBlock", 3000);
        kv.SetNum("m_MinPlayers", 0);
        kv.SetNum("m_MaxPlayers", 0);
        kv.SetNum("m_MaxCooldown", 100);
        kv.SetNum("m_NominateOnly", 0);
        kv.SetNum("m_VipOnly", 0);
        kv.SetNum("m_AdminOnly", 0);
        kv.Rewind();

        MapData mapdata;
        strcopy(mapdata.m_FileName,   128, map);
        strcopy(mapdata.m_Description, 32, "null");
        mapdata.m_Price = 100;
        mapdata.m_PricePartyBlock = 3000;
        mapdata.m_FileSize = GetMapFileSize(map);
        mapdata.m_MaxCooldown = 100;
        g_MapData.SetArray(map, mapdata, typeofdata, true);
    }

    delete maps;
}

static void LoadAllMapsData(KeyValues kv)
{
    ArrayList maps = GetAllMapsName();

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);
        kv.Rewind();

        if (!kv.JumpToKey(map, false))
        {
            // ?
            LogMessage("%s is missing in mapdata.", map);

            kv.JumpToKey(map, true);
            kv.SetString("m_Description",  "null");
            kv.SetString("m_CertainTimes", "all");
            kv.SetNum("m_Price", 100);
            kv.SetNum("m_PricePartyBlock", 3000);
            kv.SetNum("m_MinPlayers", 0);
            kv.SetNum("m_MaxPlayers", 0);
            kv.SetNum("m_MaxCooldown", 100);
            kv.SetNum("m_NominateOnly", 0);
            kv.SetNum("m_VipOnly", 0);
            kv.SetNum("m_AdminOnly", 0);
            kv.Rewind();

            MapData mapdata;
            strcopy(mapdata.m_FileName,   128, map);
            strcopy(mapdata.m_Description, 32, "null");
            mapdata.m_Price = 100;
            mapdata.m_PricePartyBlock = 3000;
            mapdata.m_FileSize = GetMapFileSize(map);
            mapdata.m_MaxCooldown = 100;
            g_MapData.SetArray(map, mapdata, typeofdata, true);

            continue;
        }

        MapData mapdata;
        strcopy(mapdata.m_FileName, 128, map);
        kv.GetString("m_Description", mapdata.m_Description, 32, "null");
        mapdata.m_Price           = kv.GetNum("m_Price", 100);
        mapdata.m_PricePartyBlock = kv.GetNum("m_PricePartyBlock", 3000);
        mapdata.m_FileSize        = GetMapFileSize(map);
        mapdata.m_MinPlayers      = kv.GetNum("m_MinPlayers", 0);
        mapdata.m_MaxPlayers      = kv.GetNum("m_MaxPlayers", 0);
        mapdata.m_MaxCooldown     = kv.GetNum("m_MaxCooldown", 100);
        mapdata.m_NominateOnly    = kv.GetNum("m_NominateOnly", 0) == 1;
        mapdata.m_VipOnly         = kv.GetNum("m_VipOnly", 0) == 1;
        mapdata.m_AdminOnly       = kv.GetNum("m_AdminOnly", 0) == 1;
        mapdata.m_RefundRatio     = kv.GetFloat("m_RefundRatio", -1.0);

        char m_CertainTimes[128];
        kv.GetString("m_CertainTimes", m_CertainTimes, 128, "all");
        if (strcmp(m_CertainTimes, "all") == 0)
        {
            // all time
            for (int i = 0; i < 24; i++)
                mapdata.m_CertainTimes[i] = true;
        }
        else
        {
            char o[24][4];
            int c = ExplodeString(m_CertainTimes, ",", o, 24, 4, false);
            for (int i = 0; i < c; i++) if (strlen(o[i]) > 0)
                mapdata.m_CertainTimes[StringToInt(o[i])] = true;
        }

        g_MapData.SetArray(map, mapdata, typeofdata, true);
    }

    delete maps;
}

bool GetDescEx(const char[] map, char[] desc, int maxLen, bool includeName, bool includeTag, bool includePrice = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetDescEx -> Failed to get map %s", map);
        return false;
    }

    strcopy(desc, maxLen, mapdata.m_Description);

    if (includeName)
        Format(desc, maxLen, "%s\n%s", map, desc);

    if (includeTag)
    {
        Format(desc, maxLen, "%s%s", mapdata.m_AdminOnly ? "[ADMIN] "   : "", desc);
        Format(desc, maxLen, "%s%s", mapdata.m_VipOnly   ? "[VIP]     " : "", desc);
    }

    if (includePrice)
    {
        // ?
        Format(desc, maxLen, "%s[%d]", desc, mapdata.m_Price);
    }

    return true;
}

float GetRefundRatio(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // fallback to default cvar
        return g_ConVars.Refunds.FloatValue;
    }

    if (mapdata.m_RefundRatio < 0.0)
        return g_ConVars.Refunds.FloatValue;

    if (mapdata.m_RefundRatio > 1.0)
        return 1.0;

    return mapdata.m_RefundRatio;
}

bool IsBigMap(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsBigMap -> Failed to get map %s", map);
        return GetMapFileSize(map) > 150;
    }

    return mapdata.m_FileSize > 150;
}

stock int GetRefundCredits(const char[] map)
{
    // disallow refund
    if (g_ConVars.Refunds.FloatValue <= 0.0)
        return 0;

    return RoundToCeil(float(GetPrice(map)) * GetRefundRatio(map));
}

int GetRefundCreditsByNomination(Nominations n)
{
    // disallow refund
    if (g_ConVars.Refunds.FloatValue <= 0.0)
        return 0;

    return RoundToCeil(float(n.m_Price) * GetRefundRatio(map));
}

int GetPrice(const char[] map, bool recently = true, bool partyblock = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetPrice -> Failed to get map %s", map);
        return 100;
    }

    if (partyblock)
        return mapdata.m_PricePartyBlock;

    if (recently && g_ConVars.Recents.IntValue > -1)
    {
        int last = GetLastPlayed(map);
        int time = GetTime();
        int hour = (time - last) / 3600;
        if (hour < g_ConVars.Recents.IntValue) //
        {
            return RoundFloat(mapdata.m_Price *= (1.0 + g_ConVars.LtpMtpl.FloatValue));
        }
    }

    return mapdata.m_Price;
}

int GetMinPlayers(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetMinPlayers -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_MinPlayers;
}

int GetMaxPlayers(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetMaxPlayers -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_MaxPlayers;
}

bool IsNominateOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsNominateOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_NominateOnly;
}

bool IsAdminOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsAdminOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_AdminOnly;
}

bool IsVIPOnly(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsVIPOnly -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_VipOnly;
}

bool IsCertainTimes(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsCertainTimes -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_CertainTimes[GetTodayHours()];
}

bool SetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("SetLastPlayed -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_RecentlyPlayed = GetTime();
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

int GetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetLastPlayed -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_RecentlyPlayed;
}

int GetCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("GetCooldown -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_CooldownLeft;
}

bool SetCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("SetCooldown -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_CooldownLeft = mapdata.m_MaxCooldown;
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

bool ClearCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("ClearCooldown -> Failed to get map %s", map);
        return false;
    }

    mapdata.m_CooldownLeft = 0;
    g_MapData.SetArray(map, mapdata, typeofdata, true);
    SaveMapPool(map);
    return true;
}

static void LoadMapPool()
{
    char path[128];
    BuildPath(Path_SM, path, 128, "data/mappool.kv");

    KeyValues kv = new KeyValues("MapData");
    if (!FileExists(path))
    {
        delete kv;
        return;
    }
    if (!kv.ImportFromFile(path))
    {
        LogStackTrace("LoadMapPool -> failed to import keyvalues from %s", path);
        delete kv;
        return;
    }

    ArrayList maps = GetAllMapsName();

    char map[128];
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);
        kv.Rewind();

        MapData mapdata;
        if (!g_MapData.GetArray(map, mapdata, typeofdata))
        {
            // ??
            continue;
        }

        if (kv.JumpToKey(map, false))
        {
            mapdata.m_CooldownLeft = kv.GetNum("m_CooldownLeft", 0);
            mapdata.m_RecentlyPlayed = kv.GetNum("m_RecentlyPlayed", 0);
            g_MapData.SetArray(map, mapdata, typeofdata, true);
        }
    }

    delete kv;
    delete maps;
}

static void SaveMapPool(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // ??
        LogStackTrace("SaveMapPool -> Failed to save %s", map);
        return;
    }

    char path[128];
    BuildPath(Path_SM, path, 128, "data/mappool.kv");

    KeyValues kv = new KeyValues("MapData");
    if (FileExists(path))
    {
        kv.ImportFromFile(path);
    }

    kv.JumpToKey(map, true);
    kv.SetNum("m_CooldownLeft", mapdata.m_CooldownLeft);
    kv.SetNum("m_RecentlyPlayed", mapdata.m_RecentlyPlayed);
    kv.Rewind();
    kv.ExportToFile(path);

    delete kv;

    Call_MapVotePoolChanged();
}

void ClearAllCooldown()
{
    ArrayList maps = GetAllMapsName();

    char map[128]; MapData mapdata;
    for(int index = 0; index < maps.Length; index++)
    {
        maps.GetString(index, map, 128);

        if (!g_MapData.GetArray(map, mapdata, typeofdata))
            continue;

        if (mapdata.m_CooldownLeft > 0)
        {
            mapdata.m_CooldownLeft = 0;
            g_MapData.SetArray(map, mapdata, typeofdata, true);
            SaveMapPool(map);
        }
    }
}

void ClearMapCooldown(int client, const char[] map)
{
    char alter[128];
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (ClearCooldown(alter))
        {
            tChatAll("%t", "mcr clear map cd", alter);
            LogAction(client, -1, "%L -> Clear [%s] cooldown.", client, alter);
        }
    }
}

void ResetMapCooldown(int client, const char[] map)
{
    char alter[128];
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (SetCooldown(alter))
        {
            tChatAll("%t", "mcr reset map cd", alter);
            LogAction(client, -1, "%L -> Reset [%s] cooldown.", client, alter);
        }
    }
}

void DisplayCooldownList(int client)
{
    PrintToConsole(client, "============[MCR]============");
    char map[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, map, 128);

        if (g_MapData.GetArray(map, mapdata, typeofdata))
        {
            if (mapdata.m_CooldownLeft > 0)
            {
                PrintToConsole(client, "[%s/%s] -> %s", PadLeft(mapdata.m_CooldownLeft, 3, " "), PadLeft(mapdata.m_MaxCooldown, 3, " "), map);
            }
        }
    }
}

void GetCooldownMaps(ArrayList maps)
{
    char map[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, map, 128);

        if (g_MapData.GetArray(map, mapdata, typeofdata))
        {
            if (mapdata.m_CooldownLeft > 0)
            {
                maps.PushString(map);
            }
        }
    }
}

void DisplayMapAttributes(int client, const char[] map)
{
    char alter[128]; MapData mapdata;
    for (int i = 0; i < g_aMapList.Length; i++)
    {
        g_aMapList.GetString(i, alter, 128);

        if (StrContains(alter, map, false) > -1)
        if (g_MapData.GetArray(alter, mapdata, typeofdata))
        {
            PrintToConsole(client, "===========[%s]===========", alter);
            PrintToConsole(client, "m_FileName       : %s", mapdata.m_FileName);
            PrintToConsole(client, "m_Description    : %s", mapdata.m_Description);
            PrintToConsole(client, "m_Price          : %d", mapdata.m_Price);
            PrintToConsole(client, "m_PricePartyBlock: %d", mapdata.m_PricePartyBlock);
            PrintToConsole(client, "m_FileSize       : %d", mapdata.m_FileSize);
            PrintToConsole(client, "m_MinPlayers     : %d", mapdata.m_MinPlayers);
            PrintToConsole(client, "m_MinPlayers     : %d", mapdata.m_MaxPlayers);
            PrintToConsole(client, "m_MaxCooldown    : %d", mapdata.m_MaxCooldown);
            PrintToConsole(client, "m_NominateOnly   : %b", mapdata.m_NominateOnly);
            PrintToConsole(client, "m_AdminOnly      : %b", mapdata.m_AdminOnly);
            PrintToConsole(client, "m_VipOnly        : %b", mapdata.m_VipOnly);
            PrintToConsole(client, "m_CooldownLeft   : %d", mapdata.m_CooldownLeft);
            PrintToConsole(client, "m_RecentlyPlayed : %d", mapdata.m_RecentlyPlayed);
            PrintToConsole(client, "m_RefundRatio    : %f", mapdata.m_RefundRatio);
        }
    }
}


Add missing key code.

Add missing key code.

//(C) 2020 rrrfffrrr <rrrfffrrr@naver.com>

#pragma semicolon 1
#pragma newdecls required

public Plugin myinfo = {
	name		= "[INS] RespawnBots",
	author		= "rrrfffrrr",
	description	= "Handling bot respawn system for coop",
	version		= "2.1.0",
	url			= ""
};

#include <sourcemod>
#include <sdktools>
#include <timers>
//#include <counterattack>
#define TEAM_SPECTATE 1
#define TEAM_SECURITY 2
#define TEAM_INSURGENT 3

ConVar CvarRTime;
ConVar CvarDisplay;
ConVar CvarDisplayDelay;
ConVar CvarDefaultPoint;
ConVar CvarAddPoint;
ConVar CvarDefaultPointCA;
ConVar CvarAddPointCA;
ConVar CvarInfiniteOnCA;
ConVar CvarOnlyAliveCA;
ConVar CvarEnableAdjustSpawnLocation;
ConVar CvarAdjustDistance;

Handle fToggleSpawnZone;
Handle fPointInSpawnZone;
Handle fGetBaseEntity;
Handle fForceRespawn;
Handle cGameConfig;

int iBotRespawnRemain = 0;

Handle tRespawnDelay[MAXPLAYERS+1];
Handle tDisplay;

Handle tOnRespawnFrame;

StringMap hSpawnZone;
StringMapSnapshot hSpawnZoneKeys;

/// Counter attack forward.
/// If you don't use CustomCounterAttack, delete #include <counterattack> in line 17 to disable it (default: disabled)
#if defined _ins_counterattack
public void OnCounterAttackFinished(bool isCustom) {
	ResetRespawnCount();
}
#endif

public void OnPluginStart()
{
	InitSDKCalls();
	InitCvars();
	HookCvars();
	HookEvents();

	UpdateDisplayTimer();
	UpdateRespawnTimer();

	hSpawnZone = new StringMap();
	hSpawnZoneKeys = hSpawnZone.Snapshot();
	BuildSpawnZoneList();
}

public void OnMapStart() {
	tDisplay = INVALID_HANDLE;
	UpdateDisplayTimer();

	tOnRespawnFrame = INVALID_HANDLE;
	UpdateRespawnTimer();

	for(int i = 1; i <= MaxClients; ++i) {
		tRespawnDelay[i] = INVALID_HANDLE;
	}
	BuildSpawnZoneList();
}

void InitSDKCalls() {
	cGameConfig = LoadGameConfigFile("insurgency.games");
	if (cGameConfig == INVALID_HANDLE) {
		SetFailState("Fatal Error: Missing File \"insurgency.games\"!");
	}

	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Virtual, "CINSPlayer::ForceRespawn");
	fForceRespawn = EndPrepSDKCall();
	if (fForceRespawn == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSPlayer::ForceRespawn");
	}

	StartPrepSDKCall(SDKCall_Static);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Signature, "CINSSpawnZone::PointInSpawnZone");
	PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
	PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer, 0, VENCODE_FLAG_COPYBACK);
	fPointInSpawnZone = EndPrepSDKCall();
	if (fPointInSpawnZone == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSSpawnZone::PointInSpawnZone");
	}
	StartPrepSDKCall(SDKCall_Raw);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Virtual, "CINSSpawnZone::GetBaseEntity");
	PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Plain);
	fGetBaseEntity = EndPrepSDKCall();
	if (fGetBaseEntity == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSSpawnZone::GetBaseEntity");
	}

	StartPrepSDKCall(SDKCall_GameRules);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Signature, "CINSRules::ToggleSpawnZone");
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
	fToggleSpawnZone = EndPrepSDKCall();
	if (fToggleSpawnZone == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSRules::ToggleSpawnZone");
	}
}

void InitCvars() {
	CvarRTime = CreateConVar("sm_botrespawn_delay", "1", "How many seconds wait before spawn for insurgent, 0 is disable delay", FCVAR_NOTIFY);

	CvarDefaultPoint = CreateConVar("sm_botrespawn_rp", "5", "Default bots count, 0 is infinity", FCVAR_NOTIFY);
	CvarAddPoint = CreateConVar("sm_botrespawn_rp_add", "5", "Number of bots that each player (rp + players * rp_add)", FCVAR_NOTIFY);

	CvarDefaultPointCA = CreateConVar("sm_botrespawn_carp", "5", "Default bots count when counter attack", FCVAR_NOTIFY);
	CvarAddPointCA = CreateConVar("sm_botrespawn_carp_add", "5", "Number of bots that each player when counter attack (carp + players * carp_add)", FCVAR_NOTIFY);
	CvarInfiniteOnCA = CreateConVar("sm_botrespawn_infinite_ca", "1", "Infinity spawn when counter attack", FCVAR_NOTIFY);

	CvarDisplay = CreateConVar("sm_botrespawn_display", "1", "Display respawn point(0 = don't display, 1 = display)", FCVAR_NOTIFY);
	CvarDisplayDelay = CreateConVar("sm_botrespawn_display_delay", "1", "Display delay(sec)", FCVAR_NOTIFY);

	CvarOnlyAliveCA = CreateConVar("sm_botrespawn_count_alive", "1", "Count only alive players when counter attack.", FCVAR_NOTIFY);

	CvarEnableAdjustSpawnLocation = CreateConVar("sm_botrespawn_adjust", "1", "Adjust spawn location to where player can't see.", FCVAR_NOTIFY);
	CvarAdjustDistance = CreateConVar("sm_botrespawn_adjust_distance", "100", "Adjust spawn location when someone close enough (Include teams)", FCVAR_NOTIFY, true, 50.0);
}

void HookCvars() {
	HookConVarChange(CvarDisplayDelay, UpdateCvarDisplay);
}

void HookEvents() {
	HookEvent("player_spawn", Event_PlayerSpawn);
	HookEvent("player_death", Event_PlayerDeath);
	HookEvent("round_start", Event_BeginRound);
	HookEvent("round_begin", Event_StartRound);
	HookEvent("controlpoint_captured", Event_Captured);
	HookEvent("object_destroyed", Event_Captured);
}

/// Display
void UpdateCvarDisplay(Handle cvar, const char[] oldValue, const char[] newValue) {
	UpdateDisplayTimer();
}

void UpdateDisplayTimer() {
	if (tDisplay != INVALID_HANDLE) {
		KillTimer(tDisplay);
	}

	tDisplay = CreateTimer(CvarDisplayDelay.FloatValue, Timer_DisplayCount,_, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}

Action Timer_DisplayCount(Handle timer) {
	if (CvarDisplay.BoolValue != true) {
		return Plugin_Continue;
	}

	char hint[64];
	if (IsCounterAttack() && CvarInfiniteOnCA.BoolValue)
		Format(hint, sizeof(hint), "Infinite respawn");
	else
		Format(hint, sizeof(hint), "남은 Insurgent : %i", ((iBotRespawnRemain > 0) ? iBotRespawnRemain : 0) + GetFakeClientCount());

	for(int i = 1; i <= MaxClients; i++) {
		if (IsValidPlayer(i) && !IsFakeClient(i)) {
			PrintHintText(i, "%s", hint);
		}
	}
	return Plugin_Continue;
}

/// Respawn tick
void UpdateRespawnTimer() {
	if (tOnRespawnFrame == INVALID_HANDLE) {
		tOnRespawnFrame = CreateTimer(1.0, Timer_RespawnTick,_, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
	}
}

Action Timer_RespawnTick(Handle timer) {
	bool IsInfiniteRespawn = (IsCounterAttack() && CvarInfiniteOnCA.BoolValue);
	if (!IsInfiniteRespawn && iBotRespawnRemain <= 0)
		return Plugin_Continue;

	for(int i = 1; i <= MaxClients; ++i) {
		if (IsValidPlayer(i) && IsFakeClient(i) && !IsPlayerAlive(i) && tRespawnDelay[i] == INVALID_HANDLE) {
			if (RespawnPlayer(i)) {
				iBotRespawnRemain -= (IsInfiniteRespawn ? 0 : 1);
				break;
			}
		}
	}

	return Plugin_Continue;
}

Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	if (tRespawnDelay[client] != INVALID_HANDLE) {
		KillTimer(tRespawnDelay[client]);
		tRespawnDelay[client] = INVALID_HANDLE;
	}
	return Plugin_Continue;
}

void ResetRespawnCount() {
	if (IsCounterAttack()) {
		iBotRespawnRemain = CalcRespawnCount(CvarDefaultPointCA, CvarAddPointCA, GetRealClientCount(true, CvarOnlyAliveCA.BoolValue));
	} else {
		iBotRespawnRemain = CalcRespawnCount(CvarDefaultPoint, CvarAddPoint, GetRealClientCount());
	}
}

int CalcRespawnCount(ConVar def, ConVar add, int player) {
	return def.IntValue + player * add.IntValue;
}

Action Event_BeginRound(Event event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

Action Event_StartRound(Event event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

Action Event_Captured(Handle event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

/// Death handler
Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	if (!IsValidPlayer(client) || !IsFakeClient(client)) {
		return Plugin_Continue;
	}

	bool isCA = IsCounterAttack();
	bool enableRespawn = (((isCA) ? CvarDefaultPointCA.IntValue : CvarDefaultPoint.IntValue) != 0);	// idk why this code exists... should i delete it?
	if (enableRespawn)
		return Plugin_Continue;

	float delay = CvarRTime.FloatValue;
	if (delay > 0.0) {
		tRespawnDelay[client] = CreateTimer(delay, Timer_RespawnDelay, client, TIMER_FLAG_NO_MAPCHANGE);
	}

	return Plugin_Continue;
}

Action Timer_RespawnDelay(Handle timer, int client) {
	tRespawnDelay[client] = INVALID_HANDLE;
	return Plugin_Continue;
}

/// Utils
bool RespawnPlayer(int client) {
	if (IsValidPlayer(client) && !IsPlayerAlive(client) && GetClientTeam(client) != TEAM_SPECTATE) {
		SDKCall(fForceRespawn, client);
		FixSpawnPoint(client);
		return true;
	}

	return false;
}

bool IsValidPlayer(int client) {
	if (client < 1 || client > MaxClients)
		return false;

	if (!IsClientInGame(client))
		return false;

	return true;
}

int GetRealClientCount(bool inGameOnly = true, bool onlyAlivePlayer = true) {
	int clients = 0;
	for(int i = 1; i <= MaxClients; i++) {
		if (((inGameOnly) ? IsClientInGame(i) : IsClientConnected(i)) && !IsFakeClient(i) && GetClientTeam(i) != TEAM_SPECTATE && ((onlyAlivePlayer) ? IsPlayerAlive(i) : true)) {
			clients++;
		}
	}
	return clients;
}

int GetFakeClientCount(bool inGameOnly = true, bool onlyAlivePlayer = true) {
	int clients = 0;
	for(int i = 1; i <= MaxClients; i++) {
		if (((inGameOnly) ? IsClientInGame(i) : IsClientConnected(i)) && IsFakeClient(i) && ((onlyAlivePlayer) ? IsPlayerAlive(i) : true)) {
			clients++;
		}
	}
	return clients;
}

bool IsCounterAttack() {
	return view_as<bool>(GameRules_GetProp("m_bCounterAttack"));
}

int GetCurrentCPIndex() {
	int res = FindEntityByClassname(-1, "ins_objective_resource");
	if (res == -1)
		return -1;

	int CP = GetEntProp(res, Prop_Send, "m_nActivePushPointIndex");

	return CP;
}

/// Fix spawn location
// This is the main feature of RespawnBots 2.0
void FixSpawnPoint(int client) {
	if (!CvarEnableAdjustSpawnLocation.BoolValue)
		return;

	float pos[3];
	GetClientAbsOrigin(client, pos);
	if (!EnemyInSightOrClose(client, pos))
		return;

	int cp = GetCurrentCPIndex();
	int team = GetClientTeam(client);
	char key[4];
	ArrayList list;
	Format(key, sizeof(key), "%s%d", ((team == TEAM_SECURITY) ? "S" : "I"), cp);
	if (hSpawnZone.GetValue(key, list)) {
		for(int i = 0; i < list.Length; ++i) {
			GetEntPropVector(list.Get(i), Prop_Send, "m_vecOrigin", pos);
			if (!EnemyInSightOrClose(client, pos)) {
				TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
				return;
			}
		}
	}

	Format(key, sizeof(key), "%s%d", ((team == TEAM_SECURITY) ? "S" : "I"), cp - 2);
	if (hSpawnZone.GetValue(key, list)) {
		for(int i = 0; i < list.Length; ++i) {
			GetEntPropVector(list.Get(i), Prop_Send, "m_vecOrigin", pos);
			if (!EnemyInSightOrClose(client, pos)) {
				TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
				return;
			}
		}
	}
}

bool EnemyInSightOrClose(int client, float pos[3]) {
	int team = GetClientTeam(client);
	int eteam = (team == TEAM_SECURITY) ? TEAM_INSURGENT : TEAM_SECURITY;
	float minDist = Pow(CvarAdjustDistance.FloatValue, 2.0);
	float org[3];
	for(int i = 1; i <= MaxClients; ++i) {
		if (i != client && IsClientInGame(i) && IsPlayerAlive(i)) {
			GetClientEyePosition(i, org);
			if (GetVectorDistance(pos, org, true) <= minDist) {
				return true;
			}

			if (GetClientTeam(i) == eteam) {
				if (NothingBetweenClient(client, i, pos, org)) {
					return true;
				}
			}
		}
	}

	return false;
}

bool NothingBetweenClient(int client1, int client2, float c1Vec[3], float c2Vec[3]) {
	Handle tr = TR_TraceRayFilterEx(c1Vec, c2Vec, MASK_PLAYERSOLID, RayType_EndPoint, Filter_Caller, client1);
	if (TR_DidHit(tr)) {
		if (TR_GetEntityIndex(tr) == client2) {
			CloseHandle(tr);
			return true;
		}

		CloseHandle(tr);
		return false;
	}
	CloseHandle(tr);
	return true;
}

bool Filter_Caller(int entity, int contentsMask, int client) {
	if (entity == client) {
		return false;
	}

	return true;
}

int FindSpawnZone(int spawnpoint) {
	Address pSpawnZone = Address_Null;
	float absOrigin[3];
	GetEntPropVector(spawnpoint, Prop_Data, "m_vecAbsOrigin", absOrigin);
	SDKCall(fPointInSpawnZone, absOrigin, spawnpoint, pSpawnZone);
	if (pSpawnZone == Address_Null) {
		return -1;
	}
	return SDKCall(fGetBaseEntity, pSpawnZone);
}

void BuildSpawnZoneList() {
	ArrayList listIns;
	ArrayList listSec;
	char key[4];
	for(int i = 0; i < hSpawnZoneKeys.Length; ++i) {
		hSpawnZoneKeys.GetKey(i, key, sizeof(key));
		if (hSpawnZone.GetValue(key, listIns)) {
			delete listIns;
		}
	}
	hSpawnZone.Clear();

	int objective = FindEntityByClassname(-1, "ins_objective_resource");
	if (objective == -1)
		return;
	int numOfSpawnZone = GetEntProp(objective, Prop_Send, "m_iNumControlPoints");
	for(int i = 0; i <= numOfSpawnZone; ++i) {
		SDKCall(fToggleSpawnZone, i, false);
	}

	int point = -1;
	int zone = -1;
	int team = 1;
	for(int i = 0; i <= numOfSpawnZone; ++i) {
		SDKCall(fToggleSpawnZone, i, true);

		listIns = new ArrayList();
		listSec = new ArrayList();
		point = FindEntityByClassname(-1, "ins_spawnpoint");
		while(point != -1) {
			zone = FindSpawnZone(point);
			if (zone != -1) {
				team = GetEntProp(point, Prop_Send, "m_iTeamNum");
				float pos[3];
				GetEntPropVector(point, Prop_Send, "m_vecOrigin", pos);
				if (team == TEAM_SECURITY)
					listSec.Push(point);
				else if (team == TEAM_INSURGENT)
					listIns.Push(point);
			}
			point = FindEntityByClassname(point, "ins_spawnpoint");
		}
		Format(key, sizeof(key), "I%d", i);
		hSpawnZone.SetValue(key, listIns, true);
		Format(key, sizeof(key), "S%d", i);
		hSpawnZone.SetValue(key, listSec, true);

		SDKCall(fToggleSpawnZone, i, false);
	}

	hSpawnZoneKeys = hSpawnZone.Snapshot();
	SDKCall(fToggleSpawnZone, GetCurrentCPIndex(), true);
}

//(C) 2020 rrrfffrrr <rrrfffrrr@naver.com>

#pragma semicolon 1
#pragma newdecls required

public Plugin myinfo = {
	name		= "[INS] RespawnBots",
	author		= "rrrfffrrr",
	description	= "Handling bot respawn system for coop",
	version		= "2.1.0",
	url			= ""
};

#include <sourcemod>
#include <sdktools>
#include <timers>
//#include <counterattack>
#define TEAM_SPECTATE 1
#define TEAM_SECURITY 2
#define TEAM_INSURGENT 3

ConVar CvarRTime;
ConVar CvarDisplay;
ConVar CvarDisplayDelay;
ConVar CvarDefaultPoint;
ConVar CvarAddPoint;
ConVar CvarDefaultPointCA;
ConVar CvarAddPointCA;
ConVar CvarInfiniteOnCA;
ConVar CvarOnlyAliveCA;
ConVar CvarEnableAdjustSpawnLocation;
ConVar CvarAdjustDistance;

Handle fToggleSpawnZone;
Handle fPointInSpawnZone;
Handle fGetBaseEntity;
Handle fForceRespawn;
Handle cGameConfig;

int iBotRespawnRemain = 0;

Handle tRespawnDelay[MAXPLAYERS+1];
Handle tDisplay;

Handle tOnRespawnFrame;

StringMap hSpawnZone;
StringMapSnapshot hSpawnZoneKeys;

/// Counter attack forward.
/// If you don't use CustomCounterAttack, delete #include <counterattack> in line 17 to disable it (default: disabled)
#if defined _ins_counterattack
public void OnCounterAttackFinished(bool isCustom) {
	ResetRespawnCount();
}
#endif

public void OnPluginStart()
{
	InitSDKCalls();
	InitCvars();
	HookCvars();
	HookEvents();

	UpdateDisplayTimer();
	UpdateRespawnTimer();

	hSpawnZone = new StringMap();
	hSpawnZoneKeys = hSpawnZone.Snapshot();
	BuildSpawnZoneList();
}

public void OnMapStart() {
	tDisplay = INVALID_HANDLE;
	UpdateDisplayTimer();

	tOnRespawnFrame = INVALID_HANDLE;
	UpdateRespawnTimer();

	for(int i = 1; i <= MaxClients; ++i) {
		tRespawnDelay[i] = INVALID_HANDLE;
	}
	BuildSpawnZoneList();
}

void InitSDKCalls() {
	cGameConfig = LoadGameConfigFile("insurgency.games");
	if (cGameConfig == INVALID_HANDLE) {
		SetFailState("Fatal Error: Missing File \"insurgency.games\"!");
	}

	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Virtual, "CINSPlayer::ForceRespawn");
	fForceRespawn = EndPrepSDKCall();
	if (fForceRespawn == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSPlayer::ForceRespawn");
	}

	StartPrepSDKCall(SDKCall_Static);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Signature, "CINSSpawnZone::PointInSpawnZone");
	PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
	PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer, 0, VENCODE_FLAG_COPYBACK);
	fPointInSpawnZone = EndPrepSDKCall();
	if (fPointInSpawnZone == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSSpawnZone::PointInSpawnZone");
	}
	StartPrepSDKCall(SDKCall_Raw);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Virtual, "CINSSpawnZone::GetBaseEntity");
	PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Plain);
	fGetBaseEntity = EndPrepSDKCall();
	if (fGetBaseEntity == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSSpawnZone::GetBaseEntity");
	}

	StartPrepSDKCall(SDKCall_GameRules);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Signature, "CINSRules::ToggleSpawnZone");
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
	fToggleSpawnZone = EndPrepSDKCall();
	if (fToggleSpawnZone == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSRules::ToggleSpawnZone");
	}
}

void InitCvars() {
	CvarRTime = CreateConVar("sm_botrespawn_delay", "1", "How many seconds wait before spawn for insurgent, 0 is disable delay", FCVAR_NOTIFY);

	CvarDefaultPoint = CreateConVar("sm_botrespawn_rp", "5", "Default bots count, 0 is infinity", FCVAR_NOTIFY);
	CvarAddPoint = CreateConVar("sm_botrespawn_rp_add", "5", "Number of bots that each player (rp + players * rp_add)", FCVAR_NOTIFY);

	CvarDefaultPointCA = CreateConVar("sm_botrespawn_carp", "5", "Default bots count when counter attack", FCVAR_NOTIFY);
	CvarAddPointCA = CreateConVar("sm_botrespawn_carp_add", "5", "Number of bots that each player when counter attack (carp + players * carp_add)", FCVAR_NOTIFY);
	CvarInfiniteOnCA = CreateConVar("sm_botrespawn_infinite_ca", "1", "Infinity spawn when counter attack", FCVAR_NOTIFY);

	CvarDisplay = CreateConVar("sm_botrespawn_display", "1", "Display respawn point(0 = don't display, 1 = display)", FCVAR_NOTIFY);
	CvarDisplayDelay = CreateConVar("sm_botrespawn_display_delay", "1", "Display delay(sec)", FCVAR_NOTIFY);

	CvarOnlyAliveCA = CreateConVar("sm_botrespawn_count_alive", "1", "Count only alive players when counter attack.", FCVAR_NOTIFY);

	CvarEnableAdjustSpawnLocation = CreateConVar("sm_botrespawn_adjust", "1", "Adjust spawn location to where player can't see.", FCVAR_NOTIFY);
	CvarAdjustDistance = CreateConVar("sm_botrespawn_adjust_distance", "100", "Adjust spawn location when someone close enough (Include teams)", FCVAR_NOTIFY, true, 50.0);
}

void HookCvars() {
	HookConVarChange(CvarDisplayDelay, UpdateCvarDisplay);
}

void HookEvents() {
	HookEvent("player_spawn", Event_PlayerSpawn);
	HookEvent("player_death", Event_PlayerDeath);
	HookEvent("round_start", Event_BeginRound);
	HookEvent("round_begin", Event_StartRound);
	HookEvent("controlpoint_captured", Event_Captured);
	HookEvent("object_destroyed", Event_Captured);
}

/// Display
void UpdateCvarDisplay(Handle cvar, const char[] oldValue, const char[] newValue) {
	UpdateDisplayTimer();
}

void UpdateDisplayTimer() {
	if (tDisplay != INVALID_HANDLE) {
		KillTimer(tDisplay);
	}

	tDisplay = CreateTimer(CvarDisplayDelay.FloatValue, Timer_DisplayCount,_, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}

Action Timer_DisplayCount(Handle timer) {
	if (CvarDisplay.BoolValue != true) {
		return Plugin_Continue;
	}

	char hint[64];
	if (IsCounterAttack() && CvarInfiniteOnCA.BoolValue)
		Format(hint, sizeof(hint), "Infinite respawn");
	else
		Format(hint, sizeof(hint), "남은 Insurgent : %i", ((iBotRespawnRemain > 0) ? iBotRespawnRemain : 0) + GetFakeClientCount());

	for(int i = 1; i <= MaxClients; i++) {
		if (IsValidPlayer(i) && !IsFakeClient(i)) {
			PrintHintText(i, "%s", hint);
		}
	}
	return Plugin_Continue;
}

/// Respawn tick
void UpdateRespawnTimer() {
	if (tOnRespawnFrame == INVALID_HANDLE) {
		tOnRespawnFrame = CreateTimer(1.0, Timer_RespawnTick,_, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
	}
}

Action Timer_RespawnTick(Handle timer) {
	bool IsInfiniteRespawn = (IsCounterAttack() && CvarInfiniteOnCA.BoolValue);
	if (!IsInfiniteRespawn && iBotRespawnRemain <= 0)
		return Plugin_Continue;

	for(int i = 1; i <= MaxClients; ++i) {
		if (IsValidPlayer(i) && IsFakeClient(i) && !IsPlayerAlive(i) && tRespawnDelay[i] == INVALID_HANDLE) {
			if (RespawnPlayer(i)) {
				iBotRespawnRemain -= (IsInfiniteRespawn ? 0 : 1);
				break;
			}
		}
	}

	return Plugin_Continue;
}

Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	if (tRespawnDelay[client] != INVALID_HANDLE) {
		KillTimer(tRespawnDelay[client]);
		tRespawnDelay[client] = INVALID_HANDLE;
	}
	return Plugin_Continue;
}

void ResetRespawnCount() {
	if (IsCounterAttack()) {
		iBotRespawnRemain = CalcRespawnCount(CvarDefaultPointCA, CvarAddPointCA, GetRealClientCount(true, CvarOnlyAliveCA.BoolValue));
	} else {
		iBotRespawnRemain = CalcRespawnCount(CvarDefaultPoint, CvarAddPoint, GetRealClientCount());
	}
}

int CalcRespawnCount(ConVar def, ConVar add, int player) {
	return def.IntValue + player * add.IntValue;
}

Action Event_BeginRound(Event event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

Action Event_StartRound(Event event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

Action Event_Captured(Handle event, const char[] name, bool dontBroadcast) {
	ResetRespawnCount();
	return Plugin_Continue;
}

/// Death handler
Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	if (!IsValidPlayer(client) || !IsFakeClient(client)) {
		return Plugin_Continue;
	}

	bool isCA = IsCounterAttack();
	bool enableRespawn = (((isCA) ? CvarDefaultPointCA.IntValue : CvarDefaultPoint.IntValue) != 0);	// idk why this code exists... should i delete it?
	if (enableRespawn)
		return Plugin_Continue;

	float delay = CvarRTime.FloatValue;
	if (delay > 0.0) {
		tRespawnDelay[client] = CreateTimer(delay, Timer_RespawnDelay, client, TIMER_FLAG_NO_MAPCHANGE);
	}

	return Plugin_Continue;
}

Action Timer_RespawnDelay(Handle timer, int client) {
	tRespawnDelay[client] = INVALID_HANDLE;
	return Plugin_Continue;
}

/// Utils
bool RespawnPlayer(int client) {
	if (IsValidPlayer(client) && !IsPlayerAlive(client) && GetClientTeam(client) != TEAM_SPECTATE) {
		SDKCall(fForceRespawn, client);
		FixSpawnPoint(client);
		return true;
	}

	return false;
}

bool IsValidPlayer(int client) {
	if (client < 1 || client > MaxClients)
		return false;

	if (!IsClientInGame(client))
		return false;

	return true;
}

int GetRealClientCount(bool inGameOnly = true, bool onlyAlivePlayer = true) {
	int clients = 0;
	for(int i = 1; i <= MaxClients; i++) {
		if (((inGameOnly) ? IsClientInGame(i) : IsClientConnected(i)) && !IsFakeClient(i) && GetClientTeam(i) != TEAM_SPECTATE && ((onlyAlivePlayer) ? IsPlayerAlive(i) : true)) {
			clients++;
		}
	}
	return clients;
}

int GetFakeClientCount(bool inGameOnly = true, bool onlyAlivePlayer = true) {
	int clients = 0;
	for(int i = 1; i <= MaxClients; i++) {
		if (((inGameOnly) ? IsClientInGame(i) : IsClientConnected(i)) && IsFakeClient(i) && ((onlyAlivePlayer) ? IsPlayerAlive(i) : true)) {
			clients++;
		}
	}
	return clients;
}

bool IsCounterAttack() {
	return view_as<bool>(GameRules_GetProp("m_bCounterAttack"));
}

int GetCurrentCPIndex() {
	int res = FindEntityByClassname(-1, "ins_objective_resource");
	if (res == -1)
		return -1;

	int CP = GetEntProp(res, Prop_Send, "m_nActivePushPointIndex");

	return CP;
}

/// Fix spawn location
// This is the main feature of RespawnBots 2.0
void FixSpawnPoint(int client) {
	if (!CvarEnableAdjustSpawnLocation.BoolValue)
		return;

	float pos[3];
	GetClientAbsOrigin(client, pos);
	if (!EnemyInSightOrClose(client, pos))
		return;

	int cp = GetCurrentCPIndex();
	int team = GetClientTeam(client);
	char key[4];
	ArrayList list;
	if (hSpawnZone.GetValue(key, list)) {
		for(int i = 0; i < list.Length; ++i) {
			GetEntPropVector(list.Get(i), Prop_Send, "m_vecOrigin", pos);
			if (!EnemyInSightOrClose(client, pos)) {
				TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
				return;
			}
		}
	}

	Format(key, sizeof(key), "%s%d", ((team == TEAM_SECURITY) ? "S" : "I"), cp - 2);
	if (hSpawnZone.GetValue(key, list)) {
		for(int i = 0; i < list.Length; ++i) {
			GetEntPropVector(list.Get(i), Prop_Send, "m_vecOrigin", pos);
			if (!EnemyInSightOrClose(client, pos)) {
				TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
				return;
			}
		}
	}
}

bool EnemyInSightOrClose(int client, float pos[3]) {
	int team = GetClientTeam(client);
	int eteam = (team == TEAM_SECURITY) ? TEAM_INSURGENT : TEAM_SECURITY;
	float minDist = Pow(CvarAdjustDistance.FloatValue, 2.0);
	float org[3];
	for(int i = 1; i <= MaxClients; ++i) {
		if (i != client && IsClientInGame(i) && IsPlayerAlive(i)) {
			GetClientEyePosition(i, org);
			if (GetVectorDistance(pos, org, true) <= minDist) {
				return true;
			}

			if (GetClientTeam(i) == eteam) {
				if (NothingBetweenClient(client, i, pos, org)) {
					return true;
				}
			}
		}
	}

	return false;
}

bool NothingBetweenClient(int client1, int client2, float c1Vec[3], float c2Vec[3]) {
	Handle tr = TR_TraceRayFilterEx(c1Vec, c2Vec, MASK_PLAYERSOLID, RayType_EndPoint, Filter_Caller, client1);
	if (TR_DidHit(tr)) {
		if (TR_GetEntityIndex(tr) == client2) {
			CloseHandle(tr);
			return true;
		}

		CloseHandle(tr);
		return false;
	}
	CloseHandle(tr);
	return true;
}

bool Filter_Caller(int entity, int contentsMask, int client) {
	if (entity == client) {
		return false;
	}

	return true;
}

int FindSpawnZone(int spawnpoint) {
	Address pSpawnZone = Address_Null;
	float absOrigin[3];
	GetEntPropVector(spawnpoint, Prop_Data, "m_vecAbsOrigin", absOrigin);
	SDKCall(fPointInSpawnZone, absOrigin, spawnpoint, pSpawnZone);
	if (pSpawnZone == Address_Null) {
		return -1;
	}
	return SDKCall(fGetBaseEntity, pSpawnZone);
}

void BuildSpawnZoneList() {
	ArrayList listIns;
	ArrayList listSec;
	char key[4];
	for(int i = 0; i < hSpawnZoneKeys.Length; ++i) {
		hSpawnZoneKeys.GetKey(i, key, sizeof(key));
		if (hSpawnZone.GetValue(key, listIns)) {
			delete listIns;
		}
	}
	hSpawnZone.Clear();

	int objective = FindEntityByClassname(-1, "ins_objective_resource");
	if (objective == -1)
		return;
	int numOfSpawnZone = GetEntProp(objective, Prop_Send, "m_iNumControlPoints");
	for(int i = 0; i <= numOfSpawnZone; ++i) {
		SDKCall(fToggleSpawnZone, i, false);
	}

	int point = -1;
	int zone = -1;
	int team = 1;
	for(int i = 0; i <= numOfSpawnZone; ++i) {
		SDKCall(fToggleSpawnZone, i, true);

		listIns = new ArrayList();
		listSec = new ArrayList();
		point = FindEntityByClassname(-1, "ins_spawnpoint");
		while(point != -1) {
			zone = FindSpawnZone(point);
			if (zone != -1) {
				team = GetEntProp(point, Prop_Send, "m_iTeamNum");
				float pos[3];
				GetEntPropVector(point, Prop_Send, "m_vecOrigin", pos);
				if (team == TEAM_SECURITY)
					listSec.Push(point);
				else if (team == TEAM_INSURGENT)
					listIns.Push(point);
			}
			point = FindEntityByClassname(point, "ins_spawnpoint");
		}
		Format(key, sizeof(key), "I%d", i);
		hSpawnZone.SetValue(key, listIns, true);
		Format(key, sizeof(key), "S%d", i);
		hSpawnZone.SetValue(key, listSec, true);

		SDKCall(fToggleSpawnZone, i, false);
	}

	hSpawnZoneKeys = hSpawnZone.Snapshot();
	SDKCall(fToggleSpawnZone, GetCurrentCPIndex(), true);
}


Update sbpp_sleuth.sp

Update sbpp_sleuth.sp

Add Sleuth Action to kick clients instead instead of banning them with a translation message as the reason

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2019 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebanspp>

#define PLUGIN_VERSION "1.7.0"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4
#define LENGTH_KICK 5

#define PREFIX "[SourceSleuth] "

//- Handles -//
Database hDatabase = null;
ArrayList g_hAllowedArray = null;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;
ConVar g_cVar_excludeOld;
ConVar g_cVar_excludeTime;

//- Bools -//
bool CanUseSourcebans = false;

public Plugin myinfo =
{
	name = "SourceBans++: SourceSleuth",
	author = "ecca, SourceBans++ Dev Team",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	LoadTranslations("sbpp_sleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only, 5 - Kick Client", 0, true, 1.0, true, 5.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )", 0);
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb", 0);
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act", 0);
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", 0, true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", 0, true, 0.0, true, 1.0);
	g_cVar_excludeOld = CreateConVar("sm_sleuth_excludeold", "0", "0 - Inactivated, 1 - Allow old bans to be excluded from ban check", 0, true, 0.0, true, 1.0);
	g_cVar_excludeTime = CreateConVar("sm_sleuth_excludetime", "31536000", "Amount of time in seconds to allow old bans to be excluded from ban check", 0, true, 1.0, false);

	g_hAllowedArray = new ArrayList(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	Database.Connect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public void OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans++");
}

public void OnLibraryAdded(const char[] name)
{
	if (StrEqual("sourcebans++", name))
	{
		CanUseSourcebans = true;
	}
}

public void OnLibraryRemoved(const char[] name)
{
	if (StrEqual("sourcebans++", name))
	{
		CanUseSourcebans = false;
	}
}

public void SQL_OnConnect(Database db, const char[] error, any data)
{
	if (db == null)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = db;
	}
}

public Action ReloadListCallBack(int client, int args)
{
	g_hAllowedArray.Clear();

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "%sWhiteList has been reloaded!", PREFIX);
	}

	return Plugin_Continue;
}

public void OnClientPostAdminCheck(int client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		char steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (g_hAllowedArray.FindString(steamid) == -1)
		{
			char IP[32], Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			char query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND (ends > %d OR ((1 = %d AND length = 0 AND ends > %d) OR (0 = %d AND length = 0)))", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0, g_cVar_excludeOld.IntValue, GetTime() - g_cVar_excludeTime.IntValue, g_cVar_excludeOld.IntValue);

			DataPack datapack = new DataPack();

			datapack.WriteCell(GetClientUserId(client));
			datapack.WriteString(steamid);
			datapack.WriteString(IP);
			datapack.Reset();

			hDatabase.Query(SQL_CheckHim, query, datapack);
		}
	}
}

public void SQL_CheckHim(Database db, DBResultSet results, const char[] error, DataPack dataPack)
{
	int client;
	char steamid[32], IP[32];

	client = GetClientOfUserId(ReadPackCell(dataPack));
	dataPack.ReadString(steamid, sizeof(steamid));
	dataPack.ReadString(IP, sizeof(IP));
	delete dataPack;

	if (results == null)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (results.FetchRow())
	{
		int TotalBans = results.RowCount;

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					int length = results.FetchInt(6);
					int time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					int time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					int length = results.FetchInt(6);

					int time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("%s%t", PREFIX, "sourcesleuth_admintext", client, steamid, IP);
				}
				case LENGTH_KICK:
				{
					KickClient(client, "%s%t", PREFIX, "sourcesleuth_kicktext");
				}
			}
		}
	}
}

stock void BanPlayer(int client, int time)
{
	char Reason[255];
	Format(Reason, sizeof(Reason), "%s%T", PREFIX, "sourcesleuth_banreason", client);
	SBPP_BanPlayer(0, client, time, Reason);
}

void PrintToAdmins(const char[] format, any ...)
{
	char g_Buffer[256];

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN))
		{
			SetGlobalTransTarget(i);

			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public void LoadWhiteList()
{
	char path[PLATFORM_MAX_PATH], line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcebans/sourcesleuth_whitelist.cfg");

	File fileHandle = OpenFile(path, "r");

	if (fileHandle == null)
	{
		LogError("Could not find the config file (%s)", path);

		return;
	}

	while (!fileHandle.EndOfFile() && fileHandle.ReadLine(line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		g_hAllowedArray.PushString(line);
	}

	delete fileHandle;
}

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2019 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebanspp>

#define PLUGIN_VERSION "1.7.0"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4

#define PREFIX "[SourceSleuth] "

//- Handles -//
Database hDatabase = null;
ArrayList g_hAllowedArray = null;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;
ConVar g_cVar_excludeOld;
ConVar g_cVar_excludeTime;

//- Bools -//
bool CanUseSourcebans = false;

public Plugin myinfo =
{
	name = "SourceBans++: SourceSleuth",
	author = "ecca, SourceBans++ Dev Team",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	LoadTranslations("sbpp_sleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only", 0, true, 1.0, true, 4.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )", 0);
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb", 0);
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act", 0);
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", 0, true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", 0, true, 0.0, true, 1.0);
	g_cVar_excludeOld = CreateConVar("sm_sleuth_excludeold", "0", "0 - Inactivated, 1 - Allow old bans to be excluded from ban check", 0, true, 0.0, true, 1.0);
	g_cVar_excludeTime = CreateConVar("sm_sleuth_excludetime", "31536000", "Amount of time in seconds to allow old bans to be excluded from ban check", 0, true, 1.0, false);

	g_hAllowedArray = new ArrayList(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	Database.Connect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public void OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans++");
}

public void OnLibraryAdded(const char[] name)
{
	if (StrEqual("sourcebans++", name))
	{
		CanUseSourcebans = true;
	}
}

public void OnLibraryRemoved(const char[] name)
{
	if (StrEqual("sourcebans++", name))
	{
		CanUseSourcebans = false;
	}
}

public void SQL_OnConnect(Database db, const char[] error, any data)
{
	if (db == null)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = db;
	}
}

public Action ReloadListCallBack(int client, int args)
{
	g_hAllowedArray.Clear();

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "%sWhiteList has been reloaded!", PREFIX);
	}

	return Plugin_Continue;
}

public void OnClientPostAdminCheck(int client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		char steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (g_hAllowedArray.FindString(steamid) == -1)
		{
			char IP[32], Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			char query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND (ends > %d OR ((1 = %d AND length = 0 AND ends > %d) OR (0 = %d AND length = 0)))", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0, g_cVar_excludeOld.IntValue, GetTime() - g_cVar_excludeTime.IntValue, g_cVar_excludeOld.IntValue);

			DataPack datapack = new DataPack();

			datapack.WriteCell(GetClientUserId(client));
			datapack.WriteString(steamid);
			datapack.WriteString(IP);
			datapack.Reset();

			hDatabase.Query(SQL_CheckHim, query, datapack);
		}
	}
}

public void SQL_CheckHim(Database db, DBResultSet results, const char[] error, DataPack dataPack)
{
	int client;
	char steamid[32], IP[32];

	client = GetClientOfUserId(ReadPackCell(dataPack));
	dataPack.ReadString(steamid, sizeof(steamid));
	dataPack.ReadString(IP, sizeof(IP));
	delete dataPack;

	if (results == null)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (results.FetchRow())
	{
		int TotalBans = results.RowCount;

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					int length = results.FetchInt(6);
					int time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					int time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					int length = results.FetchInt(6);

					int time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("%s%t", PREFIX, "sourcesleuth_admintext", client, steamid, IP);
				}
			}
		}
	}
}

stock void BanPlayer(int client, int time)
{
	char Reason[255];
	Format(Reason, sizeof(Reason), "%s%T", PREFIX, "sourcesleuth_banreason", client);
	SBPP_BanPlayer(0, client, time, Reason);
}

void PrintToAdmins(const char[] format, any ...)
{
	char g_Buffer[256];

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN))
		{
			SetGlobalTransTarget(i);

			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public void LoadWhiteList()
{
	char path[PLATFORM_MAX_PATH], line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcebans/sourcesleuth_whitelist.cfg");

	File fileHandle = OpenFile(path, "r");

	if (fileHandle == null)
	{
		LogError("Could not find the config file (%s)", path);

		return;
	}

	while (!fileHandle.EndOfFile() && fileHandle.ReadLine(line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		g_hAllowedArray.PushString(line);
	}

	delete fileHandle;
}