#include <sourcemod>
#include <sdkhooks>
#include <sdktools>
#include <multicolors>
#include <zombiereloaded>

#pragma semicolon 1
#pragma newdecls required

/* CONVARS */
ConVar g_hCVar_CollectablesEnabled;
ConVar g_hCVar_RandomIntervalMin;
ConVar g_hCVar_RandomIntervalMax;
ConVar g_hCVar_InfectionEffectEnabled;
ConVar g_hCVar_MilestoneInfection;
ConVar g_hCVar_MilestoneGrenade;
ConVar g_hCVar_MilestoneSkin;
ConVar g_hCVar_HighscoreDisplay;
ConVar g_hCVar_PlayerRequirement;
ConVar g_hCVar_EntityLimit;

/* DATABASE */
Database g_hDatabase;

/* BOOLS */
bool g_bEnabled = true;
bool g_bPreAdminChecked[MAXPLAYERS+1];
bool g_bResponseFailed[MAXPLAYERS+1];
bool g_bResponsePassed[MAXPLAYERS+1];

/* INTEGERS */
int g_iCollected[MAXPLAYERS+1];
int g_iCounter = 0;

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
	name        = "UNLOZE Season Event (XMAS)",
	author      = "Neon",
	description = "UNLOZE Season Event (XMAS)",
	version     = "2.1",
	url         = "https://steamcommunity.com/id/n3ontm"
};

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
	g_hCVar_CollectablesEnabled 	= CreateConVar("sm_unloze_season_collectables_enabled", "1", "Spawn Collectables.", 0, true, 0.0, true, 1.0);
	g_hCVar_RandomIntervalMin 		= CreateConVar("sm_unloze_season_random_interval_min", "60", "Minimum Interval between spawning Collectables.", 0, true, 0.0);
	g_hCVar_RandomIntervalMax 		= CreateConVar("sm_unloze_season_random_interval_max", "120", "Maximum Interval between spawning Collectables.", 0, true, 0.0);
	g_hCVar_InfectionEffectEnabled 	= CreateConVar("sm_unloze_season_infection_effect_enabled", "1", "Spawn Props on Infection.", 0, true, 0.0, true, 1.0);
	g_hCVar_MilestoneInfection 		= CreateConVar("sm_unloze_season_milestone_infection", "25", "Amount of Collectables you need to unlock the Infection Effect.", 0, true, 0.0);
	g_hCVar_MilestoneGrenade 		= CreateConVar("sm_unloze_season_milestone_grenade", "75", "Amount of Collectables you need to unlock the Grenade Skin.", 0, true, 0.0);
	g_hCVar_MilestoneSkin 			= CreateConVar("sm_unloze_season_milestone_skin", "150", "Amount of Collectables you need to unlock the Skin(s).", 0, true, 0.0);
	g_hCVar_HighscoreDisplay 		= CreateConVar("sm_unloze_season_highscore_display", "5", "Amount of Players to display via sm_highscore", 0, true, 0.0);
	g_hCVar_PlayerRequirement		= CreateConVar("sm_unloze_season_player_requirement", "10", "Amount of Players needed to spawn Collectables.", 0, true, 0.0);
	g_hCVar_EntityLimit				= CreateConVar("sm_unloze_season_entity_limit", "2000", "Entity Safety Limit.", 0, true, 0.0);

	HookEvent("round_start", OnRoundStart, EventHookMode_Post);

	RegConsoleCmd("sm_presents", Command_Collected, "Shows the total amount of Presents you have collected so far");
	RegConsoleCmd("sm_xmas", Command_Collected, "Shows the total amount of Presents you have collected so far");
	RegConsoleCmd("sm_highscore", Command_HighScore, "Shows the Present HighScore");

	AutoExecConfig();
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnMapStart()
{
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.phy");
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.sw.vtx");
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.vvd");
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.dx80.vtx");
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.dx90.vtx");
	AddFileToDownloadsTable("models/player/stenli/smite/fenrir.mdl");
	AddFileToDownloadsTable("materials/models/player/stenli/smite/fenrir/body.vmt");
	AddFileToDownloadsTable("materials/models/player/stenli/smite/fenrir/body.vtf");
	AddFileToDownloadsTable("materials/models/player/stenli/smite/fenrir/body_n.vtf");
	AddFileToDownloadsTable("materials/models/player/stenli/smite/fenrir/body_s.vtf");
	AddFileToDownloadsTable("materials/models/player/stenli/smite/fenrir/lights.vmt");
	PrecacheModel("models/player/stenli/smite/fenrir.mdl");

	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.vvd");
	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.dx80.vtx");
	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.dx90.vtx");
	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.mdl");
	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.phy");
	AddFileToDownloadsTable("models/player/vad36lollipop/lolli_new.sw.vtx");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_skin_d_cos11.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_skin_n_cos11.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/hat.vmt");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/hat.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/hat_n.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_cloth.vmt");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_cloth.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_cloth_n.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_hair_d_cos11.vmt");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_hair_d_cos11.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_hair_n_cos11.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36lollipop/tx_ch_main_juliet_skin_d_cos11.vmt");
	PrecacheModel("models/player/vad36lollipop/lolli_new.mdl");

	AddFileToDownloadsTable("models/player/vad36santa/red.dx80.vtx");
	AddFileToDownloadsTable("models/player/vad36santa/red.dx90.vtx");
	AddFileToDownloadsTable("models/player/vad36santa/red.mdl");
	AddFileToDownloadsTable("models/player/vad36santa/red.phy");
	AddFileToDownloadsTable("models/player/vad36santa/red.sw.vtx");
	AddFileToDownloadsTable("models/player/vad36santa/red.vvd");
	AddFileToDownloadsTable("materials/models/player/vad36santa/Santa_N.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36santa/Santa_D.vmt");
	AddFileToDownloadsTable("materials/models/player/vad36santa/Santa_D.vtf");
	AddFileToDownloadsTable("materials/models/player/vad36santa/Santa_D_B.vmt");
	AddFileToDownloadsTable("materials/models/player/vad36santa/Santa_D_B.vtf");
	PrecacheModel("models/player/vad36santa/red.mdl");

	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.dx80.vtx");
	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.dx90.vtx");
	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.mdl");
	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.phy");
	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.sw.vtx");
	AddFileToDownloadsTable("models/zombieden/xmas/giftbox.vvd");
	AddFileToDownloadsTable("materials/models/zombieden/xmas/gift.vmt");
	AddFileToDownloadsTable("materials/models/zombieden/xmas/gift.vtf");
	AddFileToDownloadsTable("materials/models/zombieden/xmas/gift_2.vmt");
	AddFileToDownloadsTable("materials/models/zombieden/xmas/gift_2.vtf");
	AddFileToDownloadsTable("materials/models/zombieden/xmas/gift_lightwarp.vtf");
	PrecacheModel("models/zombieden/xmas/giftbox.mdl");

	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.dx80.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.dx90.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.mdl");
	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.phy");
	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.sw.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmastree_mini.vvd");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca_skin2.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca_skin2.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_skin2.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_skin2.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_spec.vtf");
	PrecacheModel("models/models_kit/xmas/xmastree_mini.mdl");

	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.dx80.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.dx90.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.mdl");
	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.phy");
	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.sw.vtx");
	AddFileToDownloadsTable("models/models_kit/xmas/xmas_teddybear.vvd");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca_skin2.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_misca_skin2.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_skin2.vmt");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_skin2.vtf");
	AddFileToDownloadsTable("materials/models/models_kit/xmas/xmastree_miscb_spec.vtf");
	PrecacheModel("models/models_kit/xmas/xmas_teddybear.mdl");

	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.dx80.vtx");
	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.dx90.vtx");
	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.mdl");
	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.phy");
	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.sw.vtx");
	AddFileToDownloadsTable("models/weapons/w_revenge_xmas_candy.vvd");
	AddFileToDownloadsTable("materials/models/weapons/v_models/xmas_candy/cane.vmt");
	AddFileToDownloadsTable("materials/models/weapons/v_models/xmas_candy/cane.vtf");
	PrecacheModel("models/weapons/w_revenge_xmas_candy.mdl");

	AddFileToDownloadsTable("models/johny-srka/snowman.dx80.vtx");
	AddFileToDownloadsTable("models/johny-srka/snowman.dx90.vtx");
	AddFileToDownloadsTable("models/johny-srka/snowman.mdl");
	AddFileToDownloadsTable("models/johny-srka/snowman.phy");
	AddFileToDownloadsTable("models/johny-srka/snowman.sw.vtx");
	AddFileToDownloadsTable("models/johny-srka/snowman.vvd");
	AddFileToDownloadsTable("materials/models/johny-srka/black_felt.vmt");
	AddFileToDownloadsTable("materials/models/johny-srka/black_felt.vtf");
	AddFileToDownloadsTable("materials/models/johny-srka/fabricpatterns0017_m.vmt");
	AddFileToDownloadsTable("materials/models/johny-srka/fabricpatterns0017_m.vtf");
	AddFileToDownloadsTable("materials/models/johny-srka/snowfloor002a.vmt");
	AddFileToDownloadsTable("materials/models/johny-srka/snowfloor002a.vtf");
	AddFileToDownloadsTable("materials/models/johny-srka/wood_old.vmt");
	AddFileToDownloadsTable("materials/models/johny-srka/wood_old.vtf");
	PrecacheModel("models/johny-srka/snowman.mdl");

	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.dx80.vtx");
	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.dx90.vtx");
	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.mdl");
	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.phy");
	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.sw.vtx");
	AddFileToDownloadsTable("models/weapons/w_snowball_thrown.vvd");
	AddFileToDownloadsTable("materials/models/weapons/v_models/snooball/s.vmt");
	AddFileToDownloadsTable("materials/models/weapons/v_models/snooball/s.vtf");
	AddFileToDownloadsTable("materials/models/weapons/v_models/snooball/s_norm.vtf");
	PrecacheModel("models/weapons/w_snowball_thrown.mdl");

	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.dx80.vtx");
	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.dx90.vtx");
	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.mdl");
	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.phy");
	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.sw.vtx");
	AddFileToDownloadsTable("models/weapons/w_ornament_thrown.vvd");
	AddFileToDownloadsTable("materials/models/weapons/v_models/ornament/ornament.vmt");
	AddFileToDownloadsTable("materials/models/weapons/v_models/ornament/ornament.vtf");
	AddFileToDownloadsTable("materials/models/weapons/v_models/ornament/reflect.vtf");
	PrecacheModel("models/weapons/w_ornament_thrown.mdl");

	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.dx80.vtx");
	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.dx90.vtx");
	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.mdl");
	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.phy");
	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.sw.vtx");
	AddFileToDownloadsTable("models/weapons/w_santa_hat_thrown.vvd");
	AddFileToDownloadsTable("materials/models/weapons/v_models/eq_fraggrenade/bonnet.vmt");
	AddFileToDownloadsTable("materials/models/weapons/v_models/eq_fraggrenade/bonnet.vtf");
	PrecacheModel("models/weapons/w_santa_hat_thrown.mdl");

	AddFileToDownloadsTable("sound/unl1/season/hohohoho.wav");
	PrecacheSound("sound/unl1/season/hohohoho.wav");


	float fRandomInterval = GetRandomFloat(GetConVarFloat(g_hCVar_RandomIntervalMin), GetConVarFloat(g_hCVar_RandomIntervalMax));
	CreateTimer(fRandomInterval, SpawnCollectable, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnConfigsExecuted()
{
    if (!g_hDatabase)
    {
        Database.Connect(SQL_OnDatabaseConnect, "season");
    }
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
	if(!db || strlen(error))
	{
		LogError("Database error: %s", error);
		return;
	}

	g_hDatabase = db;

	char sQuery[256];
	Format(sQuery, sizeof(sQuery), "CREATE TABLE IF NOT EXISTS xmas_table (`steam_auth` varchar(64), `name` varchar(256), `collected` int(16), PRIMARY KEY (`steam_auth`), INDEX (`collected`))");

	g_hDatabase.Query(SQL_OnTableCreated, sQuery, _, DBPrio_Low);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnTableCreated(Database db, DBResultSet results, const char[] error, any data)
{
	if(!db || strlen(error))
	{
		LogError("Database error: %s", error);
		return;
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnRebuildAdminCache(AdminCachePart part)
{
	if (part != AdminCache_Admins)
		return;

	CreateTimer(1.0, OnRebuildAdminCachePost, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnRebuildAdminCachePost(Handle hTimer)
{
	for (int client = 1; client <= MaxClients; client++)
	{
		if(g_bResponsePassed[client] && g_bPreAdminChecked[client])
			CheckAndAddFlag(client);
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientConnected(int client)
{
	g_bPreAdminChecked[client] = false;
	g_bResponseFailed[client] = false;
	g_bResponsePassed[client] = false;

	g_iCollected[client] = 0;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
	g_bPreAdminChecked[client] = false;
	g_bResponseFailed[client] = false;
	g_bResponsePassed[client] = false;

	g_iCollected[client] = 0;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientAuthorized(int client, const char[] sSteamID32)
{
	if (IsFakeClient(client))
		return;

	char sSteamID[32];
	GetClientAuthId(client, AuthId_Steam2, sSteamID, sizeof(sSteamID));

	char sQuery[256];
	Format(sQuery, sizeof(sQuery), "SELECT collected FROM xmas_table WHERE steam_auth = '%s'", sSteamID);
	g_hDatabase.Query(SQL_OnQueryCompletedFetch, sQuery, GetClientSerial(client));
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnQueryCompletedFetch(Database db, DBResultSet results, const char[] error, int iSerial)
{
	int client;
	if ((client = GetClientFromSerial(iSerial)) == 0)
		return;

	if (!db || strlen(error))
	{
		LogError("Query error: %s", error);
		return;
	}

	if (results.RowCount && results.FetchRow())
	{
		int iFieldNum;

		results.FieldNameToNum("collected", iFieldNum);
		g_iCollected[client] = results.FetchInt(iFieldNum);
	}
	else
		g_iCollected[client] = 0;

	g_bResponsePassed[client] = true;
	if (g_bPreAdminChecked[client])
		NotifyPostAdminCheck(client);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnClientPreAdminCheck(int client)
{
	g_bPreAdminChecked[client] = true;

	if (g_bResponsePassed[client] || g_bResponseFailed[client])
		return Plugin_Continue;

	RunAdminCacheChecks(client);
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPostAdminFilter(int client)
{
	CheckAndAddFlag(client);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
	if (!g_hCVar_CollectablesEnabled.BoolValue)
		return;

	g_iCounter = 0;
	CreateTimer(10.0, CheckPlayerCount, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action CheckPlayerCount(Handle timer)
{
	g_bEnabled = true;
	if (GetClientCount(true) < g_hCVar_PlayerRequirement.IntValue)
	{
		g_bEnabled = false;
		CPrintToChatAll("{green}[UNLOZE XMAS] {white}Minimum Player Requirement to spawn Presents: {green}%d {white} - Currently online:  {red}%d{white}.", g_hCVar_PlayerRequirement.IntValue, GetClientCount(true));
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_HighScore(int client, int args)
{
	char sQuery[255];
	Format(sQuery, sizeof(sQuery), "SELECT * from xmas_table order by collected desc limit %d", g_hCVar_HighscoreDisplay.IntValue);
	g_hDatabase.Query(SQL_OnQueryCompletedHighscore, sQuery, GetClientSerial(client));
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnQueryCompletedHighscore(Database db, DBResultSet results, const char[] error, int iSerial)
{
	int client;
	if ((client = GetClientFromSerial(iSerial)) == 0)
		return;

	if (!db || strlen(error))
	{
		LogError("Query error: %s", error);
		return;
	}

	char sName[MAX_NAME_LENGTH];
	char sBuffer[2048] = "{green}[UNLOZE XMAS] {white}TOP COLLECTORS:\n";
	char sTempBuffer[1024] = "";

	for(int i = 1; i <= g_hCVar_HighscoreDisplay.IntValue; i++)
	{
		int iFieldNum;
		if (!results.FetchRow())
			break;

		results.FieldNameToNum("name", iFieldNum);
		results.FetchString(iFieldNum, sName, sizeof(sName));

		results.FieldNameToNum("collected", iFieldNum);
		int iCollected = results.FetchInt(iFieldNum);

		Format(sTempBuffer, sizeof(sTempBuffer), "{green}%d: %s - {red}%d \n", i, sName, iCollected);
		StrCat(sBuffer, sizeof(sBuffer), sTempBuffer);
	}

	CPrintToChat(client, sBuffer);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_Collected(int client, int args)
{
	char sSteamID[32];
	GetClientAuthId(client, AuthId_Steam2, sSteamID, sizeof(sSteamID));

	char sQuery[256];
	Format(sQuery, sizeof(sQuery), "SELECT collected FROM xmas_table WHERE steam_auth = '%s'", sSteamID);
	g_hDatabase.Query(SQL_OnQueryCompletedCheck, sQuery, GetClientSerial(client));
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnQueryCompletedCheck(Database db, DBResultSet results, const char[] error, int iSerial)
{
	int client;
	if ((client = GetClientFromSerial(iSerial)) == 0)
		return;

	if (!db || strlen(error))
	{
		LogError("Query error: %s", error);
		return;
	}

	if (results.RowCount && results.FetchRow())
	{
		int iFieldNum;

		results.FieldNameToNum("collected", iFieldNum);
		g_iCollected[client] = results.FetchInt(iFieldNum);
	}
	else
		g_iCollected[client] = 0;

	CPrintToChat(client, "{green}[UNLOZE XMAS] {white}You have collected {green}%d {white}presents so far.", g_iCollected[client]);

	if ((g_iCollected[client] > g_hCVar_MilestoneInfection.IntValue) && (g_iCollected[client] > g_hCVar_MilestoneSkin.IntValue))
		CPrintToChat(client, "{green}[UNLOZE XMAS] {white}You have unlocked {red}all rewards{white} already.");
	if (g_iCollected[client] < g_hCVar_MilestoneInfection.IntValue)
		CPrintToChat(client, "{green}[UNLOZE XMAS] {white}You need to collect {green}%d {white}more presents to unlock {red}INFECTION EFFECTS{white}.", g_hCVar_MilestoneInfection.IntValue - g_iCollected[client]);
	if (g_iCollected[client] < g_hCVar_MilestoneGrenade.IntValue)
		CPrintToChat(client, "{green}[UNLOZE XMAS] {white}You need to collect {green}%d {white}more presents to unlock {red}GRENADE SKINS{white}.", g_hCVar_MilestoneGrenade.IntValue - g_iCollected[client]);
	if (g_iCollected[client] < g_hCVar_MilestoneSkin.IntValue)
		CPrintToChat(client, "{green}[UNLOZE XMAS] {white}You need to collect {green}%d {white}more presents to unlock {red}XMAS PLAYER SKINS{white}.", g_hCVar_MilestoneSkin.IntValue - g_iCollected[client]);

}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action SpawnCollectable(Handle timer)
{
	float fRandomInterval = GetRandomFloat(g_hCVar_RandomIntervalMin.FloatValue, g_hCVar_RandomIntervalMax.FloatValue);
	CreateTimer(fRandomInterval, SpawnCollectable, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);

	if (!(g_hCVar_CollectablesEnabled.BoolValue) || !(g_bEnabled))
		return;

	int iTarget = GetTargetClient();

	if (!IsValidClient(iTarget, false))
		return;

	float fOrigin[3];
	GetClientAbsOrigin(iTarget, fOrigin);

	// Rotating
	int iRotating = CreateEntityAtOrigin("func_rotating", fOrigin);
	DispatchKeyFormat(iRotating, "targetname",	"season_rotating_%d", g_iCounter);
	DispatchKeyFormat(iRotating, "maxspeed",		"20");
	DispatchKeyFormat(iRotating, "spawnflags",	"65");
	SpawnAndActivate(iRotating);

	// make the trigger work.
	SetEntityBBox(iRotating, view_as<float>({-10.0, -1.0, -1.0}), view_as<float>({1.0, 1.0, 1.0}));
	SetEntityProps(iRotating);

	// Model
	int iModel  = CreateEntityAtOrigin("prop_dynamic_override", fOrigin);
	DispatchKeyFormat(iModel, "targetname", 			"season_prop_%d", g_iCounter);
	DispatchKeyFormat(iModel, "model", 				"models/zombieden/xmas/giftbox.mdl");
	DispatchKeyFormat(iModel, "modelscale", 			"0.8");
	DispatchKeyFormat(iModel, "disablebonefollowers",	"1");
	SpawnAndActivate(iModel);
	ParentToEntity(iModel, iRotating);

	int iRandomSkin = GetRandomInt(0, 1);
	if (iRandomSkin == 0)
		SetVariantString("0");
	else if (iRandomSkin == 1)
		SetVariantString("1");
	AcceptEntityInput(iModel, "Skin");


	// Particle
	int iParticle  = CreateEntityAtOrigin("info_particle_system", fOrigin);
	DispatchKeyFormat(iParticle, "targetname",		"season_particle_%d", g_iCounter);
	DispatchKeyFormat(iParticle, "effect_name",		"achieved");
	SpawnAndActivate(iParticle);
	ParentToEntity(iParticle, iRotating);


	// Trigger
	int iTrigger = CreateEntityAtOrigin("trigger_multiple", fOrigin);
	DispatchKeyFormat(iTrigger, "targetname",			"season_trigger_%d", g_iCounter);
	DispatchKeyFormat(iTrigger, "spawnflags",			"1");
	DispatchKeyFormat(iTrigger, "startdisabled",		"1");
	DispatchKeyFormat(iTrigger, "OnUser1",			"season_hitbox_%d,FireUser2,,0,1", g_iCounter);
	SpawnAndActivate(iTrigger);
	ParentToEntity(iTrigger, iRotating);

	// make the trigger work.
	SetEntityBBox(iTrigger, view_as<float>({-16.0, -16.0, -1.0}), view_as<float>({16.0, 16.0, 32.0}));
	SetEntityProps(iTrigger);

	HookSingleEntityOutput(iTrigger, "OnStartTouch", HookCallbackTrigger, false);


	// Ambient
	int iSound  = CreateEntityAtOrigin("ambient_generic", fOrigin);
	DispatchKeyFormat(iSound, "targetname",		"season_sound_%d", g_iCounter);
	DispatchKeyFormat(iSound, "spawnflags",		"49");
	DispatchKeyFormat(iSound, "radius",			"2000");
	DispatchKeyFormat(iSound, "message",		"unl1/season/hohohoho.wav");
	DispatchKeyFormat(iSound, "volume",			"10");
	DispatchKeyFormat(iSound, "health",			"10");
	DispatchKeyFormat(iSound, "pitch",			"100");
	DispatchKeyFormat(iSound, "pitchstart",		"100");
	SpawnAndActivate(iSound);
	ParentToEntity(iSound, iRotating);


	// Hitbox
	int iHitbox  = CreateEntityAtOrigin("func_physbox_multiplayer", fOrigin);
	DispatchKeyFormat(iHitbox, "targetname",				"season_hitbox_%d", g_iCounter);
	DispatchKeyFormat(iHitbox, "model",					"models/zombieden/xmas/giftbox.mdl");
	DispatchKeyFormat(iHitbox, "modelscale",				"0.8");
	DispatchKeyFormat(iHitbox, "disableshadows",			"1");
	DispatchKeyFormat(iHitbox, "disablereceiveshadows",	"1");
	DispatchKeyFormat(iHitbox, "DisableBoneFollowers",	"1");
	DispatchKeyFormat(iHitbox, "rendermode",				"10");
	DispatchKeyFormat(iHitbox, "PerformanceMode",			"1");
	DispatchKeyFormat(iHitbox, "material",				"3");
	DispatchKeyFormat(iHitbox, "health",					"200");
	DispatchKeyFormat(iHitbox, "physdamagescale",			"1.0");
	DispatchKeyFormat(iHitbox, "OnBreak",					"season_rotating_%d,KillHierarchy,,2.5,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnBreak",					"season_particle_%d,Start,,0,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnBreak",					"season_sound_%d,PlaySound,,0,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnBreak",					"season_sound_%d,Kill,,2.4,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnUser1",					"season_rotating_%d,KillHierarchy,,59.0,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnUser1",					"season_sound_%d,Kill,,59.0,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnUser2",					"season_rotating_%d,KillHierarchy,,0,1", g_iCounter);
	DispatchKeyFormat(iHitbox, "OnUser2",					"season_sound_%d,Kill,,0,1", g_iCounter);
	SpawnAndActivate(iHitbox);
	ParentToEntity(iHitbox, iRotating);

	HookSingleEntityOutput(iHitbox, "OnBreak", HookCallback, true);


	AcceptEntityInput(iHitbox, "FireUser1");
	AcceptEntityInput(iTrigger, "Enable");

	int iEntityLimit = g_hCVar_EntityLimit.IntValue;

	if ((iRotating > iEntityLimit) || (iParticle > iEntityLimit) || (iModel > iEntityLimit) || (iHitbox > iEntityLimit) || (iTrigger > iEntityLimit) || (iSound > iEntityLimit))
	{
		AcceptEntityInput(iHitbox, "FireUser2");
		CPrintToChatAll("{green}[Unloze XMAS] {white}Present removed due to {red}critical amount of entities{white}!");
	}

	g_iCounter += 1;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int GetTargetClient()
{
	int iEligibleClients[MAXPLAYERS+1];
	int iClientCount = 0;

	for(int i = 1; i <= MaxClients; i++)
	{
		if(IsClientInGame(i) && IsPlayerAlive(i) && (ZR_IsClientHuman(i)))
		{
			iEligibleClients[iClientCount] = i;
			iClientCount += 1;
		}
	}

	if (iClientCount == 0)
		return -1;

	int randomIndex = GetRandomInt(0, iClientCount - 1);
	return iEligibleClients[randomIndex];
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void HookCallbackTrigger(const char[] output, int caller, int activator, float delay)
{
	if (ZR_IsClientZombie(activator))
	{
		UnhookSingleEntityOutput(caller, "OnStartTouch", HookCallbackTrigger);
		AcceptEntityInput(caller, "FireUser1");
		CPrintToChatAll("{green}[UNLOZE XMAS] {white}Zombies {red}destroyed{white} a present!");
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void HookCallback(const char[] output, int caller, int activator, float delay)
{
	for (int client = 1; client <= MaxClients; client++)
	{
		if (IsValidClient(client) && !IsClientSourceTV(client) && IsPlayerAlive(client) && ZR_IsClientHuman(client))
		{
			char sSteamID[32];
			GetClientAuthId(client, AuthId_Steam2, sSteamID, sizeof(sSteamID));

			char sName[MAX_NAME_LENGTH];
			GetClientName(client, sName, sizeof(sName));
			char sSafeName[(2*MAX_NAME_LENGTH)+1];
			g_hDatabase.Escape(sName, sSafeName, sizeof(sSafeName));

			char sQuery[256];
			Format(sQuery, sizeof(sQuery), "INSERT INTO xmas_table (steam_auth,name,collected) VALUES ('%s','%s',1) ON DUPLICATE KEY UPDATE collected=collected+1;", sSteamID, sSafeName);
			g_hDatabase.Query(SQL_OnQueryCompletedUpdate, sQuery);

			g_iCollected[client] += 1;
			CheckAndAddFlag(client);

			CPrintToChat(client, "{green}[UNLOZE XMAS] {white}Your Team opened a present! You have collected {green}%d {white}presents so far.", g_iCollected[client]);
			if (g_iCollected[client] == g_hCVar_MilestoneInfection.IntValue)
				CPrintToChat(client, "{green}[UNLOZE XMAS] {white}Congratulations! You have unlocked {red}INFECTION EFFECTS{white}!");

			if (g_iCollected[client] == g_hCVar_MilestoneGrenade.IntValue)
				CPrintToChat(client, "{green}[UNLOZE XMAS] {white}Congratulations! You have unlocked {red}GRENADE SKINS{white}!");

			if (g_iCollected[client] == g_hCVar_MilestoneSkin.IntValue)
				CPrintToChat(client, "{green}[UNLOZE XMAS] {white}Congratulations! You have unlocked {red}XMAS SKINS{white}!");
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_OnQueryCompletedUpdate(Database db, DBResultSet results, const char[] error, any data)
{
	if (!db || strlen(error))
	{
		LogError("Query error: %s", error);
		return;
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn)
{
	if (!g_hCVar_InfectionEffectEnabled.BoolValue)
		return;

	if (!IsValidClient(attacker))
		return;

	if (g_iCollected[client] >= g_hCVar_MilestoneInfection.IntValue || g_iCollected[attacker] >= g_hCVar_MilestoneInfection.IntValue)
	{
		float fInfectionOrigin[3];
		GetClientAbsOrigin(client, fInfectionOrigin);

		// Rotating
		int iRotating  = CreateEntityAtOrigin("func_rotating", fInfectionOrigin);
		DispatchKeyFormat(iRotating, "targetname", 		"season_infection_rotating_%d", g_iCounter);
		DispatchKeyFormat(iRotating, "maxspeed", 			"13");
		DispatchKeyFormat(iRotating, "spawnflags", 		"65");
		DispatchKeyFormat(iRotating, "OnUser1", 			"!self,KillHierarchy,,45,1");
		DispatchKeyFormat(iRotating, "OnUser2", 			"!self,KillHierarchy,,0,1");
		SpawnAndActivate(iRotating);

		// make the trigger work.
		SetEntityBBox(iRotating, view_as<float>({-10.0, -1.0, -1.0}), view_as<float>({1.0, 1.0, 1.0}));
		SetEntityProps(iRotating);


		int iModel = CreateEntityAtOrigin("prop_dynamic_override", fInfectionOrigin);
		DispatchKeyFormat(iModel, "targetname",	"season_infection_prop_%d", g_iCounter);

		int iRandomSkin = GetRandomInt(0, 4);
		if (iRandomSkin == 0)
		{
			DispatchKeyFormat(iModel, "model",		"models/models_kit/xmas/xmastree_mini.mdl");
			DispatchKeyFormat(iModel, "modelscale",	"0.35");
			DispatchKeyFormat(iModel, "angles",		"0 0 0");
		}
		else if (iRandomSkin == 1)
		{
			DispatchKeyFormat(iModel, "model",		"models/weapons/w_revenge_xmas_candy.mdl");
			DispatchKeyFormat(iModel, "modelscale",	"1.2");
			DispatchKeyFormat(iModel, "angles",		"-27 0 0");
		}
		else if (iRandomSkin == 2)
		{
			DispatchKeyFormat(iModel, "model",		"models/johny-srka/snowman.mdl");
			DispatchKeyFormat(iModel, "modelscale",	"0.45");
			DispatchKeyFormat(iModel, "angles",		"0 0 0");

		}
		else if (iRandomSkin == 3)
		{
			DispatchKeyFormat(iModel, "model",		"models/weapons/w_santa_hat_thrown.mdl");
			DispatchKeyFormat(iModel, "modelscale",	"2.7");
			DispatchKeyFormat(iModel, "angles",		"0 0 -90");
			fInfectionOrigin[2] += 7;
		}
		else if (iRandomSkin == 4)
		{
			DispatchKeyFormat(iModel, "model",		"models/models_kit/xmas/xmas_teddybear.mdl");
			DispatchKeyFormat(iModel, "modelscale",	"0.7");
			DispatchKeyFormat(iModel, "angles",		"0 0 0");
		}

		DispatchKeyFormat(iModel, "disableshadows",			"1");
		DispatchKeyFormat(iModel, "disablereceiveshadows",	"1");
		DispatchKeyFormat(iModel, "DisableBoneFollowers",		"1");
		DispatchKeyValueVector(iModel, "origin", fInfectionOrigin);
		SpawnAndActivate(iModel);
		ParentToEntity(iModel, iRotating);

		AcceptEntityInput(iRotating, "FireUser1");

		int iEntityLimit = g_hCVar_EntityLimit.IntValue;
		if ((iModel > iEntityLimit) || (iRotating > iEntityLimit))
		{
			AcceptEntityInput(iRotating, "FireUser2");
			CPrintToChatAll("{green}[Unloze XMAS] {white}Infection Effect removed due to {red}critical amount of entities{white}!");
		}

		g_iCounter += 1;
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnEntitySpawned(int Entity, const char[] sClassname)
{
	if (StrContains(sClassname, "_projectile", false) == -1)
		return;

	int iOwner = GetEntPropEnt(Entity, Prop_Data, "m_hOwnerEntity");
	if(!IsValidClient(iOwner))
		return;

	if (g_iCollected[iOwner] < g_hCVar_MilestoneGrenade.IntValue)
		return;

	RequestFrame(PostNadeSpawn, EntIndexToEntRef(Entity));
}


//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void PostNadeSpawn(int iEntRef)
{
	int iEntity = EntRefToEntIndex(iEntRef);
	if (!IsValidEntity(iEntity))
		return;

	SetEntityRenderMode(iEntity, RENDER_NONE);

	float fNadeOrigin[3];
	GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fNadeOrigin);

	int iNadeProp = CreateEntityAtOrigin("prop_dynamic_override", fNadeOrigin);
	DispatchKeyFormat(iNadeProp, "targetname",				"season_nade_prop_%d", g_iCounter);
	DispatchKeyFormat(iNadeProp, "disableshadows",			"1");
	DispatchKeyFormat(iNadeProp, "disablereceiveshadows",		"1");
	DispatchKeyFormat(iNadeProp, "DisableBoneFollowers",		"1");

	int iRandomSkin = GetRandomInt(0, 3);
	if (iRandomSkin == 0)
	{
		DispatchKeyFormat(iNadeProp, "model", 		"models/weapons/w_snowball_thrown.mdl");
		DispatchKeyFormat(iNadeProp, "modelscale",	"3.0");
	}
	else if (iRandomSkin == 1)
	{
		DispatchKeyFormat(iNadeProp, "model", 		"models/zombieden/xmas/giftbox.mdl");
		DispatchKeyFormat(iNadeProp, "modelscale",	"0.6");
		iRandomSkin = GetRandomInt(0, 1);
		if (iRandomSkin == 0)
		{
			DispatchKeyFormat(iNadeProp, "skin",	"0");
		}
		else if (iRandomSkin == 1)
		{
			DispatchKeyFormat(iNadeProp, "skin",	"1");
		}
	}
	else if (iRandomSkin == 2)
	{
		DispatchKeyFormat(iNadeProp, "model", 		"models/weapons/w_ornament_thrown.mdl");
		DispatchKeyFormat(iNadeProp, "modelscale",	"2.0");
	}
	else if (iRandomSkin == 3)
	{
		DispatchKeyFormat(iNadeProp, "model", 		"models/weapons/w_santa_hat_thrown.mdl");
		DispatchKeyFormat(iNadeProp, "modelscale",	"2.3");
	}

	SpawnAndActivate(iNadeProp);
	ParentToEntity(iNadeProp, iEntity);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock int IsValidClient(int client, bool nobots = true)
{
	if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (nobots && IsFakeClient(client)))
		return false;

	return IsClientInGame(client);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void CheckAndAddFlag(int client)
{
	if (g_iCollected[client] >= g_hCVar_MilestoneSkin.IntValue)
		AddUserFlags(client, Admin_Custom4);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock int CreateEntityAtOrigin(const char[] classname, const float origin[3])
{
	int entity = CreateEntityByName(classname);

	TeleportEntity(entity, origin, NULL_VECTOR, NULL_VECTOR);

	return entity;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool DispatchKeyFormat(int entity, const char[] key, const char[] value, any ...)
{
	char buffer[1024];
	VFormat(buffer, sizeof(buffer), value, 4);

	DispatchKeyValue(entity, key, buffer);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void SpawnAndActivate(int entity)
{
	DispatchSpawn(entity);
	ActivateEntity(entity);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void ParentToEntity(int entity, int parent)
{
	SetVariantString("!activator");
	AcceptEntityInput(entity, "SetParent", parent, parent);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void SetEntityBBox(int entity, const float mins[3], const float maxs[3])
{
	SetEntPropVector(entity, Prop_Send, "m_vecMins", mins);
	SetEntPropVector(entity, Prop_Send, "m_vecMaxs", maxs);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void SetEntityProps(int entity)
{
	SetEntProp(entity, Prop_Send, "m_nSolidType", 3);
	SetEntProp(entity, Prop_Send, "m_fEffects", 32);
}
