Fix autogen

Fix autogen

/*
enum struct MapData
{
    // mapdata.kv
    char m_FileName[128];
    char m_Description[64];
    int  m_Tier;
    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()
{
    char map[128];
    GetCurrentMap(map, 128);

    SetCooldown(map, false);
    SetLastPlayed(map, false);
    SaveMapPool(map);

    if (!g_bAllowCountdown)
        return;

    ArrayList maps = GetAllMapsName();

    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);
        CleanMapsData(kv);
        LoadAllMapsData(kv);
    }

    kv.Rewind();

    if (g_ConVars.AuthGen.BoolValue)
    {
        // save sata
        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)
{
    g_MapData.Clear();

    bool autoGen = g_ConVars.AutoGen.BoolValue;

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

            if (!autoGen)
                continue;

            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_Tier            = clamp(0, MAX_TIER, kv.GetNum("m_Difficulty", 0));
        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);
    }

    LogMessage("Mapdata has been loaded with %d/%d maps...", g_MapData.Size, maps.Length);

    delete maps;
}

stock void CleanMapsData(KeyValues kv)
{
    if (!g_ConVars.AutoGen.BoolValue)
        return;

    kv.Rewind();

    if (!kv.GotoFirstSubKey(true))
        return;

    char map[128], path[128];

    do
    {
        kv.GetSectionName(map, 128);
        FormatEx(path, 128, "maps/%s.bsp", map);

        if (!FileExists(path, true))
        {
            // this file not exists
            kv.DeleteThis();
        }
    }
    while (kv.GotoNextKey(true));
    kv.Rewind();
}

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

    strcopy(desc, maxLen, mapdata.m_Description);

    if (mapdata.m_Tier > 0 && includeTier)
    {
        char tier[32];
        GetTierString(mapdata.m_Tier, tier, 32);
        Format(desc, maxLen, "<%s>%s", tier, desc);
    }

    if (includeName)
    {
        if (includeTag)
        {
            Format(desc, maxLen, "%s%s%s\n%s", map, mapdata.m_VipOnly ? " [VIP]" : "", mapdata.m_AdminOnly ? " [ADMIN]" : "", desc);
        }
        else
        {
            Format(desc, maxLen, "%s\n%s", map, desc);
        }
    }
    else
    {
        if (includeTag)
        {
            Format(desc, maxLen, "%s%s", mapdata.m_VipOnly   ? "[VIP] "   : "", desc);
            Format(desc, maxLen, "%s%s", mapdata.m_AdminOnly ? "[ADMIN] " : "", 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))
        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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("IsCertainTimes -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_CertainTimes[GetTodayHours()];
}

bool IsDisabled(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
        return true;

    return false;
}

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

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

    if (save)
        SaveMapPool(map);

    return true;
}

int GetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogMessage("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))
    {
        LogMessage("GetCooldown -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_CooldownLeft;
}

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

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

    if (save)
        SaveMapPool(map);

    return true;
}

bool ClearCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogMessage("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))
    {
        LogMessage("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;
}

void SaveMapPool(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // ??
        LogMessage("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(int client)
{
    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)
        {
            if (!Call_OnClearMapCooldown(map, client))
                continue;

            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 && Call_OnClearMapCooldown(map, client))
        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 && Call_OnResetMapCooldown(map, client))
        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_Tier           : %d", mapdata.m_Tier);
            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_Tier;
    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()
{
    char map[128];
    GetCurrentMap(map, 128);

    SetCooldown(map, false);
    SetLastPlayed(map, false);
    SaveMapPool(map);

    if (!g_bAllowCountdown)
        return;

    ArrayList maps = GetAllMapsName();

    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);
        //CleanMapsData(kv);
        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)
{
    g_MapData.Clear();

    bool autoGen = g_ConVars.AutoGen.BoolValue;

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

            if (!autoGen)
                continue;

            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_Tier            = clamp(0, MAX_TIER, kv.GetNum("m_Difficulty", 0));
        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);
    }

    LogMessage("Mapdata has been loaded with %d/%d maps...", g_MapData.Size, maps.Length);

    delete maps;
}

stock void CleanMapsData(KeyValues kv)
{
    kv.Rewind();

    if (!kv.GotoFirstSubKey(true))
        return;

    char map[128], path[128];

    do
    {
        kv.GetSectionName(map, 128);
        FormatEx(path, 128, "maps/%s.bsp", map);

        if (!FileExists(path, true))
        {
            // this file not exists
            kv.DeleteThis();
        }
    }
    while (kv.GotoNextKey(true));
    kv.Rewind();
}

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

    strcopy(desc, maxLen, mapdata.m_Description);

    if (mapdata.m_Tier > 0 && includeTier)
    {
        char tier[32];
        GetTierString(mapdata.m_Tier, tier, 32);
        Format(desc, maxLen, "<%s>%s", tier, desc);
    }

    if (includeName)
    {
        if (includeTag)
        {
            Format(desc, maxLen, "%s%s%s\n%s", map, mapdata.m_VipOnly ? " [VIP]" : "", mapdata.m_AdminOnly ? " [ADMIN]" : "", desc);
        }
        else
        {
            Format(desc, maxLen, "%s\n%s", map, desc);
        }
    }
    else
    {
        if (includeTag)
        {
            Format(desc, maxLen, "%s%s", mapdata.m_VipOnly   ? "[VIP] "   : "", desc);
            Format(desc, maxLen, "%s%s", mapdata.m_AdminOnly ? "[ADMIN] " : "", 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))
        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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("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))
    {
        LogMessage("IsCertainTimes -> Failed to get map %s", map);
        return false;
    }

    return mapdata.m_CertainTimes[GetTodayHours()];
}

bool IsDisabled(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
        return true;

    return false;
}

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

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

    if (save)
        SaveMapPool(map);

    return true;
}

int GetLastPlayed(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogMessage("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))
    {
        LogMessage("GetCooldown -> Failed to get map %s", map);
        return 0;
    }

    return mapdata.m_CooldownLeft;
}

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

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

    if (save)
        SaveMapPool(map);

    return true;
}

bool ClearCooldown(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        LogMessage("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))
    {
        LogMessage("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;
}

void SaveMapPool(const char[] map)
{
    MapData mapdata;
    if (!g_MapData.GetArray(map, mapdata, typeofdata))
    {
        // ??
        LogMessage("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(int client)
{
    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)
        {
            if (!Call_OnClearMapCooldown(map, client))
                continue;

            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 && Call_OnClearMapCooldown(map, client))
        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 && Call_OnResetMapCooldown(map, client))
        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_Tier           : %d", mapdata.m_Tier);
            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);
        }
    }
}


Fixes to last commit

Fixes to last commit

/*
 * Copyright (c) 2016 Alex "ZeroKnight" George
 *
 * This file is licensed under the MIT License. For details, please see the
 * LICENSE file that should be included with the source code, or
 * <https://opensource.org/licenses/MIT>
 */

