Building hat: effects added.

Building hat: effects added.

#pragma newdecls required
#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <clientprefs>
#include <tf2_stocks>

int g_ModelIndex[2049];
float g_flZOffset[2049];
float g_flModelScale[2049];
char g_strParticle[2049][36];
bool g_bWantsTheH[MAXPLAYERS+1];

int g_hatEnt[2049] = {INVALID_ENT_REFERENCE, ... };
int g_particleEnt[2049] = {INVALID_ENT_REFERENCE, ... };

int stringTable;
Handle hHatInfo = INVALID_HANDLE;

float RollCooldown[MAXPLAYERS+1];

char g_sParticleList[][] =
{
	{"superrare_confetti_green"},
	{"superrare_confetti_purple"},
	{"superrare_ghosts"},
	{"superrare_greenenergy"},
	{"superrare_purpleenergy"},
	{"superrare_flies"},
	{"superrare_burning1"},
	{"superrare_burning2"},
	{"superrare_plasma1"},
	{"superrare_beams1"},
	{"unusual_storm"},
	{"unusual_blizzard"},
	{"unusual_orbit_nutsnbolts"},
	{"unusual_orbit_planets"},
	{"unusual_orbit_fire"},
	{"unusual_bubbles"},
	{"unusual_smoking"},
	{"unusual_steaming"},
	{"unusual_bubbles_green"},
	{"unusual_orbit_fire_dark"},
	{"unusual_skull_misty"},
	{"unusual_storm_knives"},
	{"unusual_orbit_jack_flaming"},
	{"unusual_fullmoon_cloudy_green"},
	{"unusual_fullmoon_cloudy_secret"},
	{"unusual_fullmoon_cloudy"},
	{"unusual_storm_knives"},
	{"unusual_storm_spooky"},
	{"unusual_zap_yellow"},
	{"unusual_orbit_cards_teamcolor_blue"},
	{"unusual_orbit_cards_teamcolor_red"},
	{"unusual_orbit_cash"},
	{"unusual_zap_green"},
	{"unusual_hearts_bubbling"},
	{"unusual_crisp_spotlights"},
	{"unusual_spotlights"},
	{"unusual_robot_holo_glow_green"},
	{"unusual_robot_holo_glow_orange"},
	{"unusual_robot_orbit_binary"},
	{"unusual_robot_orbit_binary2"},
	{"unusual_robot_orbiting_sparks"},
	{"unusual_robot_orbiting_sparks2"},
	{"unusual_robot_radioactive"},
	{"unusual_robot_time_warp"},
	{"unusual_robot_time_warp2"},
	{"unusual_robot_radioactive2"},
	{"unusual_spellbook_circle_purple"},
	{"unusual_spellbook_circle_green"},
	{"unusual_bats_flaming_proxy_green"},
	{"unusual_bats_flaming_proxy_purple"},
	{"unusual_bats_flaming_proxy_orange"},
	{"unusual_meteor_shower_parent_orange"},
	{"unusual_meteor_shower_parent_purple"},
	{"unusual_meteor_shower_parent_green"},
	{"unusual_tentmonster_purple_parent"},
	{"unusual_eyes_purple_parent"},
	{"unusual_eyes_orange_parent"},
	{"unusual_eyes_green_parent"},
	{"unusual_souls_purple_parent"},
	{"unusual_souls_green_parent"},
	{"unusual_eotl_frostbite"},
	{"unusual_eotl_oribiting_burning_duck_parent"},
	{"unusual_eotl_sunrise"},
	{"unusual_eotl_sunset"},
	{"unusual_invasion_abduction"},
	{"unusual_invasion_atomic"},
	{"unusual_invasion_atomic_green"},
	{"unusual_invasion_boogaloop"},
	{"unusual_invasion_boogaloop_2"},
	{"unusual_invasion_boogaloop_3"},
	{"unusual_invasion_codex"},
	{"unusual_invasion_codex_2"},
	{"unusual_invasion_nebula"},
	{"unusual_hw_deathbydisco_parent"},
	{"unusual_mystery_parent"},
	{"unusual_mystery_parent_green"},
	{"unusual_nether_blue"},
	{"unusual_nether_pink"},
	{"unusual_eldritch_flames_purple"},
	{"unusual_eldritch_flames_orange"}
};

ConVar g_hCvarVersion;
ConVar g_hCvarEnabled;
bool g_bCvarEnabled;
ConVar g_hCvarUnusualChance;
float g_flCvarUnusualChance;
ConVar g_hCvarRerollCooldown;
int g_CvarRerollCooldown;

Handle g_hParticleMenu = INVALID_HANDLE;
Handle g_hClientCookie = INVALID_HANDLE;

#define PLUGIN_VERSION 		"2.0"

public Plugin myinfo = {
	name		= "[TF2] Building Hats",
	author		= "Pelipoika / TheXeon",
	description	= "Ain't that a cute little gun?",
	version		= PLUGIN_VERSION,
	url			= "https://forums.alliedmods.net/showthread.php?p=2164412#post2164412"
}

public void OnPluginStart()
{
	g_hCvarVersion = CreateConVar("sm_bhats_version", PLUGIN_VERSION, "Current Building Hats Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
	HookConVarChange(g_hCvarEnabled = CreateConVar("sm_bhats_enabled", "1.0", "Enable Hats on Buildings \n 0 = Disabled \n 1 = Enabled", _, true, 0.0, true, 1.0), OnConVarChange);
	HookConVarChange(g_hCvarUnusualChance = CreateConVar("sm_bhats_unusualchance", "0.1", "Chance for a building to get an unusual effect on it's hat upon being built. 0.1 = 10%", _, true, 0.0), OnConVarChange);
	HookConVarChange(g_hCvarRerollCooldown = CreateConVar("sm_bhats_rollcooldown", "30", "Hat reroll cooldown (in seconds)", _, true, 0.0), OnConVarChange);

	HookEvent("player_builtobject",		Event_PlayerBuiltObject);
	HookEvent("player_upgradedobject",	Event_UpgradeObject);
	HookEvent("player_dropobject", 		Event_DropObject);
	HookEvent("player_carryobject",		Event_PickupObject);

	stringTable = FindStringTable("modelprecache");
	hHatInfo = CreateArray(PLATFORM_MAX_PATH, 1);

	g_hClientCookie = RegClientCookie("BuildingHats", "sm_bhats_enabled", CookieAccess_Private);

	RegAdminCmd("sm_buildinghats",		 Command_iDontWantHatsOnMyThings, 0);
	RegAdminCmd("sm_bhats_reloadconfig", Command_Parse, ADMFLAG_ROOT);
	RegAdminCmd("sm_rerollhat", 		 Command_RerollHats, 0);
	RegAdminCmd("sm_buildinghateffect",  Command_ChooseBuildingEffect, ADMFLAG_ROOT);

	AutoExecConfig(true, "buildhats");

	for(int i = 0; i <= MaxClients; i++)
	{
		if(i > 0 && i <= MaxClients && IsClientInGame(i))
			g_bWantsTheH[i] = true;
	}
}

public void OnConfigsExecuted()
{
	SetConVarString(g_hCvarVersion, PLUGIN_VERSION);
	g_bCvarEnabled  = GetConVarBool(g_hCvarEnabled);
	g_flCvarUnusualChance = GetConVarFloat(g_hCvarUnusualChance);
	g_CvarRerollCooldown = GetConVarInt(g_hCvarRerollCooldown);
	ParseConfigurations();

	g_hParticleMenu = CreateMenu(Menu_SetEffect);
	SetMenuTitle(g_hParticleMenu, "[Building Hats] Hat Effects");
	for(int i = 0; i < sizeof(g_sParticleList); i++)
	{
		char info[128], display[128];
		Format(info, sizeof(info), "%s", g_sParticleList[i][0]);
		Format(display, sizeof(display), "%s", g_sParticleList[i][0]);
		AddMenuItem(g_hParticleMenu, info, display);
	}
	SetMenuExitBackButton(g_hParticleMenu, false);
}

public void OnClientAuthorized(int client)
{
	g_bWantsTheH[client] = true;
	RollCooldown[client] = 0.0;
}

public void OnClientCookiesCached(int client)
{
	char sValue[8];
	GetClientCookie(client, g_hClientCookie, sValue, sizeof(sValue));

	if(sValue[0] == '\0')
		g_bWantsTheH[client] = true;
	else
		g_bWantsTheH[client] = (sValue[0] != '\0' && StringToInt(sValue));
}

public void OnMapEnd()
{
	if(g_hParticleMenu != INVALID_HANDLE)
	{
		CloseHandle(g_hParticleMenu);
		g_hParticleMenu = INVALID_HANDLE;
	}
}

public void OnConVarChange(Handle hConvar, const char[] strOldValue, const char[] strNewValue)
{
	SetConVarString(g_hCvarVersion, PLUGIN_VERSION);
	g_bCvarEnabled = GetConVarBool(g_hCvarEnabled);
	g_flCvarUnusualChance = GetConVarFloat(g_hCvarUnusualChance);
	g_CvarRerollCooldown = GetConVarInt(g_hCvarRerollCooldown);
}

public Action Command_ChooseBuildingEffect(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
		DisplayMenuSafely(g_hParticleMenu, client);

	return Plugin_Handled;
}

public int Menu_SetEffect(Handle menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select && IsClientInGame(param1))
	{
		char info[128];
		GetMenuItem(menu, param2, info, sizeof(info));

		int iBuilding = -1;
		while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
		{
			if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == param1 && GetEntProp(iBuilding, Prop_Send, "m_bPlacing") == 0 && GetEntProp(iBuilding, Prop_Send, "m_bCarried") == 0)
			{
				if(IsValidEntity(g_particleEnt[iBuilding]))
				{
					int particle = EntRefToEntIndex(g_particleEnt[iBuilding]);
					AcceptEntityInput(particle, "Stop");
					AcceptEntityInput(particle, "Kill");
				}

				int iParticle = CreateEntityByName("info_particle_system");
				if(IsValidEdict(iParticle))
				{
					float flPos[3];

					DispatchKeyValue(iParticle, "effect_name", info);
					DispatchSpawn(iParticle);

					SetVariantString("!activator");
					AcceptEntityInput(iParticle, "SetParent", iBuilding);
					ActivateEntity(iParticle);

					TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));
					if(objectT == TFObject_Dispenser)
					{
						SetVariantString("build_point_0");
					}
					else if(objectT == TFObject_Sentry)
					{
						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") < 3)
							SetVariantString("build_point_0");
						else
							SetVariantString("rocket_r");
					}
					AcceptEntityInput(iParticle, "SetParentAttachment", iBuilding);

					GetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					if(objectT == TFObject_Dispenser)
					{
						flPos[2] += 13.0;	//Make sure the effect is on top of the dispenser

						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3)
							flPos[2] += 8.0;	//Account for level 3 dispenser
					}

					if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
					{
						flPos[2] += 6.5;	//Level 3 sentry offsets
						flPos[0] -= 11.0;	//Gotta get that effect on top of the missile thing
					}

					SetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);
					AcceptEntityInput(iParticle, "start");

					g_particleEnt[iBuilding] = EntIndexToEntRef(iParticle);
					Format(g_strParticle[iBuilding], sizeof(g_strParticle), "%s", info);
				}
			}
		}

		DisplayMenuAtItem(menu, param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
	}
}

