Fix clear map cd

Fix clear map cd

/*
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

    // 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;

        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;
}

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;
}

int GetPrice(const char[] map, bool recently = true, bool partyblock = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsBigMap -> 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--;
            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]; MapData mapdata;
    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   : %b", mapdata.m_CooldownLeft);
            PrintToConsole(client, "m_RecentlyPlayed : %b", mapdata.m_RecentlyPlayed);
        }
    }
}

/*
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

    // 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;

        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;
}

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;
}

int GetPrice(const char[] map, bool recently = true, bool partyblock = false)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogStackTrace("IsBigMap -> 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;
}

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--;
            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 (SetCooldown(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]; 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))
        {
            mapdata.m_CooldownLeft = mapdata.m_MaxCooldown;
            g_MapData.SetArray(alter, mapdata, typeofdata, true);
            SaveMapPool(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   : %b", mapdata.m_CooldownLeft);
            PrintToConsole(client, "m_RecentlyPlayed : %b", mapdata.m_RecentlyPlayed);
        }
    }
}


Use RegConsoleCmd for overrides, idk why it didnt work without

#pragma newdecls required
#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <advanced_motd>
#include <morecolors>

#define PLUGIN_VERSION "1.0.0"
#define NGSRAFFLEURL "https://steamcommunity.com/groups/NGSRaffle#announcements/detail/"

Handle hHudText;

char raffleID[MAX_BUFFER_LENGTH];
char raffleItem[MAX_BUFFER_LENGTH];
int raffleType = 0;

//--------------------//

public Plugin myinfo = {
	name = "[NGS] Raffle Announce",
	author = "TheXeon",
	description = "Announces NGS group raffles!",
	version = PLUGIN_VERSION,
	url = "https://neogenesisnetwork.net"
}

public void OnPluginStart()
{
	RegConsoleCmd("sm_setraffle", CommandSetRaffle, "Usage: sm_setraffle <raffle announcement id> <0 = free, 1 = invite, 2 = paid, 3 = donor-only, 4 = member-only> <featured item(s)>");
	RegConsoleCmd("sm_announceraffle", CommandAnnounceRaffle, "Usage: sm_announceraffle");
	RegConsoleCmd("sm_canclethefuckignraffle", CommandCancelRaffle, "Usage: sm_cancelraffle");
	RegConsoleCmd("sm_cancelraffle", CommandCancelRaffle, "Usage: sm_cancelraffle");
	RegConsoleCmd("sm_joinraffle", CommandJoinRaffle, "Opens raffle in MOTD page.");
	RegConsoleCmd("sm_hideraffle", CommandHideRaffle, "Hides the current raffle.");
	LoadTranslations("common.phrases");
}

public Action CommandSetRaffle(int client, int args)
{
	if (!CheckCommandAccess(client, "sm_raffleadmin_override", ADMFLAG_GENERIC)) return Plugin_Handled;

	if (args < 1)
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Usage: sm_setraffle <raffle announcement id> <0 = free, 1 = invite, 2 = paid, 3 = donor-only, 4 = member-only> <featured item(s)>");
		return Plugin_Handled;
	}

	char arg2[MAX_BUFFER_LENGTH];

	GetCmdArg(1, raffleID, sizeof(raffleID));
	if (GetCmdArg(2, arg2, sizeof(arg2)) > 0) GetCmdArg(2, arg2, sizeof(arg2));
	if (GetCmdArg(3, raffleItem, sizeof(raffleItem)) > 0) GetCmdArg(3, raffleItem, sizeof(raffleItem));

	if (StrEqual(arg2, "free", false)) raffleType = 0;
	else if (StrEqual(arg2, "invite", false)) raffleType = 1;
	else if (StrEqual(arg2, "paid", false)) raffleType = 2;
	else if (StrEqual(arg2, "donor-only", false)) raffleType = 3;
	else if (StrEqual(arg2, "member-only", false)) raffleType = 4;
	else raffleType = StringToInt(arg2);

	CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Set raffle announcement ID to %s!", raffleID);
	AnnounceRaffle();

	return Plugin_Handled;
}

public Action CommandAnnounceRaffle(int client, int args)
{
	if (!CheckCommandAccess(client, "sm_raffleadmin_override", ADMFLAG_GENERIC)) return Plugin_Handled;

	if (StrEqual(raffleID, "", false))
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} There is currently no raffle going on.");
		return Plugin_Handled;
	}
	AnnounceRaffle();
	return Plugin_Handled;
}

public Action CommandHideRaffle(int client, int args)
{
	if (IsValidClient(client) && hHudText != null)
	{
		ClearSyncHud(client, hHudText);
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Raffle message has been hidden.");
	}
	return Plugin_Handled;
}

public Action CommandCancelRaffle(int client, int args)
{
	if (!CheckCommandAccess(client, "sm_raffleadmin_override", ADMFLAG_GENERIC)) return Plugin_Handled;
	raffleID = NULL_STRING;
	raffleItem = NULL_STRING;
	for (int i = 1; i <= MaxClients; i++)
	{
		if (hHudText != null) ClearSyncHud(i, hHudText);
	}

	if (hHudText == null) hHudText = CreateHudSynchronizer();

	SetHudTextParams(-1.0, 0.1, 5.0, 255, 0, 0, 255, 1, 1.0, 1.0, 1.0);

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			ShowSyncHudText(i, hHudText, "Raffle has been canceled...");
		}
	}
	CloseHandle(hHudText);

	return Plugin_Handled;
}

public Action CommandJoinRaffle(int client, int args)
{
	if (StrEqual(raffleID, "", false))
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} There is currently no raffle going on.");
		return Plugin_Handled;
	}
	char raffleLink[MAX_BUFFER_LENGTH];
	Format(raffleLink, sizeof(raffleLink), "%s%s", NGSRAFFLEURL, raffleID);
	AdvMOTD_ShowMOTDPanel(client, "NGS Raffle", raffleLink, MOTDPANEL_TYPE_URL, true, true, true, OnMOTDFailure);
	return Plugin_Handled;
}

public void AnnounceRaffle()
{
	hHudText = CreateHudSynchronizer();
	SetHudTextParams(-1.0, 0.1, 30.0, 255, 0, 0, 255, 1, 1.0, 1.0, 1.0);

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			switch (raffleType)
			{
				case 0:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A free raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A free raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 1:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "An invite raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "An invite raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 2:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A paid raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A paid raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 3:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A donor-only raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A donor-only raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 4:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A member-only raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A member-only raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}
			}
		}
	}

	delete hHudText;
	hHudText = null;
}

public int OnMOTDFailure(int client, MOTDFailureReason reason)
{
	switch(reason)
	{
		case MOTDFailure_Disabled: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} You cannot join raffles with HTML MOTDs disabled!");
		case MOTDFailure_Matchmaking: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} You cannot join raffles after joining via Quickplay!");
		case MOTDFailure_QueryFailed: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} Unable to join raffle!");
	}
}

public bool IsValidClient (int client)
{
	if(client > 4096) client = EntRefToEntIndex(client);
	if(client < 1 || client > MaxClients) return false;
	if(!IsClientInGame(client)) return false;
	if(IsFakeClient(client)) return false;
	if(GetEntProp(client, Prop_Send, "m_bIsCoaching")) return false;
	return true;
}

#pragma newdecls required
#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <advanced_motd>
#include <morecolors>

#define PLUGIN_VERSION "1.0.0"
#define NGSRAFFLEURL "https://steamcommunity.com/groups/NGSRaffle#announcements/detail/"

Handle hHudText;

char raffleID[MAX_BUFFER_LENGTH];
char raffleItem[MAX_BUFFER_LENGTH];
int raffleType = 0;

//--------------------//

public Plugin myinfo = {
	name = "[NGS] Raffle Announce",
	author = "TheXeon",
	description = "Announces NGS group raffles!",
	version = PLUGIN_VERSION,
	url = "https://matespastdates.servegame.com"
}

public void OnPluginStart()
{
	RegAdminCmd("sm_setraffle", CommandSetRaffle, ADMFLAG_GENERIC, "Usage: sm_setraffle <raffle announcement id> <0 = free, 1 = invite, 2 = paid, 3 = donor-only, 4 = member-only> <featured item(s)>");
	RegAdminCmd("sm_announceraffle", CommandAnnounceRaffle, ADMFLAG_GENERIC, "Usage: sm_announceraffle");
	RegAdminCmd("sm_canclethefuckignraffle", CommandCancelRaffle, ADMFLAG_GENERIC, "Usage: sm_cancelraffle");
	RegAdminCmd("sm_cancelraffle", CommandCancelRaffle, ADMFLAG_GENERIC, "Usage: sm_cancelraffle");
	RegConsoleCmd("sm_joinraffle", CommandJoinRaffle, "Opens raffle in MOTD page.");
	RegConsoleCmd("sm_hideraffle", CommandHideRaffle, "Hides the current raffle.");
	LoadTranslations("common.phrases");
}

public Action CommandSetRaffle(int client, int args)
{
	if (args < 1)
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Usage: sm_setraffle <raffle announcement id> <0 = free, 1 = invite, 2 = paid, 3 = donor-only, 4 = member-only> <featured item(s)>");
		return Plugin_Handled;
	}

	char arg2[MAX_BUFFER_LENGTH];

	GetCmdArg(1, raffleID, sizeof(raffleID));
	if (GetCmdArg(2, arg2, sizeof(arg2)) > 0) GetCmdArg(2, arg2, sizeof(arg2));
	if (GetCmdArg(3, raffleItem, sizeof(raffleItem)) > 0) GetCmdArg(3, raffleItem, sizeof(raffleItem));

	if (StrEqual(arg2, "free", false)) raffleType = 0;
	else if (StrEqual(arg2, "invite", false)) raffleType = 1;
	else if (StrEqual(arg2, "paid", false)) raffleType = 2;
	else if (StrEqual(arg2, "donor-only", false)) raffleType = 3;
	else if (StrEqual(arg2, "member-only", false)) raffleType = 4;
	else raffleType = StringToInt(arg2);

	CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Set raffle announcement ID to %s!", raffleID);
	AnnounceRaffle();

	return Plugin_Handled;
}

public Action CommandAnnounceRaffle(int client, int args)
{
	if (StrEqual(raffleID, "", false))
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} There is currently no raffle going on.");
		return Plugin_Handled;
	}

	AnnounceRaffle();
	return Plugin_Handled;
}

public Action CommandHideRaffle(int client, int args)
{
	if (IsValidClient(client))
	{
		if (hHudText != null)
		{
			ClearSyncHud(client, hHudText);
			ShowSyncHudText(client, hHudText, "Hidden.");
			CloseHandle(hHudText);
		}
	}
	return Plugin_Handled;
}

public Action CommandCancelRaffle(int client, int args)
{
	raffleID = NULL_STRING;
	raffleItem = NULL_STRING;
	if (hHudText != null) ClearSyncHud(i, hHudText);
	if (hHudText == null) hHudText = CreateHudSynchronizer();

	SetHudTextParams(-1.0, 0.1, 5.0, 255, 0, 0, 255, 1, 1.0, 1.0, 1.0);

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			ShowSyncHudText(i, hHudText, "Raffle has been canceled...");
		}
	}
	CloseHandle(hHudText);

	return Plugin_Handled;
}

public Action CommandJoinRaffle(int client, int args)
{
	if (StrEqual(raffleID, "", false))
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} There is currently no raffle going on.");
		return Plugin_Handled;
	}
	char raffleLink[MAX_BUFFER_LENGTH];
	Format(raffleLink, sizeof(raffleLink), "%s%s", NGSRAFFLEURL, raffleID);
	AdvMOTD_ShowMOTDPanel(client, "NGS Raffle", raffleLink, MOTDPANEL_TYPE_URL, true, true, true, OnMOTDFailure);

	return Plugin_Handled;
}

public void AnnounceRaffle()
{
	hHudText = CreateHudSynchronizer();
	SetHudTextParams(-1.0, 0.1, 30.0, 255, 0, 0, 255, 1, 1.0, 1.0, 1.0);

	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			switch (raffleType)
			{
				case 0:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A free raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A free raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 1:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "An invite raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "An invite raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 2:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A paid raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A paid raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 3:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A donor-only raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A donor-only raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}

				case 4:
				{
					if (StrEqual(raffleItem, "", false)) ShowSyncHudText(i, hHudText, "A member-only raffle has started! Use !joinraffle to tune in.");
					else ShowSyncHudText(i, hHudText, "A member-only raffle has started! Use !joinraffle to tune in.\n Featured item(s): %s", raffleItem);
				}
			}
		}
	}

	CloseHandle(hHudText);
}

public void OnMOTDFailure(int client, MOTDFailureReason reason)
{
	switch(reason)
	{
		case MOTDFailure_Disabled: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} You cannot join raffles with HTML MOTDs disabled!");
		case MOTDFailure_Matchmaking: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} You cannot join raffles after joining via Quickplay!");
		case MOTDFailure_QueryFailed: CPrintToChat(client, "{GREEN}[SM]{DEFAULT} Unable to join raffle!");
	}
}

public bool IsValidClient (int client)
{
	if(client > 4096) client = EntRefToEntIndex(client);
	if(client < 1 || client > MaxClients) return false;
	if(!IsClientInGame(client)) return false;
	if(IsFakeClient(client)) return false;
	if(GetEntProp(client, Prop_Send, "m_bIsCoaching")) return false;
	return true;
}


Update Plugin Translation Load

Update Plugin Translation Load

Comment: https://github.com/sbpp/sourcebans-pp/commit/4ce3dbb7969571ef0492abf7f0959aab056f7836#commitcomment-28039726

#pragma semicolon 1

#define PLUGIN_AUTHOR "RumbleFrog, SourceBans++ Dev Team"
#define PLUGIN_VERSION "1.6.3-rc.1"

#include <sourcemod>
#include <sourcebans>

#pragma newdecls required

#define Chat_Prefix "[SourceBans++] "

enum
{
	Cooldown = 0,
	MinLen,
	Settings_Count
};

ConVar Convars[Settings_Count];

bool bInReason[MAXPLAYERS + 1];

int iMinLen = 10
	, iTargetCache[MAXPLAYERS + 1];

float fCooldown = 60.0
	, fNextUse[MAXPLAYERS + 1];

public Plugin myinfo =
{
	name = "SourceBans++ Report Plugin",
	author = PLUGIN_AUTHOR,
	description = "Adds ability for player to report offending players",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	CreateConVar("sbpp_report_version", PLUGIN_VERSION, "SBPP Report Version", FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY);

	Convars[Cooldown] = CreateConVar("sbpp_report_cooldown", "60.0", "Cooldown in seconds between per report per user", FCVAR_NONE, true, 0.0, false);
	Convars[MinLen] = CreateConVar("sbpp_report_minlen", "10", "Minimum reason length", FCVAR_NONE, true, 0.0, false);

	LoadTranslations("sourcereport.phrases");

	RegConsoleCmd("sm_report", CmdReport, "Initialize Report");

	Convars[Cooldown].AddChangeHook(OnConvarChanged);
	Convars[MinLen].AddChangeHook(OnConvarChanged);
}

public Action CmdReport(int iClient, int iArgs)
{
	if (!IsValidClient(iClient))
		return Plugin_Handled;

	if (OnCooldown(iClient))
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "In Cooldown", iClient, GetRemainingTime(iClient));

		return Plugin_Handled;
	}

	Menu PList = new Menu(ReportMenu);

	char sName[MAX_NAME_LENGTH], sIndex[4];

	for (int i = 0; i <= MaxClients; i++)
	{
		if (!IsValidClient(i))
			continue;

		GetClientName(i, sName, sizeof sName);
		IntToString(i, sIndex, sizeof sIndex);

		PList.AddItem(sIndex, sName);
	}

	PList.Display(iClient, MENU_TIME_FOREVER);

	return Plugin_Handled;
}

public int ReportMenu(Menu menu, MenuAction action, int iClient, int iItem)
{
	switch (action)
	{
		case MenuAction_Select:
		{
			char sIndex[4];

			menu.GetItem(iItem, sIndex, sizeof sIndex);

			iTargetCache[iClient] = StringToInt(sIndex);

			bInReason[iClient] = true;

			PrintToChat(iClient, "%s%T", Chat_Prefix, "Reason Prompt", iClient);
		}
		case MenuAction_End:
			delete menu;
	}
}

public Action OnClientSayCommand(int iClient, const char[] sCommand, const char[] sArgs)
{
	if (!bInReason[iClient])
		return Plugin_Continue;

	if (!IsValidClient(iClient) || (iTargetCache[iClient] != -1 && !IsValidClient(iTargetCache[iClient])))
	{
		ResetInReason(iClient);

		return Plugin_Continue;
	}

	if (StrEqual(sArgs, "cancel", false))
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "Report Canceled", iClient);

		ResetInReason(iClient);

		return Plugin_Stop;
	}

	if (strlen(sArgs) < iMinLen)
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "Reason Short Need More", iClient);

		return Plugin_Stop;
	}

	SourceBans_ReportPlayer(iClient, iTargetCache[iClient], sArgs);

	AddCooldown(iClient);

	ResetInReason(iClient);

	return Plugin_Stop;
}

public void SourceBans_OnReportPlayer(int iReporter, int iTarget, const char[] sReason)
{
	if (!IsValidClient(iReporter))
		return;

	PrintToChat(iReporter, "%s%T", Chat_Prefix, "Report Sent", iReporter);
}

void ResetInReason(int iClient)
{
	bInReason[iClient] = false;
	iTargetCache[iClient] = -1;
}

void AddCooldown(int iClient)
{
	fNextUse[iClient] = GetGameTime() + fCooldown;
}

bool OnCooldown(int iClient)
{
	return (fNextUse[iClient] - GetGameTime()) > 0.0;
}

float GetRemainingTime(int iClient)
{
	float fOffset = fNextUse[iClient] - GetGameTime();

	if (fOffset > 0.0)
		return fOffset;
	else
		return 0.0;
}

public void OnConvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
	if (convar == Convars[Cooldown])
		fCooldown = Convars[Cooldown].FloatValue;
	else if (convar == Convars[MinLen])
		iMinLen = Convars[MinLen].IntValue;
}

stock bool IsValidClient(int iClient, bool bAlive = false)
{
	if (iClient >= 1 &&
	iClient <= MaxClients &&
	IsClientConnected(iClient) &&
	IsClientInGame(iClient) &&
	!IsFakeClient(iClient) &&
	(bAlive == false || IsPlayerAlive(iClient)))
	{
		return true;
	}

	return false;
}

#pragma semicolon 1

#define PLUGIN_AUTHOR "RumbleFrog, SourceBans++ Dev Team"
#define PLUGIN_VERSION "1.6.3-rc.1"

#include <sourcemod>
#include <sourcebans>

#pragma newdecls required

#define Chat_Prefix "[SourceBans++] "

enum
{
	Cooldown = 0,
	MinLen,
	Settings_Count
};

ConVar Convars[Settings_Count];

bool bInReason[MAXPLAYERS + 1];

int iMinLen = 10
	, iTargetCache[MAXPLAYERS + 1];

float fCooldown = 60.0
	, fNextUse[MAXPLAYERS + 1];

public Plugin myinfo =
{
	name = "SourceBans++ Report Plugin",
	author = PLUGIN_AUTHOR,
	description = "Adds ability for player to report offending players",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	CreateConVar("sbpp_report_version", PLUGIN_VERSION, "SBPP Report Version", FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY);

	Convars[Cooldown] = CreateConVar("sbpp_report_cooldown", "60.0", "Cooldown in seconds between per report per user", FCVAR_NONE, true, 0.0, false);
	Convars[MinLen] = CreateConVar("sbpp_report_minlen", "10", "Minimum reason length", FCVAR_NONE, true, 0.0, false);

	LoadTranslations("sbpp_report.phrases");

	RegConsoleCmd("sm_report", CmdReport, "Initialize Report");

	Convars[Cooldown].AddChangeHook(OnConvarChanged);
	Convars[MinLen].AddChangeHook(OnConvarChanged);
}

public Action CmdReport(int iClient, int iArgs)
{
	if (!IsValidClient(iClient))
		return Plugin_Handled;

	if (OnCooldown(iClient))
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "In Cooldown", iClient, GetRemainingTime(iClient));

		return Plugin_Handled;
	}

	Menu PList = new Menu(ReportMenu);

	char sName[MAX_NAME_LENGTH], sIndex[4];

	for (int i = 0; i <= MaxClients; i++)
	{
		if (!IsValidClient(i))
			continue;

		GetClientName(i, sName, sizeof sName);
		IntToString(i, sIndex, sizeof sIndex);

		PList.AddItem(sIndex, sName);
	}

	PList.Display(iClient, MENU_TIME_FOREVER);

	return Plugin_Handled;
}

public int ReportMenu(Menu menu, MenuAction action, int iClient, int iItem)
{
	switch (action)
	{
		case MenuAction_Select:
		{
			char sIndex[4];

			menu.GetItem(iItem, sIndex, sizeof sIndex);

			iTargetCache[iClient] = StringToInt(sIndex);

			bInReason[iClient] = true;

			PrintToChat(iClient, "%s%T", Chat_Prefix, "Reason Prompt", iClient);
		}
		case MenuAction_End:
			delete menu;
	}
}

public Action OnClientSayCommand(int iClient, const char[] sCommand, const char[] sArgs)
{
	if (!bInReason[iClient])
		return Plugin_Continue;

	if (!IsValidClient(iClient) || (iTargetCache[iClient] != -1 && !IsValidClient(iTargetCache[iClient])))
	{
		ResetInReason(iClient);

		return Plugin_Continue;
	}

	if (StrEqual(sArgs, "cancel", false))
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "Report Canceled", iClient);

		ResetInReason(iClient);

		return Plugin_Stop;
	}

	if (strlen(sArgs) < iMinLen)
	{
		PrintToChat(iClient, "%s%T", Chat_Prefix, "Reason Short Need More", iClient);

		return Plugin_Stop;
	}

	SourceBans_ReportPlayer(iClient, iTargetCache[iClient], sArgs);

	AddCooldown(iClient);

	ResetInReason(iClient);

	return Plugin_Stop;
}

public void SourceBans_OnReportPlayer(int iReporter, int iTarget, const char[] sReason)
{
	if (!IsValidClient(iReporter))
		return;

	PrintToChat(iReporter, "%s%T", Chat_Prefix, "Report Sent", iReporter);
}

void ResetInReason(int iClient)
{
	bInReason[iClient] = false;
	iTargetCache[iClient] = -1;
}

void AddCooldown(int iClient)
{
	fNextUse[iClient] = GetGameTime() + fCooldown;
}

bool OnCooldown(int iClient)
{
	return (fNextUse[iClient] - GetGameTime()) > 0.0;
}

float GetRemainingTime(int iClient)
{
	float fOffset = fNextUse[iClient] - GetGameTime();

	if (fOffset > 0.0)
		return fOffset;
	else
		return 0.0;
}

public void OnConvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
	if (convar == Convars[Cooldown])
		fCooldown = Convars[Cooldown].FloatValue;
	else if (convar == Convars[MinLen])
		iMinLen = Convars[MinLen].IntValue;
}

stock bool IsValidClient(int iClient, bool bAlive = false)
{
	if (iClient >= 1 &&
	iClient <= MaxClients &&
	IsClientConnected(iClient) &&
	IsClientInGame(iClient) &&
	!IsFakeClient(iClient) &&
	(bAlive == false || IsPlayerAlive(iClient)))
	{
		return true;
	}

	return false;
}


Cooldown & Min Len

Cooldown & Min Len

#pragma semicolon 1

#define DEBUG

#define PLUGIN_AUTHOR "RumbleFrog, SourceBans++ Dev Team"
#define PLUGIN_VERSION "1.6.3-rc.1"

#include <sourcemod>
#include <sourcebans>

#pragma newdecls required

#define Chat_Prefix "[SourceBans++] "

enum
{
	Prefix = 0,
	Cooldown,
	MinLen,
	Settings_Count
};

ConVar Convars[Settings_Count];

char sPrefix[16];

bool bInReason[MAXPLAYERS + 1];

int iMinLen = 10
	, iTargetCache[MAXPLAYERS + 1];

float fCooldown = 60.0
	, fNextUse[MAXPLAYERS + 1];

public Plugin myinfo =
{
	name = "SourceBans++ Report Plugin",
	author = PLUGIN_AUTHOR,
	description = "Adds ability for player to report offending players",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	CreateConVar("sbpp_report_version", PLUGIN_VERSION, "SBPP Report Version", FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY);

	Convars[Prefix] = CreateConVar("sbpp_report_prefix", "sb", "SourceBans++ Database Table Prefix", FCVAR_NONE);
	Convars[Cooldown] = CreateConVar("sbpp_report_cooldown", "60.0", "Cooldown in seconds between per report per user", FCVAR_NONE, true, 0.0, false);
	Convars[MinLen] = CreateConVar("sbpp_report_minlen", "10", "Minimum reason length", FCVAR_NONE, true, 0.0, false);

	RegConsoleCmd("sm_report", CmdReport, "Initialize Report");

	Convars[Prefix].AddChangeHook(OnConvarChanged);
	Convars[Cooldown].AddChangeHook(OnConvarChanged);
	Convars[MinLen].AddChangeHook(OnConvarChanged);
}

public Action CmdReport(int iClient, int iArgs)
{
	if (!IsValidClient(iClient))
		return Plugin_Handled;

	if (OnCooldown(iClient))
	{
		PrintToChat(iClient, "%sPlease wait %.0f seconds before reporting again", Chat_Prefix, GetRemainingTime(iClient));

		return Plugin_Handled;
	}

	Menu PList = new Menu(ReportMenu);

	char sName[MAX_NAME_LENGTH], sIndex[4];

	for (int i = 0; i <= MaxClients; i++)
	{
		GetClientName(i, sName, sizeof sName);
		IntToString(i, sIndex, sizeof sIndex);

		PList.AddItem(sIndex, sName);
	}

	PList.Display(iClient, MENU_TIME_FOREVER);

	return Plugin_Handled;
}

public int ReportMenu(Menu menu, MenuAction action, int iClient, int iItem)
{
	switch (action)
	{
		case MenuAction_Select:
		{
			char sIndex[4];

			menu.GetItem(iItem, sIndex, sizeof sIndex);

			iTargetCache[iClient] = StringToInt(sIndex);

			bInReason[iClient] = true;

			PrintToChat(iClient, "%sPlease enter the reason for the report or \"cancel\" to cancel", Chat_Prefix);
		}
		case MenuAction_End:
			delete menu;
	}
}

public Action OnClientSayCommand(int iClient, const char[] sCommand, const char[] sArgs)
{
	if (!bInReason[iClient])
		return Plugin_Continue;

	if (!IsValidClient(iClient) || (iTargetCache[iClient] != -1 && !IsValidClient(iTargetCache[iClient])))
	{
		ResetInReason(iClient);

		return Plugin_Continue;
	}

	if (StrEqual(sArgs, "cancel", false))
	{
		PrintToChat(iClient, "%sReport cancelled", Chat_Prefix);

		ResetInReason(iClient);

		return Plugin_Stop;
	}

	if (strlen(sArgs) < iMinLen)
	{
		PrintToChat(iClient, "%sReason is too short. More details is required", Chat_Prefix);

		return Plugin_Stop;
	}

	SourceBans_ReportPlayer(iClient, iTargetCache[iClient], sArgs);

	AddCooldown(iClient);

	ResetInReason(iClient);

	return Plugin_Stop;
}

void ResetInReason(int iClient)
{
	bInReason[iClient] = false;
	iTargetCache[iClient] = -1;
}

void AddCooldown(int iClient)
{
	fNextUse[iClient] = GetGameTime() + fCooldown;
}

bool OnCooldown(int iClient)
{
	return (fNextUse[iClient] - GetGameTime()) > 0.0;
}

float GetRemainingTime(int iClient)
{
	float fOffset = fNextUse[iClient] - GetGameTime();

	if (fOffset > 0.0)
		return fOffset;
	else
		return 0.0;
}

public void OnConvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
	if (convar == Convars[Prefix])
		Convars[Prefix].GetString(sPrefix, sizeof sPrefix);
	else if (convar == Convars[Cooldown])
		fCooldown = Convars[Cooldown].FloatValue;
	else if (convar == Convars[MinLen])
		iMinLen = Convars[MinLen].IntValue;
}

stock bool IsValidClient(int iClient, bool bAlive = false)
{
	if (iClient >= 1 &&
	iClient <= MaxClients &&
	IsClientConnected(iClient) &&
	IsClientInGame(iClient) &&
	!IsFakeClient(iClient) &&
	(bAlive == false || IsPlayerAlive(iClient)))
	{
		return true;
	}

	return false;
}

#pragma semicolon 1

#define DEBUG

#define PLUGIN_AUTHOR "RumbleFrog, SourceBans++ Dev Team"
#define PLUGIN_VERSION "1.6.3-rc.1"

#include <sourcemod>
#include <sourcebans>

#pragma newdecls required

#define Chat_Prefix "[SourceBans++] "

enum
{
	Prefix = 0,
	Cooldown,
	Settings_Count
};

ConVar Convars[Settings_Count];

char sPrefix[16];

bool bInReason[MAXPLAYERS + 1];

int iTargetCache[MAXPLAYERS + 1];

float fCooldown = 60.0
	, fLastUse[MAXPLAYERS + 1];

public Plugin myinfo =
{
	name = "SourceBans++ Report Plugin",
	author = PLUGIN_AUTHOR,
	description = "Adds ability for player to report offending players",
	version = PLUGIN_VERSION,
	url = "https://sbpp.github.io"
};

public void OnPluginStart()
{
	CreateConVar("sbpp_report_version", PLUGIN_VERSION, "SBPP Report Version", FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY);

	Convars[Prefix] = CreateConVar("sbpp_report_prefix", "sb", "SourceBans++ Database Table Prefix", FCVAR_NONE);
	Convars[Cooldown] = CreateConVar("sbpp_report_cooldown", "60.0", "Cooldown in seconds between per report per user", FCVAR_NONE, true, 0.0, false);

	RegConsoleCmd("sm_report", CmdReport, "Initialize Report");

	Convars[Prefix].AddChangeHook(OnConvarChanged);
	Convars[Cooldown].AddChangeHook(OnConvarChanged);
}

public Action CmdReport(int iClient, int iArgs)
{
	if (!IsValidClient(iClient))
		return Plugin_Handled;

	Menu PList = new Menu(ReportMenu);

	char sName[MAX_NAME_LENGTH], sIndex[4];

	for (int i = 0; i <= MaxClients; i++)
	{
		GetClientName(i, sName, sizeof sName);
		IntToString(i, sIndex, sizeof sIndex);

		PList.AddItem(sIndex, sName);
	}

	PList.Display(iClient, MENU_TIME_FOREVER);

	return Plugin_Handled;
}

public int ReportMenu(Menu menu, MenuAction action, int iClient, int iItem)
{
	switch (action)
	{
		case MenuAction_Select:
		{
			char sIndex[4];

			menu.GetItem(iItem, sIndex, sizeof sIndex);

			iTargetCache[iClient] = StringToInt(sIndex);

			bInReason[iClient] = true;

			PrintToChat(iClient, "%sPlease enter the reason for the report or \"cancel\" to cancel", Chat_Prefix);
		}
		case MenuAction_End:
			delete menu;
	}
}

public Action OnClientSayCommand(int iClient, const char[] sCommand, const char[] sArgs)
{
	if (!bInReason[iClient])
		return Plugin_Continue;

	if (!IsValidClient(iClient) || (iTargetCache[iClient] != -1 && !IsValidClient(iTargetCache[iClient])))
	{
		ResetInReason(iClient);

		return Plugin_Continue;
	}

	if (StrEqual(sArgs, "cancel", false))
	{
		PrintToChat(iClient, "%sReport cancelled", Chat_Prefix);

		ResetInReason(iClient);

		return Plugin_Stop;
	}

	SourceBans_ReportPlayer(iClient, iTargetCache[iClient], sArgs);

	ResetInReason(iClient);

	return Plugin_Stop;
}

void ResetInReason(int iClient)
{
	bInReason[iClient] = false;
	iTargetCache[iClient] = -1;
}

public void OnConvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
	if (convar == Convars[Prefix])
		Convars[Prefix].GetString(sPrefix, sizeof sPrefix);
	else if (convar == Convars[Cooldown])
		fCooldown = Convars[Cooldown].FloatValue;
}

stock bool IsValidClient(int iClient, bool bAlive = false)
{
	if (iClient >= 1 &&
	iClient <= MaxClients &&
	IsClientConnected(iClient) &&
	IsClientInGame(iClient) &&
	!IsFakeClient(iClient) &&
	(bAlive == false || IsPlayerAlive(iClient)))
	{
		return true;
	}

	return false;
}