#include <sourcemod>

#pragma semicolon 1
#pragma newdecls required

char PLUGIN_NAME[]    = "Multi-Giveaway";
char PLUGIN_AUTHOR[]  = "Alex \"ZeroKnight\" George";
char PLUGIN_DESC[]    = "Expansive Giveaway system with numerous types of Giveaway events and stats";
char PLUGIN_VERSION[] = "0.1.0";
char PLUGIN_URL[]     = "http:/github.com/ZeroKnight/sm-Multi-Giveaway";
char PLUGIN_TAG[]     = "[MG]";

public Plugin myinfo =
{
  name        = PLUGIN_NAME,
  author      = PLUGIN_AUTHOR,
  description = PLUGIN_DESC,
  version     = PLUGIN_VERSION,
  url         = PLUGIN_URL
};

enum GiveawayType { GT_Invalid = -1, GT_Dice = 1, GT_Number = 2, GT_Kill = 4, GT_All = 7 };

//File configfile = ...

// TODO: Put things like this in a personal core library?
#define CVAR(%1) ConVar cv_%1
char CVAR_PREFIX[] = "multi_giveaway";
CVAR(version);
CVAR(flags);
CVAR(enabled_types);
CVAR(player_state);
CVAR(min_conn_time);
CVAR(dice_min);
CVAR(dice_max);
CVAR(dice_show_rolls);
CVAR(dice_rerolls);
CVAR(number_min);
CVAR(number_max);
CVAR(number_show_guesses);