public Action Command_RerollHats(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		if(CheckCommandAccess(client, "sm_buildinghats_access", 0))
		{
			if(RollCooldown[client] >= GetTickedTime())
			{
				ReplyToCommand(client, "Please wait %.1f seconds!", RollCooldown[client] - GetTickedTime());
				return Plugin_Handled;
			}
			else
			{
				int iBuilding = -1;
				while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
				{
					if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == client
					&& GetEntProp(iBuilding, Prop_Send, "m_bPlacing") == 0
					&& GetEntProp(iBuilding, Prop_Send, "m_bCarried") == 0)
					{
						g_ModelIndex[iBuilding]  = INVALID_STRING_INDEX;
						g_flZOffset[iBuilding]   = 0.0;
						g_flModelScale[iBuilding]= 0.0;
						Format(g_strParticle[iBuilding], sizeof(g_strParticle), "");

						if (IsValidEntity(g_hatEnt[iBuilding]))
						{
							AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
						}

						if(IsValidEntity(g_particleEnt[iBuilding]))
						{
							AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
							AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
						}

						g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
						g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;

						CreateTimer(0.1, Timer_ReHat, iBuilding);
					}
				}

				RollCooldown[client] = GetTickedTime() + float(g_CvarRerollCooldown);
			}
		}
		else
			ReplyToCommand(client, "[SM] You do not have acces to this command.");
	}

	return Plugin_Handled;
}

public Action Command_iDontWantHatsOnMyThings(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		if(CheckCommandAccess(client, "sm_buildinghats_access", 0))
		{
			if(!g_bWantsTheH[client])
			{
				SetClientCookie(client, g_hClientCookie, "1");
				OnClientCookiesCached(client);
				g_bWantsTheH[client] = true;
				PrintToChat(client, "[Building Hats] On");
			}
			else
			{
				SetClientCookie(client, g_hClientCookie, "0");
				OnClientCookiesCached(client);

				g_bWantsTheH[client] = false;
				PrintToChat(client, "[Building Hats] Off");

				int iBuilding = -1;
				while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
				{
					if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == client)
					{
						if (IsValidEntity(g_hatEnt[iBuilding]))
						{
							AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
							g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
						}
						if(IsValidEntity(g_particleEnt[iBuilding]))
						{
							AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
							AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
							g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
						}

						if (GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
						{
							SetVariantInt(0);
							AcceptEntityInput(iBuilding, "SetBodyGroup");
						}
					}
				}
			}
		}
		else
			ReplyToCommand(client, "[SM] You do not have acces to this command.");
	}

	return Plugin_Handled;
}

public Action Command_Parse(int client, int args)
{
	ResizeArray(hHatInfo, 1);
	ReplyToCommand(client, "[Building Hats] Reloading config...");
	ParseConfigurations();
	return Plugin_Handled;
}

public Action Event_PickupObject(Handle event, const char[] name, bool dontBroadcast)
{
	int client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "TurnOff");
			}
			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
			}
		}
	}

	return Plugin_Handled;
}

public Action Event_DropObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	int client = GetClientOfUserId(GetEventInt(event, "userid"));
	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client) && g_bWantsTheH[client])
	{
		if(!CheckCommandAccess(client, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if(objectT == TFObject_Sentry && GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
			{
				SetVariantInt(2);
				AcceptEntityInput(iBuilding, "SetBodyGroup");
				CreateTimer(2.0, Timer_TurnTheLightsOff, iBuilding);
			}

			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "TurnOn");
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Start");
			}
		}
	}

	return Plugin_Handled;
}

public Action Event_UpgradeObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	int iBuilding = GetEventInt(event, "index");
	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		int builder = GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder");
		if(builder >= 1 && builder <= MaxClients && IsClientInGame(builder) && !g_bWantsTheH[builder] || !CheckCommandAccess(builder, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		if(objectT == TFObject_Sentry)
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
				g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
				AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
				g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			CreateTimer(2.0, Timer_ReHat, iBuilding);
		}
		if(objectT == TFObject_Dispenser && GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 2)
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
				g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
				AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
				g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			CreateTimer(2.0, Timer_ReHat, iBuilding);
		}
	}

	return Plugin_Handled;
}

public Action Timer_ReHat(Handle timer, any iBuilding)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		char strPath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];
		int row = (GetArraySize(hHatInfo) / 4) - 1;
		int index = (GetRandomInt(0, row)) * 4;

		GetArrayString(hHatInfo, index+1, strPath, sizeof(strPath));
		GetArrayString(hHatInfo, index+2, strOffz, sizeof(strOffz));
		GetArrayString(hHatInfo, index+3, strScale, sizeof(strScale));
		GetArrayString(hHatInfo, index+4, strAnima, sizeof(strAnima));

		TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));

		if(objectT == TFObject_Sentry)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Sentry, strAnima);
		else if(objectT == TFObject_Dispenser)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Dispenser, strAnima);
	}

	return Plugin_Handled;
}

public Action Event_PlayerBuiltObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	int client = GetClientOfUserId(GetEventInt(event, "userid"));
	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client) && g_bWantsTheH[client])
	{
		if(!CheckCommandAccess(client, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if(!GetEntProp(iBuilding, Prop_Send, "m_bCarryDeploy"))
			{
				g_ModelIndex[iBuilding]  = INVALID_STRING_INDEX;
				g_flZOffset[iBuilding]   = 0.0;
				g_flModelScale[iBuilding]= 0.0;
				Format(g_strParticle[iBuilding], sizeof(g_strParticle), "");

				char strPath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];
				int row = (GetArraySize(hHatInfo) / 4) - 1;
				int index = (GetRandomInt(0, row)) * 4;

				GetArrayString(hHatInfo, index+1, strPath, sizeof(strPath));
				GetArrayString(hHatInfo, index+2, strOffz, sizeof(strOffz));
				GetArrayString(hHatInfo, index+3, strScale, sizeof(strScale));
				GetArrayString(hHatInfo, index+4, strAnima, sizeof(strAnima));

				if(objectT == TFObject_Sentry)
				{
					if(GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
					{
						SetVariantInt(2);
						AcceptEntityInput(iBuilding, "SetBodyGroup");
						CreateTimer(3.0, Timer_TurnTheLightsOff, iBuilding);
					}

					ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Sentry, strAnima);
				//	PrintToChatAll("%s", strPath);
				}
				else if(objectT == TFObject_Dispenser)
				{
					ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Dispenser, strAnima);
				//	PrintToChatAll("%s", strPath);
				}
			}
		}
	}

	return Plugin_Handled;
}

public Action Timer_TurnTheLightsOff(Handle timer, any iBuilding)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		SetVariantInt(2);
		AcceptEntityInput(iBuilding, "SetBodyGroup");
	}

	return Plugin_Continue;
}