void RegisterConVars()
{
  // XXX: Why is CreateConVar() not overloaded?! Are there even overloads?!
  char sGT_All[32]; IntToString(GT_All, sGT_All, sizeof(sGT_All));

  cv_version = CreateConVar(
    "multi_givaway_version",
    PLUGIN_VERSION,
    "Plugin version",
    FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD);
  cv_flags = CreateConVar(
    "multi_givaway_flags",
    "b",
    "List of admin flags allowed to manage giveaways");
  cv_enabled_types = CreateConVar(
    "multi_giveaway_enabled_types",
    sGT_All,
    "Bit-field (sum of options) of enabled Giveaway types",
    FCVAR_NONE,
    true, 1.0,
    true, GT_All);
  cv_player_state = CreateConVar(
    "multi_giveaway_player_state",
    "7",
    "Bit-field (sum of options) of player states that are valid for giveaway participation: 1 - Spectator, 2 - Dead, 4 - Alive",
    FCVAR_NONE,
    true, 1.0,
    true, 7.0);
  cv_min_conn_time = CreateConVar( // TODO: Allow certain giveaways to override (eg. kill objective)
    "multi_giveaway_min_conn_time",
    "300", // 5 minutes
    "Amount of time in seconds a player must be connected to participate in a giveaway",
    FCVAR_NONE,
    true, 0.0);

  cv_dice_min = CreateConVar(
    "multi_giveaway_dice_min",
    "1",
    "Lowest possible number a player may roll",
    FCVAR_NONE,
    true, 1.0);
  cv_dice_max = CreateConVar(
    "multi_giveaway_dice_max",
    "50",
    "Highest possible number a player may roll");
  cv_dice_show_rolls = CreateConVar(
    "multi_giveaway_dice_show_rolls",
    "0",
    "Who can see other clients' dice rolls: 0 - Nobody, 1 - Admins, 2 - Everyone",
    FCVAR_NONE,
    true, 0.0,
    true, 2.0);
  cv_dice_rerolls = CreateConVar(
    "multi_giveaway_dice_reroll",
    "0",
    "Number of 'do-overs' a player gets when rolling. Only the last is counted, for better or for worse");

  cv_number_min = CreateConVar(
    "multi_giveaway_number_min",
    "1",
    "Lower bound of the range of numbers",
    FCVAR_NONE,
    true, 1.0);
  cv_number_max = CreateConVar(
    "multi_giveaway_number_max",
    "100",
    "Upper bound of the range of numbers");
  cv_number_show_guesses = CreateConVar(
    "multi_giveaway_number_show_guesses",
    "2",
    "Who can see other clients' guesses: 0 - Nobody, 1 - Admins, 2 - Everyone",
    FCVAR_NONE,
    true, 0.0,
    true, 2.0);
}

void RegisterCommands()
{
  // XXX: Is there a better way to create aliases?
  // RegConsoleCmd for client commands (ie participation)
  // RegAdminCmd for admin commands (ie (start|stop)ing giveaways
  // methodmap may be helpful

  RegAdminCmd("sm_mg_reload", Command_ReloadConfig, ADMFLAG_CONFIG,
    "Reload configuration file");
  // start/stop/status <type> - style command
  RegAdminCmd("sm_multigiveaway", Command_MultiGiveaway, ADMFLAG_GENERIC,
    "Manage giveaways");
  RegAdminCmd("sm_mg", Command_MultiGiveaway, ADMFLAG_GENERIC,
    "Manage giveaways");
}

void LoadConfig()
{
  //if (configfile != null) // is this boilerplate necessary?
    //CloseHandle(configfile);
  // ...
}

// TODO: If (Source)Pawn has function overloading, implement this as GetType()
// This is sufficient. I refuse to use disgusting macro hacks.
GiveawayType Type_Str2Enum(const char[] type)
{
  if (StrEqual(type, "dice", false)) return GT_Dice;
  else if (StrEqual(type, "number", false)) return GT_Number;
  else if (StrEqual(type, "kill", false)) return GT_Kill;
  else return GT_Invalid;
}

char[] Type_Enum2Str(const GiveawayType type)
{
  switch (type)
  {
    case GT_Dice: return "Dice";
    case GT_Number: return "Number";
    case GT_Kill: return "Kill";
    case GT_Invalid: return "";
    default: return ""; // Blah, no fallthrough
  }
}

// TODO: Return something meaningful if possible, rather than void
void Giveaway_Start(const GiveawayType type, const ArrayList args)
{
  // ...
}

void Giveaway_Stop(const GiveawayType type)
{
  // ...
}

void Giveaway_Restart(const GiveawayType type)
{
  // ...
}

void Giveaway_Status()
{
  // return what giveaway is running, if any
  // do other things
}

// Forwards  ///////////////////////

public void OnPluginStart()
{
  // NOTE:
  // load our config. make use of the Kv*/KeyValue functions
  // for flag cvars, ReadFlagString(), GetUserFlagBits() and & are our friends
  // register event forwards (CreateGlobalForward())? ie kill-objectives

  RegisterCommands();
  RegisterConVars();

  char translatefile[128];
  Format(translatefile, sizeof(translatefile), "%s.phrases", PLUGIN_NAME);
  LoadTranslations(translatefile);
}

public void OnClientDisconnect(int client)
{
  // remove client from any current lists, etc
}

// Callbacks ///////////////////////

public Action Command_ReloadConfig(int client, int args)
{
  LoadConfig();
  LogAction(client, -1, "%s configuration reloaded by %L", PLUGIN_NAME, client);
  ReplyToCommand(client, "%s Configuration reloaded!", PLUGIN_TAG);
  return Plugin_Handled;
}

public Action Command_MultiGiveaway(int client, int args)
{
  // sm_mg [start|stop|restart|status] <dice|number|kill|...> [params]...

  // XXX: should we use GetCmdArgs() or args for this?
  if (!GetCmdArgs())
  {
    // interactive. open a menu
  }

  char action[32], type_str[32];

  // ???: is this a blank string if there is no arg?
  GetCmdArg(1, action, sizeof(action));
  GetCmdArg(2, type_str, sizeof(type_str));
  GiveawayType type = Type_Str2Enum(type_str);

  if (StrEqual(action, "start", false))
  {
    ArrayList typeargs = CreateArray();
    for (int i = 3; i <= GetCmdArgs(); ++i)
    {
      char arg[64];
      GetCmdArg(i, arg, sizeof(arg));
      typeargs.PushString(arg);
    }
    Giveaway_Start(type, typeargs);
  }
  else if (StrEqual(action, "stop", false))
    Giveaway_Stop(type);
  else if (StrEqual(action, "restart", false))
    Giveaway_Restart(type);
  else if (StrEqual(action, "status", false))
    Giveaway_Status();
  else
  {
    // TODO: List valid actions in error message, or inform of a help command
    ReplyToCommand(client, "%s Invalid action '%s'", PLUGIN_TAG, action);
    return Plugin_Handled;
  }
}

// vim: et sts=2 sw=2

/*
 * Copyright (c) 2016 Alex "ZeroKnight" George
 *
 * This file is licensed under the MIT License. For details, please see the
 * LICENSE file that should be included with the source code, or
 * <https://opensource.org/licenses/MIT>
 */

#include <sourcemod>

#pragma semicolon 1
#pragma newdecls required

const char PLUGIN_NAME[]    = "Multi-Giveaway";
const char PLUGIN_AUTHOR[]  = "Alex \"ZeroKnight\" George";
const char PLUGIN_DESC[]    = "Expansive Giveaway system with numerous types of Giveaway events and stats";
const char PLUGIN_VERSION[] = "0.1.0";
const char PLUGIN_URL[]     = "http:/github.com/ZeroKnight/sm-Multi-Giveaway";
const char PLUGIN_TAG[]     = "[MG]";

public Plugin myinfo =
{
  name        = PLUGIN_NAME,
  author      = PLUGIN_AUTHOR,
  description = PLUGIN_DESC,
  version     = PLUGIN_VERSION,
  url         = PLUGIN_URL
};

enum GiveawayType { GT_Invalid = -1, GT_Dice = 1, GT_Number = 2, GT_Kill = 4, GT_All = 7 };