//Avert your eyes children.
void ParentHatEntity(int entity, const char[] smodel, float flZOffset = 0.0, float flModelScale, TFObjectType objectT, const char[] strAnimation)
{
	float pPos[3], pAng[3];
	int builder = GetEntPropEnt(entity, Prop_Send, "m_hBuilder");
	int prop = CreateEntityByName("prop_dynamic_override");

	char strModelPath[PLATFORM_MAX_PATH];

	if(g_ModelIndex[entity] != INVALID_STRING_INDEX)
		ReadStringTable(stringTable, g_ModelIndex[entity], strModelPath, PLATFORM_MAX_PATH);

	if(StrEqual(strModelPath, "", false))
		g_ModelIndex[entity] = PrecacheModel(smodel);

	if(IsValidEntity(prop))
	{
		if(!StrEqual(strModelPath, "", false))
			DispatchKeyValue(prop, "model", strModelPath);
		else
			DispatchKeyValue(prop, "model", smodel);

		if(g_flModelScale[entity] != 0.0)
			SetEntPropFloat(prop, Prop_Send, "m_flModelScale", g_flModelScale[entity]);
		else
			SetEntPropFloat(prop, Prop_Send, "m_flModelScale", flModelScale);

		DispatchSpawn(prop);
		AcceptEntityInput(prop, "Enable");
		SetEntProp(prop, Prop_Send, "m_nSkin", GetClientTeam(builder) - 2);

		SetVariantString("!activator");
		AcceptEntityInput(prop, "SetParent", entity);

		if(objectT == TFObject_Dispenser)
		{
			SetVariantString("build_point_0");
		}
		else if(objectT == TFObject_Sentry)
		{
			if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") < 3)
				SetVariantString("build_point_0");
			else
				SetVariantString("rocket_r");
		}

		AcceptEntityInput(prop, "SetParentAttachment", entity);

		GetEntPropVector(prop, Prop_Send, "m_vecOrigin", pPos);
		GetEntPropVector(prop, Prop_Send, "m_angRotation", pAng);

		if(!StrEqual(strAnimation, "default", false))
		{
			SetVariantString(strAnimation);
			AcceptEntityInput(prop, "SetAnimation");
			SetVariantString(strAnimation);
			AcceptEntityInput(prop, "SetDefaultAnimation");
		}

		if(g_flZOffset[entity] != 0.0)
			pPos[2] += g_flZOffset[entity];
		else
			pPos[2] += flZOffset;

		if(objectT == TFObject_Dispenser)
		{
			pPos[2] += 13.0;	//Make sure the hat is on top of the dispenser
			pAng[1] += 180.0;	//Make hat face builder

			if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3)
			{
				pPos[2] += 8.0;	//Account for level 3 dispenser
			}
		}

		if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
		{
			pPos[2] += 6.5;		//Level 3 sentry offsets
			pPos[0] -= 11.0;	//Gotta get that hat on top of the missile thing
		}

		SetEntPropVector(prop, Prop_Send, "m_vecOrigin", pPos);
		SetEntPropVector(prop, Prop_Send, "m_angRotation", pAng);

		g_hatEnt[entity] = EntIndexToEntRef(prop);

		if(g_flZOffset[entity] == 0.0)
			g_flZOffset[entity] = flZOffset;

		if(g_flModelScale[entity] == 0.0)
			g_flModelScale[entity] = flModelScale;

		if(g_particleEnt[entity] == INVALID_ENT_REFERENCE && CheckCommandAccess(builder, "sm_buildinghats_unusuals", 0))
		{
			int iParticle = CreateEntityByName("info_particle_system");
			if(IsValidEdict(iParticle))
			{
				float flPos[3];
				bool kill = false;

				if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") > 1 && StrEqual(g_strParticle[entity], "", false))
					kill = true;

				int sParticle = GetRandomInt(0, sizeof(g_sParticleList)-1);

				if(!StrEqual(g_strParticle[entity], "", false))
					DispatchKeyValue(iParticle, "effect_name", g_strParticle[entity]);
				else
				{
					if(g_flCvarUnusualChance == 1.0)	//100% Unusual chance fix?
						DispatchKeyValue(iParticle, "effect_name", g_sParticleList[sParticle][0]);
					else if(GetRandomFloat(0.0, 1.0) <= g_flCvarUnusualChance)
						DispatchKeyValue(iParticle, "effect_name", g_sParticleList[sParticle][0]);
					else
						kill = true;
				}

				if(!kill)
				{
					DispatchSpawn(iParticle);

					SetVariantString("!activator");
					AcceptEntityInput(iParticle, "SetParent", entity);
					ActivateEntity(iParticle);

					if(objectT == TFObject_Dispenser)
					{
						SetVariantString("build_point_0");
					}
					else if(objectT == TFObject_Sentry)
					{
						if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") < 3)
							SetVariantString("build_point_0");
						else
							SetVariantString("rocket_r");
					}
					AcceptEntityInput(iParticle, "SetParentAttachment", entity);

					GetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					if(objectT == TFObject_Dispenser)
					{
						flPos[2] += 13.0;	//Make sure the effect is on top of the dispenser

						if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3)
							flPos[2] += 8.0;	//Account for level 3 dispenser
					}

					if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
					{
						flPos[2] += 6.5;	//Level 3 sentry offsets
						flPos[0] -= 11.0;	//Gotta get that effect on top of the missile thing
					}

					SetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					AcceptEntityInput(iParticle, "start");

					g_particleEnt[entity] = EntIndexToEntRef(iParticle);

					if(StrEqual(g_strParticle[entity], "", false))
						Format(g_strParticle[entity], sizeof(g_strParticle), "%s", g_sParticleList[sParticle][0]);
				}
				else
					AcceptEntityInput(iParticle, "Kill");
			}
		}
	}
}

bool ParseConfigurations()
{
	char strPath[PLATFORM_MAX_PATH];
	char strFileName[PLATFORM_MAX_PATH];
	Format(strFileName, sizeof(strFileName), "configs/buildinghats.cfg");
	BuildPath(Path_SM, strPath, sizeof(strPath), strFileName);

	LogMessage("[Building Hats] Executing configuration file %s", strPath);

	if (FileExists(strPath, true))
	{
		Handle kvConfig = CreateKeyValues("TF2_Buildinghats");
		if (FileToKeyValues(kvConfig, strPath) == false) SetFailState("[Building Hats] Error while parsing the configuration file.");
		KvGotoFirstSubKey(kvConfig);

		do
		{
			char strMpath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];

			KvGetString(kvConfig, "modelpath",	strMpath, sizeof(strMpath));
			KvGetString(kvConfig, "offset", 	strOffz,  sizeof(strOffz));
			KvGetString(kvConfig, "modelscale", strScale, sizeof(strScale));
			KvGetString(kvConfig, "animation",  strAnima, sizeof(strAnima));

			PrecacheModel(strMpath);

			PushArrayString(hHatInfo, strMpath);
			PushArrayString(hHatInfo, strOffz);
			PushArrayString(hHatInfo, strScale);
			PushArrayString(hHatInfo, strAnima);
		}
		while (KvGotoNextKey(kvConfig));

		CloseHandle(kvConfig);
	}
}

stock void DisplayMenuSafely(Handle menu, int client)
{
    if(client >= 1 && client <= MaxClients && IsClientInGame(client))
    {
        if(menu == INVALID_HANDLE)
        {
            PrintToConsole(client, "ERROR: Unable to open Menu.");
        }
        else
        {
            DisplayMenu(menu, client, MENU_TIME_FOREVER);
        }
    }
}

#pragma newdecls required
#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <clientprefs>
#include <tf2_stocks>

int g_ModelIndex[2049];
float g_flZOffset[2049];
float g_flModelScale[2049];
char g_strParticle[2049][36];
bool g_bWantsTheH[MAXPLAYERS+1];

int g_hatEnt[2049] = {INVALID_ENT_REFERENCE, ... };
int g_particleEnt[2049] = {INVALID_ENT_REFERENCE, ... };

int stringTable;
Handle hHatInfo = null;

float RollCooldown[MAXPLAYERS+1];

char g_sParticleList[][] = {
	{"superrare_confetti_green"},
	{"superrare_confetti_purple"},
	{"superrare_ghosts"},
	{"superrare_greenenergy"},
	{"superrare_purpleenergy"},
	{"superrare_flies"},
	{"superrare_burning1"},
	{"superrare_burning2"},
	{"superrare_plasma1"},
	{"superrare_beams1"},
	{"unusual_storm"},
	{"unusual_blizzard"},
	{"unusual_orbit_nutsnbolts"},
	{"unusual_orbit_fire"},
	{"unusual_bubbles"},
	{"unusual_smoking"},
	{"unusual_steaming"},
	{"unusual_bubbles_green"},
	{"unusual_orbit_fire_dark"},
	{"unusual_storm_knives"},
	{"unusual_storm_spooky"},
	{"unusual_zap_yellow"},
	{"unusual_zap_green"},
	{"unusual_hearts_bubbling"},
	{"unusual_crisp_spotlights"},
	{"unusual_spotlights"},
	{"unusual_robot_holo_glow_green"},
	{"unusual_robot_holo_glow_orange"},
	{"unusual_robot_orbit_binary"},
	{"unusual_robot_orbit_binary2"},
	{"unusual_robot_orbiting_sparks"},
	{"unusual_robot_orbiting_sparks2"},
	{"unusual_robot_radioactive"},
	{"unusual_robot_time_warp"},
	{"unusual_robot_time_warp2"},
	{"unusual_robot_radioactive2"},
	{"unusual_spellbook_circle_purple"},
	{"unusual_spellbook_circle_green"},
	{"unusual_souls_purple_parent"},
	{"unusual_souls_green_parent"}
};

Handle g_hCvarVersion;
Handle g_hCvarEnabled;
bool g_bCvarEnabled;