//File configfile = ...

// TODO: Put things like this in a personal core library?
#define CVAR(%1) ConVar cv_%1;
const char CVAR_PREFIX[] = "multi_giveaway";
CVAR("version");
CVAR("flags");
CVAR("enaled_types");
CVAR("player_state");
CVAR("min_conn_time");
CVAR("dice_min");
CVAR("dice_max");
CVAR("dice_show_rolls");
CVAR("dice_rerolls");
CVAR("number_min");
CVAR("number_max");
CVAR("number_show_guesses");

void RegisterConVars()
{
  // XXX: Why is CreateConVar() not overloaded?! Are there even overloads?!
  const char sGT_All[32]; IntToString(GT_All, sGT_All, sizeof(sGT_All));

  cv_version = CreateConVar(
    "multi_givaway_version",
    PLUGIN_VERSION,
    "Plugin version",
    FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD);
  cv_flags = CreateConVar(
    "multi_givaway_flags",
    "b",
    "List of admin flags allowed to manage giveaways");
  cv_enabled_types = CreateConVar(
    "multi_giveaway_enabled_types",
    sGT_All,
    "Bit-field (sum of options) of enabled Giveaway types",
    true, 1.0,
    true, GT_All);
  cv_player_state = CreateConVar(
    "multi_giveaway_player_state",
    "7",
    "Bit-field (sum of options) of player states that are valid for giveaway participation: 1 - Spectator, 2 - Dead, 4 - Alive"
    true, 1.0,
    true, 7.0);
  cv_min_conn_time = CreateConVar( // TODO: Allow certain giveaways to override (eg. kill objective)
    "multi_giveaway_min_conn_time",
    "300", // 5 minutes
    "Amount of time in seconds a player must be connected to participate in a giveaway",
    true, 0.0);

  cv_dice_min = CreateConVar(
    "multi_giveaway_dice_min",
    "1",
    "Lowest possible number a player may roll",
    true, 1.0);
  cv_dice_max = CreateConVar(
    "multi_giveaway_dice_max",
    "50",
    "Highest possible number a player may roll");
  cv_dice_show_rolls = CreateConVar(
    "multi_giveaway_dice_show_rolls",
    "0",
    "Who can see other clients' dice rolls: 0 - Nobody, 1 - Admins, 2 - Everyone",
    true, 0.0
    true, 2.0);
  cv_dice_rerolls = CreateConVar(
    "multi_giveaway_dice_reroll",
    "0",
    "Number of 'do-overs' a player gets when rolling. Only the last is counted, for better or for worse");

  cv_number_min = CreateConVar(
    "multi_giveaway_number_min",
    "1",
    "Lower bound of the range of numbers",
    true, 1.0);
  cv_number_max = CreateConVar(
    "multi_giveaway_number_max",
    "100",
    "Upper bound of the range of numbers");
  cv_number_show_guesses = CreateConVar(
    "multi_giveaway_number_show_guesses",
    "2",
    "Who can see other clients' guesses: 0 - Nobody, 1 - Admins, 2 - Everyone",
    true, 0.0,
    true, 2.0);
}

void RegisterCommands()
{
  // XXX: Is there a better way to create aliases?
  // RegConsoleCmd for client commands (ie participation)
  // RegAdminCmd for admin commands (ie (start|stop)ing giveaways
  // methodmap may be helpful

  RegAdminCmd("sm_mg_reload", Command_ReloadConfig, ADMFLAG_CONFIG,
    "Reload configuration file");
  // start/stop/status <type> - style command
  RegAdminCmd("sm_multigiveaway", Command_MultiGiveaway, ADMFLAG_GENERIC,
    "Manage giveaways");
  RegAdminCmd("sm_mg", Command_MultiGiveaway, ADMFLAG_GENERIC,
    "Manage giveaways");
}

void LoadConfig()
{
  if (configfile != null) // is this boilerplate necessary?
    CloseHandle(configfile);
  // ...
}