Handle g_hCvarUnusualChance;
float g_flCvarUnusualChance;
Handle g_hCvarRerollCooldown;
int g_CvarRerollCooldown;

Handle g_hParticleMenu = null;
Handle g_hHatMenu = null;
Handle g_hClientCookie = null;

#define PLUGIN_VERSION 		"2.0"

public Plugin myinfo = {
	name		= "[NGS] Building Hats",
	author		= "Pelipoika / TheXeon",
	description	= "Ain't that a cute little gun?",
	version		= PLUGIN_VERSION,
	url			= "https://forums.alliedmods.net/showthread.php?p=2164412#post2164412"
}

public void OnPluginStart()
{
	g_hCvarVersion = CreateConVar("sm_bhats_version", PLUGIN_VERSION, "Current Building Hats Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
	HookConVarChange(g_hCvarEnabled = CreateConVar("sm_bhats_enabled", "1.0", "Enable Hats on Buildings \n 0 = Disabled \n 1 = Enabled", _, true, 0.0, true, 1.0), OnConVarChange);
	HookConVarChange(g_hCvarUnusualChance = CreateConVar("sm_bhats_unusualchance", "0.1", "Chance for a building to get an unusual effect on it's hat upon being built. 0.1 = 10%", _, true, 0.0), OnConVarChange);
	HookConVarChange(g_hCvarRerollCooldown = CreateConVar("sm_bhats_rollcooldown", "30", "Hat reroll cooldown (in seconds)", _, true, 0.0), OnConVarChange);

	HookEvent("player_builtobject",		Event_PlayerBuiltObject);
	HookEvent("player_upgradedobject",	Event_UpgradeObject);
	HookEvent("player_dropobject", 		Event_DropObject);
	HookEvent("player_carryobject",		Event_PickupObject);

	stringTable = FindStringTable("modelprecache");
	hHatInfo = CreateArray(PLATFORM_MAX_PATH, 1);

	g_hClientCookie = RegClientCookie("BuildingHats", "sm_bhats_enabled", CookieAccess_Private);

	RegAdminCmd("sm_buildinghats",		 Command_iDontWantHatsOnMyThings, 0);
	RegAdminCmd("sm_bhats_reloadconfig", Command_Parse, ADMFLAG_ROOT);
	RegAdminCmd("sm_rerollhat", 		 Command_RerollHats, 0);
	RegAdminCmd("sm_choosebh",  Command_ChooseBuildingHat, ADMFLAG_ROOT);
	RegAdminCmd("sm_buildinghateffect",  Command_ChooseBuildingEffect, ADMFLAG_ROOT);

	AutoExecConfig(true);

	for(int i = 0; i <= MaxClients; i++)
	{
		if(i > 0 && i <= MaxClients && IsClientInGame(i))
			g_bWantsTheH[i] = true;
	}
}

public void OnConfigsExecuted()
{
	SetConVarString(g_hCvarVersion, PLUGIN_VERSION);
	g_bCvarEnabled  = GetConVarBool(g_hCvarEnabled);
	g_flCvarUnusualChance = GetConVarFloat(g_hCvarUnusualChance);
	g_CvarRerollCooldown = GetConVarInt(g_hCvarRerollCooldown);
	ParseConfigurations();

	g_hParticleMenu = CreateMenu(Menu_SetEffect);
	SetMenuTitle(g_hParticleMenu, "[Building Hats] Hat Effects");
	for(int i = 0; i < sizeof(g_sParticleList); i++)
	{
		char info[128], display[128];
		Format(info, sizeof(info), "%s", g_sParticleList[i][0]);
		Format(display, sizeof(display), "%s", g_sParticleList[i][0]);
		AddMenuItem(g_hParticleMenu, info, display);
	}
	SetMenuExitBackButton(g_hParticleMenu, false);

	g_hHatMenu = CreateMenu(Menu_SetHat);
	SetMenuTitle(g_hHatMenu, "[Building Hats] Hats");
	for(int i = 0; i < sizeof(g_sParticleList); i++)
	{
		char info[128], display[128];
		Format(info, sizeof(info), "%s", g_sParticleList[i][0]);
		Format(display, sizeof(display), "%s", g_sParticleList[i][0]);
		AddMenuItem(g_hHatMenu, info, display);
	}
	SetMenuExitBackButton(g_hHatMenu, false);
}

public void OnClientAuthorized(int client)
{
	g_bWantsTheH[client] = true;
	RollCooldown[client] = 0.0;
}

public void OnClientCookiesCached(int client)
{
	char sValue[8];
	GetClientCookie(client, g_hClientCookie, sValue, sizeof(sValue));

	if(sValue[0] == '\0')
		g_bWantsTheH[client] = true;
	else
		g_bWantsTheH[client] = (sValue[0] != '\0' && StringToInt(sValue));
}

public void OnMapEnd()
{
	if(g_hParticleMenu != null)
	{
		CloseHandle(g_hParticleMenu);
		g_hParticleMenu = null;
	}
}

public void OnConVarChange(Handle hConvar, const char[] strOldValue, const char[] strNewValue)
{
	SetConVarString(g_hCvarVersion, PLUGIN_VERSION);
	g_bCvarEnabled = GetConVarBool(g_hCvarEnabled);
	g_flCvarUnusualChance = GetConVarFloat(g_hCvarUnusualChance);
	g_CvarRerollCooldown = GetConVarInt(g_hCvarRerollCooldown);
}

public Action Command_ChooseBuildingEffect(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
		DisplayMenuSafely(g_hParticleMenu, client);

	return Plugin_Handled;
}

public Action Command_ChooseBuildingHat(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
		DisplayMenuSafely(g_hHatMenu, client);

	return Plugin_Handled;
}

public int Menu_SetEffect(Handle menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select && IsClientInGame(param1))
	{
		char info[128];
		GetMenuItem(menu, param2, info, sizeof(info));

		int iBuilding = -1;
		while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
		{
			if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == param1 && GetEntProp(iBuilding, Prop_Send, "m_bPlacing") == 0 && GetEntProp(iBuilding, Prop_Send, "m_bCarried") == 0)
			{
				if(IsValidEntity(g_particleEnt[iBuilding]))
				{
					int particle = EntRefToEntIndex(g_particleEnt[iBuilding]);
					AcceptEntityInput(particle, "Stop");
					AcceptEntityInput(particle, "Kill");
				}

				int iParticle = CreateEntityByName("info_particle_system");
				if(IsValidEdict(iParticle))
				{
					float flPos[3];

					DispatchKeyValue(iParticle, "effect_name", info);
					DispatchSpawn(iParticle);

					SetVariantString("!activator");
					AcceptEntityInput(iParticle, "SetParent", iBuilding);
					ActivateEntity(iParticle);

					TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));
					if(objectT == TFObject_Dispenser)
					{
						SetVariantString("build_point_0");
					}
					else if(objectT == TFObject_Sentry)
					{
						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") < 3)
							SetVariantString("build_point_0");
						else
							SetVariantString("rocket_r");
					}
					AcceptEntityInput(iParticle, "SetParentAttachment", iBuilding);

					GetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					if(objectT == TFObject_Dispenser)
					{
						flPos[2] += 13.0;	//Make sure the effect is on top of the dispenser

						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3)
							flPos[2] += 8.0;	//Account for level 3 dispenser
					}

					if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
					{
						flPos[2] += 6.5;	//Level 3 sentry offsets
						flPos[0] -= 11.0;	//Gotta get that effect on top of the missile thing
					}

					SetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);
					AcceptEntityInput(iParticle, "start");

					g_particleEnt[iBuilding] = EntIndexToEntRef(iParticle);
					Format(g_strParticle[iBuilding], sizeof(g_strParticle), "%s", info);
				}
			}
		}

		DisplayMenuAtItem(menu, param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
	}
}

public int Menu_SetHat(Handle menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select && IsClientInGame(param1))
	{
		char info[128];
		GetMenuItem(menu, param2, info, sizeof(info));

		int iBuilding = -1;
		while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
		{
			if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == param1 && GetEntProp(iBuilding, Prop_Send, "m_bPlacing") == 0 && GetEntProp(iBuilding, Prop_Send, "m_bCarried") == 0)
			{
				if(IsValidEntity(g_particleEnt[iBuilding]))
				{
					int particle = EntRefToEntIndex(g_particleEnt[iBuilding]);
					AcceptEntityInput(particle, "Stop");
					AcceptEntityInput(particle, "Kill");
				}

				int iParticle = CreateEntityByName("info_particle_system");
				if(IsValidEdict(iParticle))
				{
					float flPos[3];

					DispatchKeyValue(iParticle, "effect_name", info);
					DispatchSpawn(iParticle);

					SetVariantString("!activator");
					AcceptEntityInput(iParticle, "SetParent", iBuilding);
					ActivateEntity(iParticle);

					TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));
					if(objectT == TFObject_Dispenser)
					{
						SetVariantString("build_point_0");
					}
					else if(objectT == TFObject_Sentry)
					{
						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") < 3)
							SetVariantString("build_point_0");
						else
							SetVariantString("rocket_r");
					}
					AcceptEntityInput(iParticle, "SetParentAttachment", iBuilding);

					GetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					if(objectT == TFObject_Dispenser)
					{
						flPos[2] += 13.0;	//Make sure the effect is on top of the dispenser

						if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3)
							flPos[2] += 8.0;	//Account for level 3 dispenser
					}

					if(GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
					{
						flPos[2] += 6.5;	//Level 3 sentry offsets
						flPos[0] -= 11.0;	//Gotta get that effect on top of the missile thing
					}

					SetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);
					AcceptEntityInput(iParticle, "start");

					g_particleEnt[iBuilding] = EntIndexToEntRef(iParticle);
					Format(g_strParticle[iBuilding], sizeof(g_strParticle), "%s", info);
				}
			}
		}

		DisplayMenuAtItem(menu, param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
	}
}

public Action Timer_SetHat(Handle timer, any iBuilding)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		char strPath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];
		int row = (GetArraySize(hHatInfo) / 4) - 1;
		int index = (GetRandomInt(0, row)) * 4;

		GetArrayString(hHatInfo, index+1, strPath, sizeof(strPath));
		GetArrayString(hHatInfo, index+2, strOffz, sizeof(strOffz));
		GetArrayString(hHatInfo, index+3, strScale, sizeof(strScale));
		GetArrayString(hHatInfo, index+4, strAnima, sizeof(strAnima));

		TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));

		if(objectT == TFObject_Sentry)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Sentry, strAnima);
		else if(objectT == TFObject_Dispenser)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Dispenser, strAnima);
	}

	return Plugin_Handled;
}

public Action Command_RerollHats(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		if(CheckCommandAccess(client, "sm_buildinghats_access", 0))
		{
			if(RollCooldown[client] >= GetTickedTime())
			{
				ReplyToCommand(client, "Please wait %.1f seconds!", RollCooldown[client] - GetTickedTime());
				return Plugin_Handled;
			}
			else
			{
				int iBuilding = -1;
				while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
				{
					if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == client
					&& GetEntProp(iBuilding, Prop_Send, "m_bPlacing") == 0
					&& GetEntProp(iBuilding, Prop_Send, "m_bCarried") == 0)
					{
						g_ModelIndex[iBuilding]  = INVALID_STRING_INDEX;
						g_flZOffset[iBuilding]   = 0.0;
						g_flModelScale[iBuilding]= 0.0;
						Format(g_strParticle[iBuilding], sizeof(g_strParticle), "");

						if (IsValidEntity(g_hatEnt[iBuilding]))
						{
							AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
						}

						if(IsValidEntity(g_particleEnt[iBuilding]))
						{
							AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
							AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
						}

						g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
						g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;

						CreateTimer(0.1, Timer_ReHat, iBuilding);
					}
				}

				RollCooldown[client] = GetTickedTime() + float(g_CvarRerollCooldown);
			}
		}
		else
			ReplyToCommand(client, "[SM] You do not have acces to this command.");
	}

	return Plugin_Handled;
}

public Action Command_iDontWantHatsOnMyThings(int client, int args)
{
	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		if(CheckCommandAccess(client, "sm_buildinghats_access", 0))
		{
			if(!g_bWantsTheH[client])
			{
				SetClientCookie(client, g_hClientCookie, "1");
				OnClientCookiesCached(client);
				g_bWantsTheH[client] = true;
				PrintToChat(client, "[Building Hats] On");
			}
			else
			{
				SetClientCookie(client, g_hClientCookie, "0");
				OnClientCookiesCached(client);

				g_bWantsTheH[client] = false;
				PrintToChat(client, "[Building Hats] Off");

				int iBuilding = -1;
				while ((iBuilding = FindEntityByClassname(iBuilding, "obj_*")) != -1)
				{
					if(GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder") == client)
					{
						if (IsValidEntity(g_hatEnt[iBuilding]))
						{
							AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
							g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
						}
						if(IsValidEntity(g_particleEnt[iBuilding]))
						{
							AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
							AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
							g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
						}

						if (GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
						{
							SetVariantInt(0);
							AcceptEntityInput(iBuilding, "SetBodyGroup");
						}
					}
				}
			}
		}
		else
			ReplyToCommand(client, "[SM] You do not have acces to this command.");
	}

	return Plugin_Handled;
}

public Action Command_Parse(int client, int args)
{
	ResizeArray(hHatInfo, 1);
	ReplyToCommand(client, "[Building Hats] Reloading config...");
	ParseConfigurations();
	return Plugin_Handled;
}

public Action Event_PickupObject(Handle event, const char[] name, bool dontBroadcast)
{
	int client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client))
	{
		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "TurnOff");
			}
			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
			}
		}
	}

	return Plugin_Handled;
}

public Action Event_DropObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	int client = GetClientOfUserId(GetEventInt(event, "userid"));
	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client) && g_bWantsTheH[client])
	{
		if(!CheckCommandAccess(client, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if(objectT == TFObject_Sentry && GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
			{
				SetVariantInt(2);
				AcceptEntityInput(iBuilding, "SetBodyGroup");
				CreateTimer(2.0, Timer_TurnTheLightsOff, iBuilding);
			}

			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "TurnOn");
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Start");
			}
		}
	}

	return Plugin_Handled;
}

public Action Event_UpgradeObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	int iBuilding = GetEventInt(event, "index");
	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		int builder = GetEntPropEnt(iBuilding, Prop_Send, "m_hBuilder");
		if(builder >= 1 && builder <= MaxClients && IsClientInGame(builder) && !g_bWantsTheH[builder] || !CheckCommandAccess(builder, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		if(objectT == TFObject_Sentry)
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
				g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
				AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
				g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			CreateTimer(2.0, Timer_ReHat, iBuilding);
		}
		if(objectT == TFObject_Dispenser && GetEntProp(iBuilding, Prop_Send, "m_iUpgradeLevel") == 2)
		{
			if (IsValidEntity(g_hatEnt[iBuilding]))
			{
				AcceptEntityInput(g_hatEnt[iBuilding], "Kill");
				g_hatEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			if(IsValidEntity(g_particleEnt[iBuilding]))
			{
				AcceptEntityInput(g_particleEnt[iBuilding], "Stop");
				AcceptEntityInput(g_particleEnt[iBuilding], "Kill");
				g_particleEnt[iBuilding] = INVALID_ENT_REFERENCE;
			}

			CreateTimer(2.0, Timer_ReHat, iBuilding);
		}
	}

	return Plugin_Handled;
}

public Action Timer_ReHat(Handle timer, any iBuilding)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		char strPath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];
		int row = (GetArraySize(hHatInfo) / 4) - 1;
		int index = (GetRandomInt(0, row)) * 4;

		GetArrayString(hHatInfo, index+1, strPath, sizeof(strPath));
		GetArrayString(hHatInfo, index+2, strOffz, sizeof(strOffz));
		GetArrayString(hHatInfo, index+3, strScale, sizeof(strScale));
		GetArrayString(hHatInfo, index+4, strAnima, sizeof(strAnima));

		TFObjectType objectT = view_as<TFObjectType>(TF2_GetObjectType(iBuilding));

		if(objectT == TFObject_Sentry)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Sentry, strAnima);
		else if(objectT == TFObject_Dispenser)
			ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Dispenser, strAnima);
	}

	return Plugin_Handled;
}

public Action Event_PlayerBuiltObject(Handle event, const char[] name, bool dontBroadcast)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	int client = GetClientOfUserId(GetEventInt(event, "userid"));
	TFObjectType objectT = view_as<TFObjectType>(GetEventInt(event, "object"));

	if(client >= 1 && client <= MaxClients && IsClientInGame(client) && g_bWantsTheH[client])
	{
		if(!CheckCommandAccess(client, "sm_buildinghats_access", 0))
			return Plugin_Handled;

		int iBuilding = GetEventInt(event, "index");
		if(iBuilding > MaxClients && IsValidEntity(iBuilding))
		{
			if(!GetEntProp(iBuilding, Prop_Send, "m_bCarryDeploy"))
			{
				g_ModelIndex[iBuilding]  = INVALID_STRING_INDEX;
				g_flZOffset[iBuilding]   = 0.0;
				g_flModelScale[iBuilding]= 0.0;
				Format(g_strParticle[iBuilding], sizeof(g_strParticle), "");

				char strPath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];
				int row = (GetArraySize(hHatInfo) / 4) - 1;
				int index = (GetRandomInt(0, row)) * 4;

				GetArrayString(hHatInfo, index+1, strPath, sizeof(strPath));
				GetArrayString(hHatInfo, index+2, strOffz, sizeof(strOffz));
				GetArrayString(hHatInfo, index+3, strScale, sizeof(strScale));
				GetArrayString(hHatInfo, index+4, strAnima, sizeof(strAnima));

				if(objectT == TFObject_Sentry)
				{
					if(GetEntProp(iBuilding, Prop_Send, "m_bMiniBuilding"))
					{
						SetVariantInt(2);
						AcceptEntityInput(iBuilding, "SetBodyGroup");
						CreateTimer(3.0, Timer_TurnTheLightsOff, iBuilding);
					}

					ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Sentry, strAnima);
				//	PrintToChatAll("%s", strPath);
				}
				else if(objectT == TFObject_Dispenser)
				{
					ParentHatEntity(iBuilding, strPath, StringToFloat(strOffz), StringToFloat(strScale), TFObject_Dispenser, strAnima);
				//	PrintToChatAll("%s", strPath);
				}
			}
		}
	}

	return Plugin_Handled;
}