// TODO: If (Source)Pawn has function overloading, implement this as GetType()
// This is sufficient. I refuse to use disgusting macro hacks.
GiveawayType Type_Str2Enum(const char[] type)
{
  if (StrEqual(type, "dice", false)) return GT_Dice;
  else if (StrEqual(type, "number", false)) return GT_Number;
  else if (StrEqual(type, "kill", false)) return GT_Kill;
  else return GT_Invalid;
}

const char[] Type_Enum2Str(const GiveawayType type)
{
  switch (type)
  {
    case GT_Dice: return "Dice";
    case GT_Number: return "Number";
    case GT_Kill: return "Kill";
    case GT_Invalid: return "";
    default: return ""; // Blah, no fallthrough
  }
}

// TODO: Return something meaningful if possible, rather than void
void Giveaway_Start(const GiveawayType type, const ArrayList args)
{
  // ...
}

void Giveaway_Stop(const GiveawayType type)
{
  // ...
}

void Giveaway_Restart(const GiveawayType type)
{
  // ...
}

void Giveaway_Status()
{
  // return what giveaway is running, if any
  // do other things
}

// Forwards  ///////////////////////

public void OnPluginStart()
{
  // NOTE:
  // load our config. make use of the Kv*/KeyValue functions
  // for flag cvars, ReadFlagString(), GetUserFlagBits() and & are our friends
  // register event forwards (CreateGlobalForward())? ie kill-objectives

  RegisterCommands();
  RegisterConVars();

  const char translatefile[128];
  Format(translatefile, sizeof(translatefile), "%s.phrases", PLUGIN_NAME);
  LoadTranslations(translatefile);

  return Plugin_Handled;
}

public void OnClientDisconnect(int client)
{
  // remove client from any current lists, etc
}

// Callbacks ///////////////////////

public Action Command_ReloadConfig(int client, int args)
{
  LoadConfig();
  LogAction(client, -1, "%s configuration reloaded by %L", PLUGIN_NAME, client);
  ReplyToCommand(client, "%s Configuration reloaded!", PLUGIN_TAG);
  return Plugin_Handled;
}

public Action Command_MultiGiveaway(int client, int args)
{
  // sm_mg [start|stop|restart|status] <dice|number|kill|...> [params]...

  // XXX: should we use GetCmdArgs() or args for this?
  if (!GetCmdArgs())
  {
    // interactive. open a menu
  }

  char action[32], typestr[32];

  // ???: is this a blank string if there is no arg?
  GetCmdArgString(1, action, sizeof(action));
  GetCmdArgString(2, type_str, sizeof(type_str));
  GiveawayType type = Type_Str2Enum(type_str);

  if (StrEqual(action, "start", false))
  {
    ArrayList typeargs = CreateArray();
    for (i = 3; i <= GetCmdArgs(); ++i)
    {
      char arg[64];
      GetCmdArgString(i, arg, sizeof(arg));
      typeargs.Push(arg);
    }
    Giveaway_Start(type, typeargs);
  }
  else if (StrEqual(action, "stop", false))
    Giveaway_Stop(type);
  else if (StrEqual(action, "restart", false))
    Giveaway_Restart(type);
  else if (StrEqual(action, "status", false))
    Giveaway_Status(type);
  else
  {
    // TODO: List valid actions in error message, or inform of a help command
    ReplyToCommand(client, "%s Invalid action '%s'", PLUGIN_TAG, action);
    return Plugin_Handled;
  }
}

// vim: et sts=2 sw=2


fixed crossfade patch

fixed crossfade patch

# Simple Crossfade example, using port filters and switches

# input 1
440 0.5 sine

# input 2
550 0.5 sine

# s s

1 metro 0 1 switch 0.005 port dup

# s s t t

rot mul

# s t t s
# s t m

rot rot

# t m s
# m s t

1 swap sub mul

# m s t 1
# m s 1 t
# m s (1 - t)
# m s * (1 - t)

add

# m1 +  m2

# Simple Crossfade example, using port filters and switches

# input 1
440 0.5 sine

# input 2
550 0.5 sine

# s s

0 1 1 metro switch 0.005 port dup

# s s t t

rot mul

# s t t s
# s t m

rot rot

# t m s
# m s t

1 swap sub mul

# m s t 1
# m s 1 t
# m s (1 - t)
# m s * (1 - t)

add

# m1 +  m2