public Action Timer_TurnTheLightsOff(Handle timer, any iBuilding)
{
	if(!g_bCvarEnabled)
		return Plugin_Continue;

	if(iBuilding > MaxClients && IsValidEntity(iBuilding))
	{
		SetVariantInt(2);
		AcceptEntityInput(iBuilding, "SetBodyGroup");
	}

	return Plugin_Continue;
}

//Avert your eyes children.
void ParentHatEntity(int entity, const char[] smodel, float flZOffset = 0.0, float flModelScale, TFObjectType objectT, const char[] strAnimation)
{
	float pPos[3], pAng[3];
	int builder = GetEntPropEnt(entity, Prop_Send, "m_hBuilder");
	int prop = CreateEntityByName("prop_dynamic_override");

	char strModelPath[PLATFORM_MAX_PATH];

	if(g_ModelIndex[entity] != INVALID_STRING_INDEX)
		ReadStringTable(stringTable, g_ModelIndex[entity], strModelPath, PLATFORM_MAX_PATH);

	if(StrEqual(strModelPath, "", false))
		g_ModelIndex[entity] = PrecacheModel(smodel);

	if(IsValidEntity(prop))
	{
		if(!StrEqual(strModelPath, "", false))
			DispatchKeyValue(prop, "model", strModelPath);
		else
			DispatchKeyValue(prop, "model", smodel);

		if(g_flModelScale[entity] != 0.0)
			SetEntPropFloat(prop, Prop_Send, "m_flModelScale", g_flModelScale[entity]);
		else
			SetEntPropFloat(prop, Prop_Send, "m_flModelScale", flModelScale);

		DispatchSpawn(prop);
		AcceptEntityInput(prop, "Enable");
		SetEntProp(prop, Prop_Send, "m_nSkin", GetClientTeam(builder) - 2);

		SetVariantString("!activator");
		AcceptEntityInput(prop, "SetParent", entity);

		if(objectT == TFObject_Dispenser)
		{
			SetVariantString("build_point_0");
		}
		else if(objectT == TFObject_Sentry)
		{
			if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") < 3)
				SetVariantString("build_point_0");
			else
				SetVariantString("rocket_r");
		}

		AcceptEntityInput(prop, "SetParentAttachment", entity);

		GetEntPropVector(prop, Prop_Send, "m_vecOrigin", pPos);
		GetEntPropVector(prop, Prop_Send, "m_angRotation", pAng);

		if(!StrEqual(strAnimation, "default", false))
		{
			SetVariantString(strAnimation);
			AcceptEntityInput(prop, "SetAnimation");
			SetVariantString(strAnimation);
			AcceptEntityInput(prop, "SetDefaultAnimation");
		}

		if(g_flZOffset[entity] != 0.0)
			pPos[2] += g_flZOffset[entity];
		else
			pPos[2] += flZOffset;

		if(objectT == TFObject_Dispenser)
		{
			pPos[2] += 13.0;	//Make sure the hat is on top of the dispenser
			pAng[1] += 180.0;	//Make hat face builder

			if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3)
			{
				pPos[2] += 8.0;	//Account for level 3 dispenser
			}
		}

		if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
		{
			pPos[2] += 6.5;		//Level 3 sentry offsets
			pPos[0] -= 11.0;	//Gotta get that hat on top of the missile thing
		}

		SetEntPropVector(prop, Prop_Send, "m_vecOrigin", pPos);
		SetEntPropVector(prop, Prop_Send, "m_angRotation", pAng);

		g_hatEnt[entity] = EntIndexToEntRef(prop);

		if(g_flZOffset[entity] == 0.0)
			g_flZOffset[entity] = flZOffset;

		if(g_flModelScale[entity] == 0.0)
			g_flModelScale[entity] = flModelScale;

		if(g_particleEnt[entity] == INVALID_ENT_REFERENCE && CheckCommandAccess(builder, "sm_buildinghats_unusuals", 0))
		{
			int iParticle = CreateEntityByName("info_particle_system");
			if(IsValidEdict(iParticle))
			{
				float flPos[3];
				bool kill = false;

				if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") > 1 && StrEqual(g_strParticle[entity], "", false))
					kill = true;

				int sParticle = GetRandomInt(0, sizeof(g_sParticleList)-1);

				if(!StrEqual(g_strParticle[entity], "", false))
					DispatchKeyValue(iParticle, "effect_name", g_strParticle[entity]);
				else
				{
					if(g_flCvarUnusualChance == 1.0)	//100% Unusual chance fix?
						DispatchKeyValue(iParticle, "effect_name", g_sParticleList[sParticle][0]);
					else if(GetRandomFloat(0.0, 1.0) <= g_flCvarUnusualChance)
						DispatchKeyValue(iParticle, "effect_name", g_sParticleList[sParticle][0]);
					else
						kill = true;
				}

				if(!kill)
				{
					DispatchSpawn(iParticle);

					SetVariantString("!activator");
					AcceptEntityInput(iParticle, "SetParent", entity);
					ActivateEntity(iParticle);

					if(objectT == TFObject_Dispenser)
					{
						SetVariantString("build_point_0");
					}
					else if(objectT == TFObject_Sentry)
					{
						if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") < 3)
							SetVariantString("build_point_0");
						else
							SetVariantString("rocket_r");
					}
					AcceptEntityInput(iParticle, "SetParentAttachment", entity);

					GetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					if(objectT == TFObject_Dispenser)
					{
						flPos[2] += 13.0;	//Make sure the effect is on top of the dispenser

						if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3)
							flPos[2] += 8.0;	//Account for level 3 dispenser
					}

					if(GetEntProp(entity, Prop_Send, "m_iUpgradeLevel") == 3 && objectT != TFObject_Dispenser)
					{
						flPos[2] += 6.5;	//Level 3 sentry offsets
						flPos[0] -= 11.0;	//Gotta get that effect on top of the missile thing
					}

					SetEntPropVector(iParticle, Prop_Send, "m_vecOrigin", flPos);

					AcceptEntityInput(iParticle, "start");

					g_particleEnt[entity] = EntIndexToEntRef(iParticle);

					if(StrEqual(g_strParticle[entity], "", false))
						Format(g_strParticle[entity], sizeof(g_strParticle), "%s", g_sParticleList[sParticle][0]);
				}
				else
					AcceptEntityInput(iParticle, "Kill");
			}
		}
	}
}

bool ParseConfigurations()
{
	char strPath[PLATFORM_MAX_PATH], strFileName[PLATFORM_MAX_PATH];
	Format(strFileName, sizeof(strFileName), "configs/buildinghats.cfg");
	BuildPath(Path_SM, strPath, sizeof(strPath), strFileName);

	LogMessage("[Building Hats] Executing configuration file %s", strPath);

	if (FileExists(strPath, true))
	{
		Handle kvConfig = CreateKeyValues("TF2_Buildinghats");
		if (FileToKeyValues(kvConfig, strPath) == false) SetFailState("[Building Hats] Error while parsing the configuration file.");
		KvGotoFirstSubKey(kvConfig);

		do
		{
			char strMpath[PLATFORM_MAX_PATH], strOffz[16], strScale[16], strAnima[128];

			KvGetString(kvConfig, "modelpath",	strMpath, sizeof(strMpath));
			KvGetString(kvConfig, "offset", 	strOffz,  sizeof(strOffz));
			KvGetString(kvConfig, "modelscale", strScale, sizeof(strScale));
			KvGetString(kvConfig, "animation",  strAnima, sizeof(strAnima));

			PrecacheModel(strMpath);

			PushArrayString(hHatInfo, strMpath);
			PushArrayString(hHatInfo, strOffz);
			PushArrayString(hHatInfo, strScale);
			PushArrayString(hHatInfo, strAnima);
		}
		while (KvGotoNextKey(kvConfig));

		CloseHandle(kvConfig);
	}
}

stock void DisplayMenuSafely(Handle menu, int client)
{
    if(client >= 1 && client <= MaxClients && IsClientInGame(client))
    {
        if(menu == null)
        {
            PrintToConsole(client, "ERROR: Unable to open Menu.");
        }
        else
        {
            DisplayMenu(menu, client, MENU_TIME_FOREVER);
        }
    }
}


check

check

/**
 * vim: set ts=4 :
 * =============================================================================
 * map_votes
 * Interact with the Map Votes web api - https://github.com/CrimsonTautology/map_votes
 *
 * Copyright 2013 The Crimson Tautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <socket>
#include <base64>

#undef REQUIRE_EXTENSIONS
#include <smjansson>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo = {
    name = "MapVotes",
    author = "CrimsonTautology",
    description = "Interact with the Map Votes web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_map_votes"
};

#define CAST_VOTE_ROUTE "/v1/api/cast_vote"
#define WRITE_MESSAGE_ROUTE "/v1/api/write_message"
#define SERVER_QUERY_ROUTE "/v1/api/server_query"
#define MAPS_ROUTE "/maps"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18

new Handle:g_Cvar_MapVotesUrl = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesPort = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesApiKey = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesVotingEnabled = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesCommentingEnabled = INVALID_HANDLE;

new bool:g_JanssonEnabled = false;

public OnPluginStart()
{

    g_Cvar_MapVotesUrl = CreateConVar("sm_map_votes_url", "", "URL to your Map Votes web page");
    g_Cvar_MapVotesPort = CreateConVar("sm_map_votes_port", "80", "HTTP Port used");
    g_Cvar_MapVotesApiKey = CreateConVar("sm_map_votes_api_key", "", "The API key you generated to interact with the Map Votes web page");
    g_Cvar_MapVotesVotingEnabled = CreateConVar("sm_map_votes_voting_enabled", "1", "Whether players are allowed to vote on the current map");
    g_Cvar_MapVotesCommentingEnabled = CreateConVar("sm_map_votes_commenting_enabled", "1", "Whether players are allowed to comment on the current map");

    RegConsoleCmd("sm_vote_menu", Command_VoteMenu, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_up", Command_VoteUp, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_down", Command_VoteDown, "Vote that you hate the current map");
    RegConsoleCmd("sm_map_comment", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_mc", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_view_map", Command_ViewMap, "View the Map Votes web page for this map");
    RegConsoleCmd("sm_call_vote", Command_CallVote, "Popup a vote panel to every player on the server that has not yet voted on this map");

}

public OnAllPluginsLoaded() {
	if (LibraryExists("jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryAdded(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryRemoved(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = false;
	}
}

public Action:Command_VoteMenu(client, args)
{
    if(client && IsClientAuthorized(client)){
        CallVoteOnClient(client);
    }
}

public Action:Command_VoteUp(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, 1);
    }
}

public Action:Command_VoteDown(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, -1);
    }
}

public Action:Command_ViewMap(client, args)
{
    if(client && IsClientAuthorized(client)){
        ViewMap(client);
    }
}

public Action:Command_MapComment(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[MapVotes] Usage: !map_comment <comment>");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:comment[256];
        GetCmdArgString(comment, sizeof(comment));
        WriteMessage(client, comment);
    }

    return Plugin_Handled;
}

public Action:Command_CallVote(client, args)
{
    //TODO
}

public OnSocketConnected(Handle:socket, any:headers_pack)
{
    decl String:request_string[1024];

    ResetPack(headers_pack);
    ReadPackString(headers_pack, request_string, sizeof(request_string));

    SocketSend(socket, request_string);
}

public OnSocketReceive(Handle:socket, String:receive_data[], const data_size, any:headers_pack) {
    if(g_JanssonEnabled)
    {
        //TODO parse JSON response
    } else
    {
        PrintToConsole(0,"Cannot parse JSON; SMJannson not installed");
    }
    //Used for data received back
    PrintToConsole(0,"%s", receive_data);//TODO
}

public OnSocketDisconnected(Handle:socket, any:headers_pack) {
    // Connection: close advises the webserver to close the connection when the transfer is finished
    // we're done here
    CloseHandle(headers_pack);
    CloseHandle(socket);
}

public OnSocketError(Handle:socket, const error_type, const error_num, any:headers_pack) {
    // a socket error occured
    if(error_type == EMPTY_HOST )
    {
        LogError("[MapVotes] Empty Host (errno %d)", error_num);
    } else if (error_type == NO_HOST )
    {
        LogError("[MapVotes] No Host (errno %d)", error_num);
    } else if (error_type == CONNECT_ERROR )
    {
        LogError("[MapVotes] Connection Error (errno %d)", error_num);
    } else if (error_type == SEND_ERROR )
    {
        LogError("[MapVotes] Send Error (errno %d)", error_num);
    } else if (error_type == BIND_ERROR )
    {
        LogError("[MapVotes] Bind Error (errno %d)", error_num);
    } else if (error_type == RECV_ERROR )
    {
        LogError("[MapVotes] Recieve Error (errno %d)", error_num);
    } else if (error_type == LISTEN_ERROR )
    {
        LogError("[MapVotes] Listen Error (errno %d)", error_num);
    } else
    {
        LogError("[MapVotes] socket error %d (errno %d)", errorType, error_num);
    }

    CloseHandle(headers_pack);
    CloseHandle(socket);
}

//By 11530
//GetSteamAccountID(client) does not work because we don't have 64 bit types
stock bool:GetCommunityIDString(const String:SteamID[], String:CommunityID[], const CommunityIDSize)
{
    decl String:SteamIDParts[3][11];
    new const String:Identifier[] = "76561197960265728";

    if ((CommunityIDSize < 1) || (ExplodeString(SteamID, ":", SteamIDParts, sizeof(SteamIDParts), sizeof(SteamIDParts[])) != 3))
    {
        CommunityID[0] = '\0';
        return false;
    }

    new Current, CarryOver = (SteamIDParts[1][0] == '1');
    for (new i = (CommunityIDSize - 2), j = (strlen(SteamIDParts[2]) - 1), k = (strlen(Identifier) - 1); i >= 0; i--, j--, k--)
    {
        Current = (j >= 0 ? (2 * (SteamIDParts[2][j] - '0')) : 0) + CarryOver + (k >= 0 ? ((Identifier[k] - '0') * 1) : 0);
        CarryOver = Current / 10;
        CommunityID[i] = (Current % 10) + '0';
    }

    CommunityID[CommunityIDSize - 1] = '\0';
    return true;
}

public MapVotesCall(String:route[128], String:query_params[512])
{
    new port= GetConVarInt(g_Cvar_MapVotesPort);
    decl String:base_url[128], String:api_key[128];
    GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
    GetConVarString(g_Cvar_MapVotesApiKey, api_key, sizeof(api_key));

    ReplaceString(base_url, sizeof(base_url), "http://", "", false);
    ReplaceString(base_url, sizeof(base_url), "https://", "", false);

    Format(query_params, sizeof(query_params), "%s&access_token=%s", query_params, api_key);

    HTTPPost(base_url, route, query_params, port);
}

public HTTPPost(String:base_url[128], String:route[128], String:query_params[512], port)
{
    new Handle:socket = SocketCreate(SOCKET_TCP, OnSocketError);

    //This Formats the headers needed to make a HTTP/1.1 POST request.
    new String:request_string[1024];
    Format(request_string, sizeof(request_string), "POST %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: %d\r\n\r\n%s", route, base_url, strlen(headers), headers);

    new Handle:headers_pack = CreateDataPack();
    WritePackString(headers_pack, request_string);
    SocketSetArg(socket, headers_pack);

    SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, base_url, port);
}
public WriteMessage(client, String:message[256])
{
    //Encode the message to be url safe
    decl String:base64[256], String:base64_url[256];
    EncodeBase64(base64, sizeof(base64), message);
    Base64MimeToUrl(base64_url, sizeof(base64_url), base64);

    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    decl String:query_params[512], String:map[128];
    GetCurrentMap(map, sizeof(map));
    Format(query_params, sizeof(query_params),
            "map=%s&uid=%s&comment=%s&base64=true", map, uid, base64);

    MapVotesCall(WRITE_MESSAGE_ROUTE, query_params);
}

public CastVote(client, value)
{
    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    if(!(value == -1 || value == 0 || value == 1)){
        LogError("[MapVotes] invalid vote value %d (steam_user: %s)", value, uid);
    }else{
        decl String:query_params[512], String:map[128];

        GetCurrentMap(map, sizeof(map));

        Format(query_params, sizeof(query_params),
                "map=%s&uid=%s&value=%d", map, uid, value);

        MapVotesCall(CAST_VOTE_ROUTE, query_params);
    }

}

public CallVoteOnClient(client)
{
    new Handle:menu = CreateMenu(VoteMenuHandler);
    SetMenuTitle(menu, "Do you like this map?");
    AddMenuItem(menu, "1","Like it.");
    AddMenuItem(menu, "-1","Hate it.");
    AddMenuItem(menu, "0","I have no strong feelings one way or the other.");
    DisplayMenu(menu, client, 20);
}

public VoteMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_End)
    {
        CloseHandle(menu);
    } else if (action == MenuAction_VoteCancel)
    {
    } else if (action == MenuAction_Select)
    {
        new String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        new value = StringToInt(info);

        CastVote(param1, value);
    }
}
public ViewMap(client)
{
        decl String:map[128], String:url[256], String:base_url[128];
        GetCurrentMap(map, sizeof(map));
        GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
        ReplaceString(base_url, sizeof(base_url), "http://", "", false);
        ReplaceString(base_url, sizeof(base_url), "https://", "", false);

        Format(url, sizeof(url),
                "http://%s%s/%s", base_url, MAPS_ROUTE, map);

        ShowMOTDPanel(client, "Map Viewer", url, MOTDPANEL_TYPE_URL);

}

public ServerQuery()
{
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * map_votes
 * Interact with the Map Votes web api - https://github.com/CrimsonTautology/map_votes
 *
 * Copyright 2013 The Crimson Tautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <socket>
#include <base64>

#undef REQUIRE_EXTENSIONS
#include <smjansson>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo = {
    name = "MapVotes",
    author = "CrimsonTautology",
    description = "Interact with the Map Votes web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_map_votes"
};

#define CAST_VOTE_ROUTE "/v1/api/cast_vote"
#define WRITE_MESSAGE_ROUTE "/v1/api/write_message"
#define SERVER_QUERY_ROUTE "/v1/api/server_query"
#define MAPS_ROUTE "/maps"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18

new Handle:g_Cvar_MapVotesUrl = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesPort = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesApiKey = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesVotingEnabled = INVALID_HANDLE;
new Handle:g_Cvar_MapVotesCommentingEnabled = INVALID_HANDLE;

new bool:g_JanssonEnabled = false;

public OnPluginStart()
{

    g_Cvar_MapVotesUrl = CreateConVar("sm_map_votes_url", "", "URL to your Map Votes web page");
    g_Cvar_MapVotesPort = CreateConVar("sm_map_votes_port", "80", "HTTP Port used");
    g_Cvar_MapVotesApiKey = CreateConVar("sm_map_votes_api_key", "", "The API key you generated to interact with the Map Votes web page");
    g_Cvar_MapVotesVotingEnabled = CreateConVar("sm_map_votes_voting_enabled", "1", "Whether players are allowed to vote on the current map");
    g_Cvar_MapVotesCommentingEnabled = CreateConVar("sm_map_votes_commenting_enabled", "1", "Whether players are allowed to comment on the current map");

    RegConsoleCmd("sm_vote_menu", Command_VoteMenu, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_up", Command_VoteUp, "Vote that you like the current map");
    RegConsoleCmd("sm_vote_down", Command_VoteDown, "Vote that you hate the current map");
    RegConsoleCmd("sm_map_comment", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_mc", Command_MapComment, "Comment on the current map");
    RegConsoleCmd("sm_view_map", Command_ViewMap, "View the Map Votes web page for this map");
    RegConsoleCmd("sm_call_vote", Command_CallVote, "Popup a vote panel to every player on the server that has not yet voted on this map");

}

public OnAllPluginsLoaded() {
	if (LibraryExists("jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryAdded(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = true;
	}
}

public OnLibraryRemoved(const String:name[]) {
	if (StrEqual(name, "jansson")) {
		g_JanssonEnabled = false;
	}
}

public Action:Command_VoteMenu(client, args)
{
    if(client && IsClientAuthorized(client)){
        CallVoteOnClient(client);
    }
}

public Action:Command_VoteUp(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, 1);
    }
}

public Action:Command_VoteDown(client, args)
{
    if(client && IsClientAuthorized(client)){
        CastVote(client, -1);
    }
}

public Action:Command_ViewMap(client, args)
{
    if(client && IsClientAuthorized(client)){
        ViewMap(client);
    }
}

public Action:Command_MapComment(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[MapVotes] Usage: !map_comment <comment>");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:comment[256];
        GetCmdArgString(comment, sizeof(comment));
        WriteMessage(client, comment);
    }

    return Plugin_Handled;
}

public Action:Command_CallVote(client, args)
{
    //TODO
}

public OnSocketConnected(Handle:socket, any:headers_pack)
{
    decl String:request_string[1024];

    ResetPack(headers_pack);
    ReadPackString(headers_pack, request_string, sizeof(request_string));

    SocketSend(socket, request_string);
}

public OnSocketReceive(Handle:socket, String:receive_data[], const data_size, any:headers_pack) {
    if(g_JanssonEnabled) {
        //TODO parse JSON response
    }
    //Used for data received back
    PrintToConsole(0,"%s", receive_data);//TODO
}

public OnSocketDisconnected(Handle:socket, any:headers_pack) {
    // Connection: close advises the webserver to close the connection when the transfer is finished
    // we're done here
    CloseHandle(headers_pack);
    CloseHandle(socket);
}

public OnSocketError(Handle:socket, const error_type, const error_num, any:headers_pack) {
    // a socket error occured
    if(error_type == EMPTY_HOST )
    {
        LogError("[MapVotes] Empty Host (errno %d)", error_num);
    } else if (error_type == NO_HOST )
    {
        LogError("[MapVotes] No Host (errno %d)", error_num);
    } else if (error_type == CONNECT_ERROR )
    {
        LogError("[MapVotes] Connection Error (errno %d)", error_num);
    } else if (error_type == SEND_ERROR )
    {
        LogError("[MapVotes] Send Error (errno %d)", error_num);
    } else if (error_type == BIND_ERROR )
    {
        LogError("[MapVotes] Bind Error (errno %d)", error_num);
    } else if (error_type == RECV_ERROR )
    {
        LogError("[MapVotes] Recieve Error (errno %d)", error_num);
    } else if (error_type == LISTEN_ERROR )
    {
        LogError("[MapVotes] Listen Error (errno %d)", error_num);
    } else
    {
        LogError("[MapVotes] socket error %d (errno %d)", errorType, error_num);
    }

    CloseHandle(headers_pack);
    CloseHandle(socket);
}

//By 11530
//GetSteamAccountID(client) does not work because we don't have 64 bit types
stock bool:GetCommunityIDString(const String:SteamID[], String:CommunityID[], const CommunityIDSize)
{
    decl String:SteamIDParts[3][11];
    new const String:Identifier[] = "76561197960265728";

    if ((CommunityIDSize < 1) || (ExplodeString(SteamID, ":", SteamIDParts, sizeof(SteamIDParts), sizeof(SteamIDParts[])) != 3))
    {
        CommunityID[0] = '\0';
        return false;
    }

    new Current, CarryOver = (SteamIDParts[1][0] == '1');
    for (new i = (CommunityIDSize - 2), j = (strlen(SteamIDParts[2]) - 1), k = (strlen(Identifier) - 1); i >= 0; i--, j--, k--)
    {
        Current = (j >= 0 ? (2 * (SteamIDParts[2][j] - '0')) : 0) + CarryOver + (k >= 0 ? ((Identifier[k] - '0') * 1) : 0);
        CarryOver = Current / 10;
        CommunityID[i] = (Current % 10) + '0';
    }

    CommunityID[CommunityIDSize - 1] = '\0';
    return true;
}

public MapVotesCall(String:route[128], String:query_params[512])
{
    new port= GetConVarInt(g_Cvar_MapVotesPort);
    decl String:base_url[128], String:api_key[128];
    GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
    GetConVarString(g_Cvar_MapVotesApiKey, api_key, sizeof(api_key));

    ReplaceString(base_url, sizeof(base_url), "http://", "", false);
    ReplaceString(base_url, sizeof(base_url), "https://", "", false);

    Format(query_params, sizeof(query_params), "%s&access_token=%s", query_params, api_key);

    HTTPPost(base_url, route, query_params, port);
}

public HTTPPost(String:base_url[128], String:route[128], String:query_params[512], port)
{
    new Handle:socket = SocketCreate(SOCKET_TCP, OnSocketError);

    //This Formats the headers needed to make a HTTP/1.1 POST request.
    new String:request_string[1024];
    Format(request_string, sizeof(request_string), "POST %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: %d\r\n\r\n%s", route, base_url, strlen(headers), headers);

    new Handle:headers_pack = CreateDataPack();
    WritePackString(headers_pack, request_string);
    SocketSetArg(socket, headers_pack);

    SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, base_url, port);
}
public WriteMessage(client, String:message[256])
{
    //Encode the message to be url safe
    decl String:base64[256], String:base64_url[256];
    EncodeBase64(base64, sizeof(base64), message);
    Base64MimeToUrl(base64_url, sizeof(base64_url), base64);

    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    decl String:query_params[512], String:map[128];
    GetCurrentMap(map, sizeof(map));
    Format(query_params, sizeof(query_params),
            "map=%s&uid=%s&comment=%s&base64=true", map, uid, base64);

    MapVotesCall(WRITE_MESSAGE_ROUTE, query_params);
}

public CastVote(client, value)
{
    decl String:buffer[MAX_STEAMID_LENGTH], String:uid[MAX_COMMUNITYID_LENGTH];
    GetClientAuthString(client, buffer, sizeof(buffer));
    GetCommunityIDString(buffer, uid, sizeof(uid));

    if(!(value == -1 || value == 0 || value == 1)){
        LogError("[MapVotes] invalid vote value %d (steam_user: %s)", value, uid);
    }else{
        decl String:query_params[512], String:map[128];

        GetCurrentMap(map, sizeof(map));

        Format(query_params, sizeof(query_params),
                "map=%s&uid=%s&value=%d", map, uid, value);

        MapVotesCall(CAST_VOTE_ROUTE, query_params);
    }

}

public CallVoteOnClient(client)
{
    new Handle:menu = CreateMenu(VoteMenuHandler);
    SetMenuTitle(menu, "Do you like this map?");
    AddMenuItem(menu, "1","Like it.");
    AddMenuItem(menu, "-1","Hate it.");
    AddMenuItem(menu, "0","I have no strong feelings one way or the other.");
    DisplayMenu(menu, client, 20);
}

public VoteMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_End)
    {
        CloseHandle(menu);
    } else if (action == MenuAction_VoteCancel)
    {
    } else if (action == MenuAction_Select)
    {
        new String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        new value = StringToInt(info);

        CastVote(param1, value);
    }
}
public ViewMap(client)
{
        decl String:map[128], String:url[256], String:base_url[128];
        GetCurrentMap(map, sizeof(map));
        GetConVarString(g_Cvar_MapVotesUrl, base_url, sizeof(base_url));
        ReplaceString(base_url, sizeof(base_url), "http://", "", false);
        ReplaceString(base_url, sizeof(base_url), "https://", "", false);

        Format(url, sizeof(url),
                "http://%s%s/%s", base_url, MAPS_ROUTE, map);

        ShowMOTDPanel(client, "Map Viewer", url, MOTDPANEL_TYPE_URL);

}

public ServerQuery()
{
}