Add coop_fall

Add coop_fall

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <mapchooser_redux>

#undef REQUIRE_PLUGIN
#include <fys.pupd>

int g_iMapCount = 0;
bool g_bStartup = true;

ConVar mcr_delete_offical_map;
ConVar mcr_generate_mapcycle;
ConVar mcr_generate_mapgroup;

public Plugin myinfo =
{
    name        = "Map Lister Redux",
    author      = "Kyle",
    description = "Automated Map Voting with Extensions",
    version     = MCR_VERSION,
    url         = "https://www.kxnrl.com"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Pupd_CheckPlugin");
    return APLRes_Success;
}

public void OnPluginStart()
{
    mcr_delete_offical_map = CreateConVar("mcr_delete_offical_map", "1", "auto-delete offical maps", _, true, 0.0, true, 1.0);
    mcr_generate_mapcycle  = CreateConVar("mcr_generate_mapcycle",  "1", "auto-generate map list in mapcycle.txt", _, true, 0.0, true, 1.0);
    mcr_generate_mapgroup  = CreateConVar("mcr_generate_mapgroup",  "1", "auto-generate map group in gamemodes_server.txt", _, true, 0.0, true, 1.0);

    if (!DirExists("cfg/sourcemod/mapchooser"))
        if (!CreateDirectory("cfg/sourcemod/mapchooser", 511))
            SetFailState("Failed to create folder \"cfg/sourcemod/mapchooser\"");

    AutoExecConfig(true, "maplister_redux", "sourcemod/mapchooser");

    g_iMapCount = GetMapCount();

    CreateTimer(600.0, Timer_Detected, _, TIMER_REPEAT);
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public Action Timer_Detected(Handle timer)
{
    int count = GetMapCount();
    if (count != g_iMapCount)
    {
        LogMessage("Detected: Map count was changed! last check: %d  current: %d", g_iMapCount, count);
        DeleteMap();
        MapCycle();
        MapGroup();
    }
    return Plugin_Continue;
}

public void OnConfigsExecuted()
{
    if (g_bStartup)
    {
        g_bStartup = false;
        DeleteMap();
        MapCycle();
        MapGroup();
    }
}

static int GetMapCount()
{
    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
        ThrowError("Failed to open maps.");

    int count = 0;

    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        count++;
    }
    delete dir;

    return count;
}

static void DeleteMap()
{
    if (!mcr_delete_offical_map.BoolValue)
        return;

    LogMessage("Process delete offical maps ...");

    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("DeleteMap -> Failed to open maps");
        return;
    }

    g_iMapCount = 0;

    FileType type = FileType_Unknown;
    char map[128]; bool deleted = false;
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        if (!IsOfficalMap(map))
        {
            g_iMapCount++;
            continue;
        }

        Format(map, 128, "maps/%s.bsp", map);

        deleted = true;
        LogMessage("%s delete offical map [%s]", DeleteFile(map) ? "Successful" : "Failed", map);
    }
    delete dir;

    if (deleted)
        ServerCommand("sm_reloadmcr");
}

static void MapCycle()
{
    if (!mcr_generate_mapcycle.BoolValue)
        return;

    LogMessage("Process generate mapcycle ...");

    File file = OpenFile("mapcycle.txt", "w+");
    if (file == null)
    {
        LogError("MapCycle -> Failed to open mapcycle.txt");
        return;
    }

    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("MapCycle -> Failed to open maps");
        return;
    }

    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        file.WriteLine(map);
    }
    delete dir;
    file.Close();
}

static void MapGroup()
{
    if (!mcr_generate_mapgroup.BoolValue)
        return;

    LogMessage("Process generate mapgroup ...");

    KeyValues kv = new KeyValues("GameModes_Server.txt");

    if (FileExists("gamemodes_server.txt"))
        kv.ImportFromFile("gamemodes_server.txt");

    kv.JumpToKey("mapgroups", true);

    if (kv.JumpToKey("custom_maps", false))
    {
        kv.GoBack();
        kv.DeleteKey("custom_maps");
    }

    kv.JumpToKey("custom_maps", true);

    kv.SetString("name", "custom_maps");

    kv.JumpToKey("maps", true);

    // foreach
    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("MapGroup -> Failed to open maps");
        delete kv;
        return;
    }
    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        kv.SetString(map, " ");
    }
    delete dir;

    kv.Rewind();
    kv.ExportToFile("gamemodes_server.txt");

    delete kv;
}

static bool IsOfficalMap(const char[] map)
{
    static ArrayList officalmaps = null;
    if (officalmaps == null)
    {
        // create
        officalmaps = new ArrayList(ByteCountToCells(32));

        // Operation Broken Fang
        officalmaps.PushString("coop_autumn");
        officalmaps.PushString("coop_fall");
        officalmaps.PushString("cs_apollo");
        officalmaps.PushString("de_ancient");
        officalmaps.PushString("de_elysion");
        officalmaps.PushString("de_engage");
        officalmaps.PushString("de_guard");
        officalmaps.PushString("dz_frostbite");
        officalmaps.PushString("lobby_mapveto");

        // input
        officalmaps.PushString("ar_baggage");
        officalmaps.PushString("ar_dizzy");
        officalmaps.PushString("ar_lunacy");
        officalmaps.PushString("ar_monastery");
        officalmaps.PushString("ar_shoots");
        officalmaps.PushString("coop_kasbah");
        officalmaps.PushString("cs_agency");
        officalmaps.PushString("cs_assault");
        officalmaps.PushString("cs_italy");
        officalmaps.PushString("cs_militia");
        officalmaps.PushString("cs_office");
        officalmaps.PushString("de_swamp");
        officalmaps.PushString("de_mutiny");
        officalmaps.PushString("de_bank");
        officalmaps.PushString("de_anubis");
        officalmaps.PushString("de_breach");
        officalmaps.PushString("de_cache");
        officalmaps.PushString("de_canals");
        officalmaps.PushString("de_cbble");
        officalmaps.PushString("de_chlorine");
        officalmaps.PushString("de_dust2");
        officalmaps.PushString("de_inferno");
        officalmaps.PushString("de_lake");
        officalmaps.PushString("de_mirage");
        officalmaps.PushString("de_nuke");
        officalmaps.PushString("de_overpass");
        officalmaps.PushString("de_safehouse");
        officalmaps.PushString("de_shortdust");
        officalmaps.PushString("de_shortnuke");
        officalmaps.PushString("de_stmarc");
        officalmaps.PushString("de_studio");
        officalmaps.PushString("de_sugarcane");
        officalmaps.PushString("de_train");
        officalmaps.PushString("de_vertigo");
        officalmaps.PushString("dz_blacksite");
        officalmaps.PushString("dz_junglety");
        officalmaps.PushString("dz_sirocco");
        officalmaps.PushString("gd_cbble");
        officalmaps.PushString("gd_rialto");
        officalmaps.PushString("training1");
    }

    return (officalmaps.FindString(map) > -1);
}

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <mapchooser_redux>

#undef REQUIRE_PLUGIN
#include <fys.pupd>

int g_iMapCount = 0;
bool g_bStartup = true;

ConVar mcr_delete_offical_map;
ConVar mcr_generate_mapcycle;
ConVar mcr_generate_mapgroup;

public Plugin myinfo =
{
    name        = "Map Lister Redux",
    author      = "Kyle",
    description = "Automated Map Voting with Extensions",
    version     = MCR_VERSION,
    url         = "https://www.kxnrl.com"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Pupd_CheckPlugin");
    return APLRes_Success;
}

public void OnPluginStart()
{
    mcr_delete_offical_map = CreateConVar("mcr_delete_offical_map", "1", "auto-delete offical maps", _, true, 0.0, true, 1.0);
    mcr_generate_mapcycle  = CreateConVar("mcr_generate_mapcycle",  "1", "auto-generate map list in mapcycle.txt", _, true, 0.0, true, 1.0);
    mcr_generate_mapgroup  = CreateConVar("mcr_generate_mapgroup",  "1", "auto-generate map group in gamemodes_server.txt", _, true, 0.0, true, 1.0);

    if (!DirExists("cfg/sourcemod/mapchooser"))
        if (!CreateDirectory("cfg/sourcemod/mapchooser", 511))
            SetFailState("Failed to create folder \"cfg/sourcemod/mapchooser\"");

    AutoExecConfig(true, "maplister_redux", "sourcemod/mapchooser");

    g_iMapCount = GetMapCount();

    CreateTimer(600.0, Timer_Detected, _, TIMER_REPEAT);
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public Action Timer_Detected(Handle timer)
{
    int count = GetMapCount();
    if (count != g_iMapCount)
    {
        LogMessage("Detected: Map count was changed! last check: %d  current: %d", g_iMapCount, count);
        DeleteMap();
        MapCycle();
        MapGroup();
    }
    return Plugin_Continue;
}

public void OnConfigsExecuted()
{
    if (g_bStartup)
    {
        g_bStartup = false;
        DeleteMap();
        MapCycle();
        MapGroup();
    }
}

static int GetMapCount()
{
    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
        ThrowError("Failed to open maps.");

    int count = 0;

    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        count++;
    }
    delete dir;

    return count;
}

static void DeleteMap()
{
    if (!mcr_delete_offical_map.BoolValue)
        return;

    LogMessage("Process delete offical maps ...");

    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("DeleteMap -> Failed to open maps");
        return;
    }

    g_iMapCount = 0;

    FileType type = FileType_Unknown;
    char map[128]; bool deleted = false;
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        if (!IsOfficalMap(map))
        {
            g_iMapCount++;
            continue;
        }

        Format(map, 128, "maps/%s.bsp", map);

        deleted = true;
        LogMessage("%s delete offical map [%s]", DeleteFile(map) ? "Successful" : "Failed", map);
    }
    delete dir;

    if (deleted)
        ServerCommand("sm_reloadmcr");
}

static void MapCycle()
{
    if (!mcr_generate_mapcycle.BoolValue)
        return;

    LogMessage("Process generate mapcycle ...");

    File file = OpenFile("mapcycle.txt", "w+");
    if (file == null)
    {
        LogError("MapCycle -> Failed to open mapcycle.txt");
        return;
    }

    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("MapCycle -> Failed to open maps");
        return;
    }

    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        file.WriteLine(map);
    }
    delete dir;
    file.Close();
}

static void MapGroup()
{
    if (!mcr_generate_mapgroup.BoolValue)
        return;

    LogMessage("Process generate mapgroup ...");

    KeyValues kv = new KeyValues("GameModes_Server.txt");

    if (FileExists("gamemodes_server.txt"))
        kv.ImportFromFile("gamemodes_server.txt");

    kv.JumpToKey("mapgroups", true);

    if (kv.JumpToKey("custom_maps", false))
    {
        kv.GoBack();
        kv.DeleteKey("custom_maps");
    }

    kv.JumpToKey("custom_maps", true);

    kv.SetString("name", "custom_maps");

    kv.JumpToKey("maps", true);

    // foreach
    DirectoryListing dir = OpenDirectory("maps");
    if (dir == null)
    {
        LogError("MapGroup -> Failed to open maps");
        delete kv;
        return;
    }
    FileType type = FileType_Unknown;
    char map[128];
    while(dir.GetNext(map, 128, type))
    {
        if (type != FileType_File || StrContains(map, ".bsp", false) == -1)
            continue;

        int c = FindCharInString(map, '.', true);
        map[c] = '\0';

        kv.SetString(map, " ");
    }
    delete dir;

    kv.Rewind();
    kv.ExportToFile("gamemodes_server.txt");

    delete kv;
}

static bool IsOfficalMap(const char[] map)
{
    static ArrayList officalmaps = null;
    if (officalmaps == null)
    {
        // create
        officalmaps = new ArrayList(ByteCountToCells(32));

        // Operation Broken Fang
        officalmaps.PushString("coop_autumn");
        officalmaps.PushString("cs_apollo");
        officalmaps.PushString("de_ancient");
        officalmaps.PushString("de_elysion");
        officalmaps.PushString("de_engage");
        officalmaps.PushString("de_guard");
        officalmaps.PushString("dz_frostbite");
        officalmaps.PushString("lobby_mapveto");

        // input
        officalmaps.PushString("ar_baggage");
        officalmaps.PushString("ar_dizzy");
        officalmaps.PushString("ar_lunacy");
        officalmaps.PushString("ar_monastery");
        officalmaps.PushString("ar_shoots");
        officalmaps.PushString("coop_kasbah");
        officalmaps.PushString("cs_agency");
        officalmaps.PushString("cs_assault");
        officalmaps.PushString("cs_italy");
        officalmaps.PushString("cs_militia");
        officalmaps.PushString("cs_office");
        officalmaps.PushString("de_swamp");
        officalmaps.PushString("de_mutiny");
        officalmaps.PushString("de_bank");
        officalmaps.PushString("de_anubis");
        officalmaps.PushString("de_breach");
        officalmaps.PushString("de_cache");
        officalmaps.PushString("de_canals");
        officalmaps.PushString("de_cbble");
        officalmaps.PushString("de_chlorine");
        officalmaps.PushString("de_dust2");
        officalmaps.PushString("de_inferno");
        officalmaps.PushString("de_lake");
        officalmaps.PushString("de_mirage");
        officalmaps.PushString("de_nuke");
        officalmaps.PushString("de_overpass");
        officalmaps.PushString("de_safehouse");
        officalmaps.PushString("de_shortdust");
        officalmaps.PushString("de_shortnuke");
        officalmaps.PushString("de_stmarc");
        officalmaps.PushString("de_studio");
        officalmaps.PushString("de_sugarcane");
        officalmaps.PushString("de_train");
        officalmaps.PushString("de_vertigo");
        officalmaps.PushString("dz_blacksite");
        officalmaps.PushString("dz_junglety");
        officalmaps.PushString("dz_sirocco");
        officalmaps.PushString("gd_cbble");
        officalmaps.PushString("gd_rialto");
        officalmaps.PushString("training1");
    }

    return (officalmaps.FindString(map) > -1);
}


Remove limiting spycrab menu

Remove limiting spycrab menu

Could probably even bring in a queue system :)

/**
* TheXeon
* ngs_spycrab.sp
*
* Files:
* addons/sourcemod/plugins/ngs_spycrab.smx
* addons/sourcemod/configs/crablocations.cfg
*
* Dependencies:
* sdkhooks.inc, adminmenu.inc, multicolors.inc, tf2_stocks.inc,
* ngsutils.inc, ngsupdater.inc
*/
#include <sdkhooks>
#include <adminmenu>
#include <multicolors>
#include <tf2_stocks>
#include <ngsutils>
#include <ngsupdater>

// Obviously inspired by Erreur 500
enum CrabData
{
	Challenger,
	bool:IsEnabled,
	TimesCrabbed,
	SMTimer:MovementTimer,
	TimesTaunted,
	UserId,
	TFTeam:Team,
	bool:IsFirstClient,
	CrabType:Type,
}

enum CrabType
{
	FirstToThreeWins,
	FirstToThreeLoses
}

ConVar mapNameContains;
KeyValues mapLocations;

bool spycrabInProgress;
int playerCrabData[MAXPLAYERS + 1][CrabData];
int hudTextChannel;
float firstClientOrigin[3], secondClientOrigin[3];

public Plugin myinfo = {
	name = "[NGS] Spycrab Suite",
	author = "TheXeon / EasyE",
	description = "Automate a spycrab with people.",
	version = "1.1.0",
	url = "https://www.neogenesisnetwork.net"
}

public void OnPluginStart()
{
	RegConsoleCmd("sm_spycrab", CommandCrab, "Displays spycrab target menu.");
	RegConsoleCmd("sm_crab", CommandCrab, "Displays spycrab target menu.");
	RegAdminCmd("sm_cancelcrab", CommandCancelCrab, ADMFLAG_GENERIC, "Cancels the spycrab.");
	RegAdminCmd("sm_reloadcrab", CommandReloadCrabConfig, ADMFLAG_GENERIC, "Reloads the map config file.");

	mapNameContains = CreateConVar("sm_spycrab_config_contains", "1", "Whether map names in config will be checked partially or fully.");
	HookEvent("player_death", OnPlayerDeath);
	HookEvent("player_disconnect", OnPlayerDisconnect, EventHookMode_Pre);

	LoadConfig();
}

// Thank you Dr.Mckay and your CCC
public void LoadConfig()
{
	char configFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, configFile, sizeof(configFile), "configs/crablocations.cfg");
	if (!FileExists(configFile))
	{
		SetFailState("Missing config file (should be at %s)! Please get it from the repo!", configFile);
	}
	delete mapLocations;
	mapLocations = new KeyValues("Locations");
	if (!mapLocations.ImportFromFile(configFile))
	{
		SetFailState("Invalid config file at %s! Please fix it!", configFile);
	}
}

public void OnMapStart()
{
	char mapName[MAX_BUFFER_LENGTH];
	GetCurrentMap(mapName, sizeof(mapName));
	FindMapCoords(mapName);
}

void FindMapCoords(char[] mapName)
{
	firstClientOrigin = NULL_VECTOR;
	secondClientOrigin = NULL_VECTOR;
	char buffer[MAX_BUFFER_LENGTH];
	mapLocations.Rewind();
	if (!mapLocations.JumpToKey(mapName))
	{
		if (mapNameContains.BoolValue)
		{
			mapLocations.Rewind();
			mapLocations.GotoFirstSubKey();
			do
			{
				mapLocations.GetSectionName(buffer, sizeof(buffer));
				if (StrContains(mapName, buffer, false) != -1)
				{
					SetMapCoords(buffer);
					break;
				}
			}
			while (mapLocations.GotoNextKey());
		}
		else
		{
			LogError("Map %s is not in config file!", mapName);
		}
	}
	else
	{
		SetMapCoords(mapName);
	}
}

void SetMapCoords(char[] sectionName)
{
	char firstClientBuffer[MAX_BUFFER_LENGTH], secondClientBuffer[MAX_BUFFER_LENGTH],
		firstClientVector[3][MAX_BUFFER_LENGTH], secondClientVector[3][MAX_BUFFER_LENGTH];
	mapLocations.GetString("firstClient", firstClientBuffer, sizeof(firstClientBuffer));
	mapLocations.GetString("secondClient", secondClientBuffer, sizeof(secondClientBuffer));
	if (firstClientBuffer[0] != '\0' && secondClientBuffer[0] != '\0')
	{
		ExplodeString(firstClientBuffer, ",", firstClientVector, sizeof(firstClientVector), sizeof(firstClientVector[]));
		ExplodeString(secondClientBuffer, ",", secondClientVector, sizeof(secondClientVector), sizeof(secondClientVector[]));
		for (int i = 0; i < 3; i++)
		{
			firstClientOrigin[i] = StringToFloat(firstClientVector[i]);
			secondClientOrigin[i] = StringToFloat(secondClientVector[i]);\
		}
	}
	else
	{
		LogError("Invalidly formatted location string in config file around Section \'%s\'. Make sure it is a comma separated 3d vector!", sectionName);
	}
}

public Action CommandReloadCrabConfig(int client, int args)
{
	if (mapLocations == null)
	{
		CReplyToCommand(client, "The config file does not currently exist!");
	}
	else
	{
		char mapName[MAX_BUFFER_LENGTH];
		GetCurrentMap(mapName, sizeof(mapName));
		LoadConfig();
		FindMapCoords(mapName);
		CReplyToCommand(client, "Locations have been reloaded!");
	}
	return Plugin_Handled;
}

public Action CommandCrab(int client, int args)
{
	if (!IsValidClient(client, true)) return Plugin_Handled;
	if (spycrabInProgress)
	{
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} There is currently another spycrab happening.");
	}
	else if (firstClientOrigin[0] == NULL_VECTOR[0])
	{
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} The map is not configured for this plugin, notify an admin!");
	}
	else
	{
		Menu spycrabMenu = new Menu(SpycrabMenuHandler);
		spycrabMenu.SetTitle("Select a player:");
		AddTargetsToMenu(spycrabMenu, 0, true, true);
		spycrabMenu.Display(client, 20);
	}
	return Plugin_Handled;
}

public Action CommandCancelCrab(int client, int args)
{
	if (!spycrabInProgress)
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} There isn\'t any spycrab going on right now.");
	else
		ResetSpycrabClients(true);
	return Plugin_Handled;
}

public int SpycrabMenuHandler(Menu menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select)
	{
		char target[16];
		if (menu.GetItem(param2, target, sizeof(target)))
		{
			int targetUserId = StringToInt(target);
			int iTarget = GetClientOfUserId(targetUserId);
			if (param1 == iTarget)
			{
				CPrintToChat(param1, "{LIGHTGREEN}[Crab]{DEFAULT} You may not target yourself!");
				return;
			}
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N has challenged %N to a spycrab showdown!", param1, iTarget);
			Menu acceptMenu = new Menu(AcceptMenuHandler);
			acceptMenu.SetTitle("Do you accept?");
			acceptMenu.AddItem("yes", "Yes!");
			acceptMenu.AddItem("no", "No!");
			acceptMenu.Display(iTarget, 20);
			playerCrabData[param1][Challenger] = iTarget;
			playerCrabData[iTarget][Challenger] = param1;
		}
	}
	if (action == MenuAction_End)
	{
		delete menu;
	}
}

public int AcceptMenuHandler(Menu menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select)
	{
		char answer[8];
		int firstClient = playerCrabData[param1][Challenger];
		if (menu.GetItem(param2, answer, sizeof(answer)) && IsValidClient(firstClient) && IsValidClient(param1))
		{
			if (StrEqual(answer, "yes", false))
			{
				CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N accepted %N\'s spycrab!", param1, firstClient);
				LogAction(param1, firstClient, "%N has accepted %N\'s spycrab.", param1, firstClient);
				StartSpyCrab(param1);
			}
			else
			{
				CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N declined %N\'s spycrab!", param1, firstClient);
				ResetSpycrabClients(false, param1);
			}
		}
		else
		{
			ResetSpycrabClients(false, param1);
		}
	}
	if (action == MenuAction_Cancel)
	{
		ResetSpycrabClients(false, param1);
	}
	if (action == MenuAction_End)
	{
		delete menu;
	}
}

public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])
{
	if (spycrabInProgress && playerCrabData[client][IsEnabled] && impulse >= 221 && impulse <=239 && impulse != 230)
	{
		CPrintToChat(client, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not attempt to disguise!");
		return Plugin_Handled;
	}
	return Plugin_Continue;
}

public void StartSpyCrab(int client)
{
	spycrabInProgress = true;
	// SetHudTextParams(0.1, 0.5, 22.0, 102, 51, 153, 255);
	SetHudTextParams(0.1, 0.5, 22.0, 0, 255, 0, 255);
	hudTextChannel = ShowHudText(client, -1, "Starting spycrab...");
	playerCrabData[client][IsFirstClient] = true;
	PreparePlayerForSpycrab(client);
	PreparePlayerForSpycrab(playerCrabData[client][Challenger]);
}

void PreparePlayerForSpycrab(int client)
{
	if (!IsValidClient(client))
	{
		spycrabInProgress = false;
		return;
	}
	TF2_SetPlayerClass(client, TFClass_Spy);
	TF2_RemovePlayerDisguise(client);
	TF2_RegeneratePlayer(client);
	StripToPDA(client);
	int userid = GetClientUserId(client);
	if (playerCrabData[client][IsFirstClient])
	{
		TeleportEntity(client, firstClientOrigin, NULL_VECTOR, NULL_VECTOR);
	}
	else
	{
		TeleportEntity(client, secondClientOrigin, NULL_VECTOR, NULL_VECTOR);
	}
	playerCrabData[client][MovementTimer] = new SMTimer(0.5, testSpycrabClientMov, userid, TIMER_REPEAT);
	playerCrabData[client][Team] = TF2_GetClientTeam(client);
	if (CommandExists("sm_tauntspeed"))
	{
		ServerCommand("sm_tauntspeed #%d 1.3", userid);
	}
}

public void StripToPDA(int client)
{
	if (CommandExists("sm_spycrabpda"))
	{
		TF2_RemoveAllWeapons(client);
		ServerCommand("sm_spycrabpda #%d", GetClientUserId(client));
	}
	else
	{
		// Taken from Shaders Allen's TF2_StripToMelee

		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Building);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Melee);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Item1);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Item2);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_PDA);

		int disguisekit = GetPlayerWeaponSlot(client, TFWeaponSlot_Grenade);

		if (IsValidEntity(disguisekit))
		{
			EquipPlayerWeapon(client, disguisekit);
		}
	}
}

void ResetSpycrabClients(bool endOfCrab = false, int client = -1)
{
	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			ShowHudText(i, hudTextChannel, "");
		}
		if (client == -1 && playerCrabData[i][IsEnabled])
		{
			client = i;
		}
	}
	ResetClient(client, endOfCrab);
	ResetClient(playerCrabData[client][Challenger], endOfCrab);
	spycrabInProgress = false;
}

void ResetClient(int client, bool endOfCrab)
{
	if (endOfCrab)
	{
		if (IsValidClient(client))
		{
			TF2_RespawnPlayer(client);
			if (CommandExists("sm_tauntspeed"))
			{
				ServerCommand("sm_tauntspeed #%d 1", GetClientUserId(client));
			}
		}
	}
	delete playerCrabData[client][MovementTimer];
	playerCrabData[client][TimesCrabbed] = 0;
	playerCrabData[client][TimesTaunted] = 0;
	playerCrabData[client][IsEnabled] = false;
}

public Action testSpycrabClientMov(Handle timer, any userid)
{
	int client = GetClientOfUserId(userid);
	float location[3], noMovement[3] = {0.0, 0.0, 0.0};
	GetClientAbsOrigin(client, location);
	if (playerCrabData[client][IsFirstClient])
	{
		if (GetVectorDistance(location, firstClientOrigin) > 100.0)
		{
			TeleportEntity(client, firstClientOrigin, NULL_VECTOR, noMovement);
			CPrintToChat(client, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not move!");
		}
	}
	else
	{
		GetClientAbsOrigin(client, location);
		if (GetVectorDistance(location, secondClientOrigin) > 100.0)
		{
			TeleportEntity(client, secondClientOrigin, NULL_VECTOR, noMovement);
			CPrintToChat(client, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not move!");
		}
	}
}

public void OnEntityCreated(int entity, const char[] classname)
{
    if (spycrabInProgress && StrEqual(classname, "instanced_scripted_scene", false))
    {
    	SDKHook(entity, SDKHook_Spawn, OnSceneSpawned);
    }
}

public Action OnSceneSpawned(int entity)
{
	if (!spycrabInProgress) return;
	int client = GetEntPropEnt(entity, Prop_Data, "m_hOwner");
	if (!IsValidClient(client) || !playerCrabData[client][IsEnabled]) return;
	// SetHudTextParams(0.1, 0.5, 22.0, 102, 51, 153, 255);
	SetHudTextParams(0.1, 0.5, 22.0, 0, 255, 0, 255);
	char scenefile[128];
	GetEntPropString(entity, Prop_Data, "m_iszSceneFile", scenefile, sizeof(scenefile));
	if (StrContains(scenefile, "scenes/player/spy/low/taunt04", false) == -1 && StrContains(scenefile, "scenes/player/spy/low/taunt05", false) == -1 && StrContains(scenefile, "scenes/player/spy/low/taunt06", false) == -1) return;
	playerCrabData[client][TimesTaunted]++;
	int challenger = playerCrabData[client][Challenger];
	int absoluteTimesTaunted = abs(playerCrabData[client][TimesTaunted] - playerCrabData[challenger][TimesTaunted]);
	if (absoluteTimesTaunted > 1)
	{
		if (playerCrabData[client][TimesTaunted] > playerCrabData[challenger][TimesTaunted])
		{
			CPrintToChat(challenger, "{LIGHTGREEN}[Crab]{DEFAULT} You must taunt %d more times for any more points to be counted.", absoluteTimesTaunted);
			CPrintToChat(client, "{LIGHTGREEN}[Crab]{DEFAULT} %N must taunt %d more times for any more points to be counted.", challenger, absoluteTimesTaunted);
			playerCrabData[client][TimesTaunted]--;
			return;
		}
	}
	if (StrEqual(scenefile, "scenes/player/spy/low/taunt05.vcd"))
	{
		playerCrabData[client][TimesCrabbed]++;
		CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N just crabbed!", client);
		if (absoluteTimesTaunted == 0 && (playerCrabData[client][TimesCrabbed] > 2 || playerCrabData[challenger][TimesCrabbed] > 2))
		{
			int printClient = (playerCrabData[client][TimesCrabbed] > playerCrabData[challenger][TimesCrabbed]) ? challenger : client;
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} We have a winner! Congrats to %N.", printClient);
			LogMessage("%N has won the spycrab between them and %N.", printClient, playerCrabData[printClient][Challenger])
			SMTimer.Make(3.5, ResetCrabOnWinTimer);
			return;
		}
	}
	else
	{
		if (playerCrabData[client][TimesCrabbed] > 2 || playerCrabData[challenger][TimesCrabbed] > 2)
		{
			int printClient = (playerCrabData[client][TimesCrabbed] > playerCrabData[challenger][TimesCrabbed]) ? challenger : client;
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} We have a winner! Congrats to %N.", printClient);
			LogMessage("%N has won the spycrab between them and %N.", printClient, playerCrabData[printClient][Challenger])
			SMTimer.Make(3.5, ResetCrabOnWinTimer);
			return;
		}
	}
	int firstClient = playerCrabData[client][IsFirstClient] ? client : challenger;
	int secondClient = playerCrabData[firstClient][Challenger];
	for (int i = 1; i <= MaxClients; i++)
		if (IsValidClient(i))
			ShowHudText(i, hudTextChannel, "%N: %d / %d\n%N: %d / %d", firstClient,
				playerCrabData[firstClient][TimesCrabbed], playerCrabData[firstClient][TimesTaunted], secondClient,
				playerCrabData[secondClient][TimesCrabbed], playerCrabData[secondClient][TimesTaunted]);
}

public Action ResetCrabOnWinTimer(Handle timer)
{
	ResetSpycrabClients(true);
}

///////////////////////////////////
///////////// CHECKS //////////////
///////////////////////////////////

public Action OnPlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
{
	int userid = event.GetInt("userid");
	int client = GetClientOfUserId(userid);
	if (playerCrabData[client][IsEnabled])
	{
		if (spycrabInProgress)
		{
			LogMessage("%L may have just run from a spycrab!", client);
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %L may have just run from a spycrab!", client);
			ResetSpycrabClients(true, client);
		}
	}
	return Plugin_Continue;
}

public void OnPlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
	if (!spycrabInProgress) return;
	int userid = event.GetInt("userid");
	int client = GetClientOfUserId(userid);
	if (IsValidClient(client) && playerCrabData[client][IsEnabled])
	{
		SMTimer.Make(0.2, OnPlayerDeathTimer, userid);
	}
}

public Action OnPlayerDeathTimer(Handle timer, any userid)
{
	float noMovement[3] = {0.0, 0.0, 0.0};
	int client = GetClientOfUserId(userid);
	if (!IsValidClient(client) && spycrabInProgress)
	{
		ResetSpycrabClients(false);
		return;
	}
	if (!playerCrabData[client][IsEnabled])
	{
		return;
	}
	if (TF2_GetClientTeam(client) != playerCrabData[client][Team])
	{
		TF2_ChangeClientTeam(client, playerCrabData[client][Team]);
	}
	TF2_SetPlayerClass(client, TFClass_Spy, false);
	TF2_RespawnPlayer(client);
	StripToPDA(client);
	if (playerCrabData[client][IsFirstClient]) TeleportEntity(client, firstClientOrigin, NULL_VECTOR, noMovement);
	else TeleportEntity(client, secondClientOrigin, NULL_VECTOR, noMovement);
}

stock int abs(int x) { return RoundToNearest(FloatAbs(float(x))); }

/**
* TheXeon
* ngs_spycrab.sp
*
* Files:
* addons/sourcemod/plugins/ngs_spycrab.smx
* addons/sourcemod/configs/crablocations.cfg
*
* Dependencies:
* sdkhooks.inc, adminmenu.inc, multicolors.inc, tf2_stocks.inc,
* ngsutils.inc, ngsupdater.inc
*/
#include <sdkhooks>
#include <adminmenu>
#include <multicolors>
#include <tf2_stocks>
#include <ngsutils>
#include <ngsupdater>

//// Obviously inspired by Erreur 500
//enum CrabData
//{
//	Challenger,
//	bool:Enabled,
//	Score,
//	SMTimer:MovementTimer,
//	TimesTaunted,
//	Deads,
//	ClassRestrict,
//	GodMod,
//	bool:HeadShot,
//	TimeLeft,
//	CSprite,
//	SpriteParent,
//	bool:HideSprite,
//}

ConVar mapNameContains;
KeyValues mapLocations;

bool spycrabInProgress, spycrabMenuInUse;
//int playerCrabData[MAXPLAYERS + 1][CrabData];
int firstClient = -1, secondClient = -1, firstClientScore, secondClientScore,
	firstClientTimesTaunted, secondClientTimesTaunted, hudTextChannel;
float firstClientOrigin[3], secondClientOrigin[3];
TFTeam firstClientTeam, secondClientTeam;

SMTimer firstClientMovement, secondClientMovement;

public Plugin myinfo = {
	name = "[NGS] Spycrab Suite",
	author = "TheXeon / EasyE",
	description = "Automate a spycrab with people.",
	version = "1.1.0",
	url = "https://www.neogenesisnetwork.net"
}

public void OnPluginStart()
{
	RegConsoleCmd("sm_spycrab", CommandCrab, "Displays spycrab target menu.");
	RegConsoleCmd("sm_crab", CommandCrab, "Displays spycrab target menu.");
	RegAdminCmd("sm_cancelcrab", CommandCancelCrab, ADMFLAG_GENERIC, "Cancels the spycrab.");
	RegAdminCmd("sm_reloadcrab", CommandReloadCrabConfig, ADMFLAG_GENERIC, "Reloads the map config file.");

	mapNameContains = CreateConVar("sm_spycrab_config_contains", "1", "Whether map names in config will be checked partially or fully.");
	HookEvent("player_death", OnPlayerDeath);
	HookEvent("player_disconnect", OnPlayerDisconnect, EventHookMode_Pre);

	LoadConfig();
}

// Thank you Dr.Mckay and your CCC
public void LoadConfig()
{
	char configFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, configFile, sizeof(configFile), "configs/crablocations.cfg");
	if (!FileExists(configFile))
	{
		SetFailState("Missing config file (should be at %s)! Please get it from the repo!", configFile);
	}
	delete mapLocations;
	mapLocations = new KeyValues("Locations");
	if (!mapLocations.ImportFromFile(configFile))
	{
		SetFailState("Invalid config file at %s! Please fix it!", configFile);
	}
}

public void OnMapStart()
{
	char mapName[MAX_BUFFER_LENGTH];
	GetCurrentMap(mapName, sizeof(mapName));
	FindMapCoords(mapName);
}

void FindMapCoords(char[] mapName)
{
	firstClientOrigin = NULL_VECTOR;
	secondClientOrigin = NULL_VECTOR;
	char buffer[MAX_BUFFER_LENGTH];
	mapLocations.Rewind();
	if (!mapLocations.JumpToKey(mapName))
	{
		if (mapNameContains.BoolValue)
		{
			mapLocations.Rewind();
			mapLocations.GotoFirstSubKey();
			do
			{
				mapLocations.GetSectionName(buffer, sizeof(buffer));
				if (StrContains(mapName, buffer, false) != -1)
				{
					SetMapCoords(buffer);
					break;
				}
			}
			while (mapLocations.GotoNextKey());
		}
		else
		{
			LogError("Map %s is not in config file!", mapName);
		}
	}
	else
	{
		SetMapCoords(mapName);
	}
}

void SetMapCoords(char[] sectionName)
{
	char firstClientBuffer[MAX_BUFFER_LENGTH], secondClientBuffer[MAX_BUFFER_LENGTH],
		firstClientVector[3][MAX_BUFFER_LENGTH], secondClientVector[3][MAX_BUFFER_LENGTH];
	mapLocations.GetString("firstClient", firstClientBuffer, sizeof(firstClientBuffer));
	mapLocations.GetString("secondClient", secondClientBuffer, sizeof(secondClientBuffer));
	if (firstClientBuffer[0] != '\0' && secondClientBuffer[0] != '\0')
	{
		ExplodeString(firstClientBuffer, ",", firstClientVector, sizeof(firstClientVector), sizeof(firstClientVector[]));
		ExplodeString(secondClientBuffer, ",", secondClientVector, sizeof(secondClientVector), sizeof(secondClientVector[]));
		for (int i = 0; i < 3; i++)
		{
			firstClientOrigin[i] = StringToFloat(firstClientVector[i]);
			secondClientOrigin[i] = StringToFloat(secondClientVector[i]);\
		}
	}
	else
	{
		LogError("Invalidly formatted location string in config file around Section \'%s\'. Make sure it is a comma separated 3d vector!", sectionName);
	}
}

public Action CommandReloadCrabConfig(int client, int args)
{
	if (mapLocations == null)
	{
		CReplyToCommand(client, "The config file does not currently exist!");
	}
	else
	{
		char mapName[MAX_BUFFER_LENGTH];
		GetCurrentMap(mapName, sizeof(mapName));
		LoadConfig();
		FindMapCoords(mapName);
		CReplyToCommand(client, "Locations have been reloaded!");
	}
	return Plugin_Handled;
}

public Action CommandCrab(int client, int args)
{
	if (!IsValidClient(client, true)) return Plugin_Handled;
	if (spycrabInProgress)
	{
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} There is currently another spycrab happening.");
	}
	else if (spycrabMenuInUse)
	{
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} The spycrab menu is currently being used by someone else right now.");
	}
	else if (firstClientOrigin[0] == NULL_VECTOR[0])
	{
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} The map is not configured for this plugin, notify an admin!");
	}
	else
	{
		Menu spycrabMenu = new Menu(SpycrabMenuHandler);
		spycrabMenu.SetTitle("Select a player:");
		AddTargetsToMenu(spycrabMenu, 0, true, true);
		spycrabMenu.Display(client, 20);
		spycrabMenuInUse = true;
	}
	return Plugin_Handled;
}

public Action CommandCancelCrab(int client, int args)
{
	if (!spycrabInProgress)
		CReplyToCommand(client, "{LIGHTGREEN}[Crab]{DEFAULT} There isn\'t any spycrab going on right now.");
	else
		ResetSpycrabClients(true);
	return Plugin_Handled;
}

public int SpycrabMenuHandler(Menu menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select)
	{
		char target[16];
		int iTarget;
		menu.GetItem(param2, target, sizeof(target));
		iTarget = GetClientOfUserId(StringToInt(target));
		if (param1 == iTarget)
		{
			CPrintToChat(param1, "{LIGHTGREEN}[Crab]{DEFAULT} You may not target yourself!");
			spycrabMenuInUse = false;
			return;
		}
		CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N has challenged %N to a spycrab showdown!", param1, iTarget);
		Menu acceptMenu = new Menu(AcceptMenuHandler);
		acceptMenu.SetTitle("Do you accept?");
		acceptMenu.AddItem("yes", "Yes!");
		acceptMenu.AddItem("no", "No!");
		acceptMenu.Display(iTarget, 20);
		firstClient = param1;
	}
	if (action == MenuAction_End)
	{
		delete menu;
	}
	if (action == MenuAction_Cancel)
	{
		spycrabMenuInUse = false;
	}
}

public int AcceptMenuHandler(Menu menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_Select)
	{
		char answer[8];
		if (menu.GetItem(param2, answer, sizeof(answer)))
		{
			if (StrEqual(answer, "yes", false))
			{
				CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N accepted %N\'s spycrab!", param1, firstClient);
				secondClient = param1;
				LogAction(secondClient, firstClient, "%N has accepted %N\'s spycrab.", secondClient, firstClient);
				StartSpyCrab();
			}
			else
			{
				CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N declined %N\'s spycrab!", param1, firstClient);
				ResetSpycrabClients();
			}
		}
		spycrabMenuInUse = false;
	}
	if (action == MenuAction_Cancel)
	{
		ResetSpycrabClients();
		spycrabMenuInUse = false;
	}
	if (action == MenuAction_End)
	{
		delete menu;
		spycrabMenuInUse = false;
	}
}

public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])
{
	if ((client == firstClient || client == secondClient) && impulse >= 221 && impulse <=239 && impulse != 230)
	{
		CPrintToChat(client, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not attempt to disguise!");
		return Plugin_Handled;
	}
	return Plugin_Continue;
}

public void StartSpyCrab()
{
	spycrabInProgress = true;
	// SetHudTextParams(0.1, 0.5, 22.0, 102, 51, 153, 255);
	SetHudTextParams(0.1, 0.5, 22.0, 0, 255, 0, 255);
	hudTextChannel = ShowHudText(firstClient, -1, "Starting spycrab...");
	// Have to double each command, bleh
	TF2_SetPlayerClass(firstClient, TFClass_Spy);
	TF2_SetPlayerClass(secondClient, TFClass_Spy);
	TF2_RemovePlayerDisguise(firstClient);
	TF2_RemovePlayerDisguise(secondClient);
	TF2_RegeneratePlayer(firstClient);
	TF2_RegeneratePlayer(secondClient);
	StripToPDA(firstClient);
	StripToPDA(secondClient);
	TeleportEntity(firstClient, firstClientOrigin, NULL_VECTOR, NULL_VECTOR);
	TeleportEntity(secondClient, secondClientOrigin, NULL_VECTOR, NULL_VECTOR);
	firstClientMovement = new SMTimer(0.5, testFirstClientMov, _, TIMER_REPEAT);
	secondClientMovement = new SMTimer(0.5, testSecondClientMov, _, TIMER_REPEAT);
	firstClientTeam = TF2_GetClientTeam(firstClient);
	secondClientTeam = TF2_GetClientTeam(secondClient);
	if (CommandExists("sm_tauntspeed"))
	{
		ServerCommand("sm_tauntspeed #%d 1.3", GetClientUserId(firstClient));
		ServerCommand("sm_tauntspeed #%d 1.3", GetClientUserId(secondClient));
	}
}

public void StripToPDA(int client)
{
	if (CommandExists("sm_spycrabpda"))
	{
		TF2_RemoveAllWeapons(client);
		ServerCommand("sm_spycrabpda #%d", GetClientUserId(client));
	}
	else
	{
		// Taken from Shaders Allen's TF2_StripToMelee

		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Building);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Melee);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Item1);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_Item2);
		TF2_RemoveWeaponSlot(client, TFWeaponSlot_PDA);

		int disguisekit = GetPlayerWeaponSlot(client, TFWeaponSlot_Grenade);

		if (IsValidEntity(disguisekit))
		{
			EquipPlayerWeapon(client, disguisekit);
		}
	}
}

void ResetSpycrabClients(bool endOfCrab = false)
{
	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsValidClient(i))
		{
			ShowHudText(i, hudTextChannel, "");
		}
	}
	if (endOfCrab)
	{
		if (IsValidClient(firstClient))
		{
			TF2_RespawnPlayer(firstClient);
			if (CommandExists("sm_tauntspeed"))
			{
				ServerCommand("sm_tauntspeed #%d 1", GetClientUserId(firstClient));
			}
		}
		if (IsValidClient(secondClient))
		{
			TF2_RespawnPlayer(secondClient);
			if (CommandExists("sm_tauntspeed"))
			{
				ServerCommand("sm_tauntspeed #%d 1", GetClientUserId(secondClient));
			}
		}
	}
	firstClient = -1;
	secondClient = -1;
	delete firstClientMovement;
	delete secondClientMovement;
	firstClientScore = 0;
	secondClientScore = 0;
	firstClientTimesTaunted = 0;
	secondClientTimesTaunted = 0;
	spycrabInProgress = false;
}

public Action testFirstClientMov(Handle timer)
{
	float location[3], noMovement[3] = {0.0, 0.0, 0.0};
	GetClientAbsOrigin(firstClient, location);
	if (GetVectorDistance(location, firstClientOrigin) > 100.0)
	{
		TeleportEntity(firstClient, firstClientOrigin, NULL_VECTOR, noMovement);
		CPrintToChat(firstClient, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not move!");
	}
}

public Action testSecondClientMov(Handle timer)
{
	float location[3], noMovement[3] = {0.0, 0.0, 0.0};
	GetClientAbsOrigin(secondClient, location);
	if (GetVectorDistance(location, secondClientOrigin) > 100.0)
	{
		TeleportEntity(secondClient, secondClientOrigin, NULL_VECTOR, noMovement);
		CPrintToChat(secondClient, "{LIGHTGREEN}[Crab]{DEFAULT} Please do not move!");
	}
}

public void OnEntityCreated(int entity, const char[] classname)
{
    if (!StrEqual(classname, "instanced_scripted_scene", false)) return;
    SDKHook(entity, SDKHook_Spawn, OnSceneSpawned);
}

public Action OnSceneSpawned(int entity)
{
	if (!spycrabInProgress) return;
	int client = GetEntPropEnt(entity, Prop_Data, "m_hOwner");
	if (client != firstClient && client != secondClient) return;
	// SetHudTextParams(0.1, 0.5, 22.0, 102, 51, 153, 255);
	SetHudTextParams(0.1, 0.5, 22.0, 0, 255, 0, 255);
	char scenefile[128];
	GetEntPropString(entity, Prop_Data, "m_iszSceneFile", scenefile, sizeof(scenefile));
	if (StrContains(scenefile, "scenes/player/spy/low/taunt04", false) == -1 && StrContains(scenefile, "scenes/player/spy/low/taunt05", false) == -1 && StrContains(scenefile, "scenes/player/spy/low/taunt06", false) == -1) return;
	if (client == firstClient) firstClientTimesTaunted++;
	else if (client == secondClient) secondClientTimesTaunted++;
	int absoluteTimesTaunted = abs(secondClientTimesTaunted - firstClientTimesTaunted);
	if (absoluteTimesTaunted > 1)
	{
		if (secondClientTimesTaunted > firstClientTimesTaunted)
		{
			CPrintToChat(firstClient, "{LIGHTGREEN}[Crab]{DEFAULT} You must taunt %d more times for any more points to be counted.", absoluteTimesTaunted);
			CPrintToChat(secondClient, "{LIGHTGREEN}[Crab]{DEFAULT} %N must taunt %d more times for any more points to be counted.", firstClient, absoluteTimesTaunted);
			secondClientTimesTaunted--;
			return;
		}
		else
		{
			CPrintToChat(secondClient, "{LIGHTGREEN}[Crab]{DEFAULT} You must taunt %d more times for any more points to be counted.", absoluteTimesTaunted);
			CPrintToChat(firstClient, "{LIGHTGREEN}[Crab]{DEFAULT} %N must taunt %d more times for any more points to be counted.", secondClient, absoluteTimesTaunted);
			firstClientTimesTaunted--;
			return;
		}
	}
	if (StrEqual(scenefile, "scenes/player/spy/low/taunt05.vcd"))
	{
		if (client == firstClient) firstClientScore++;
		else if (client == secondClient) secondClientScore++;
		CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %N just crabbed!", client);
		if (absoluteTimesTaunted == 0 && (firstClientScore > 2 || secondClientScore > 2))
		{
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} We have a winner! Congrats to %N.", (firstClientScore > secondClientScore) ? secondClient : firstClient);
			LogMessage("%N has won the spycrab between them and %N.", (firstClientScore > secondClientScore) ? secondClient : firstClient, (firstClientScore > secondClientScore) ? firstClient : secondClient)
			SMTimer.Make(3.5, ResetCrabOnWinTimer);
			return;
		}
		for (int i = 1; i <= MaxClients; i++)
			if (IsValidClient(i)) ShowHudText(i, hudTextChannel, "%N: %d / %d\n%N: %d / %d", firstClient, firstClientScore, firstClientTimesTaunted, secondClient, secondClientScore, secondClientTimesTaunted);
	}
	else
	{
		for (int i = 1; i <= MaxClients; i++)
			if (IsValidClient(i)) ShowHudText(i, hudTextChannel, "%N: %d / %d\n%N: %d / %d", firstClient, firstClientScore, firstClientTimesTaunted, secondClient, secondClientScore, secondClientTimesTaunted);
		if (firstClientScore > 2 || secondClientScore > 2)
		{
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} We have a winner! Congrats to %N.", (firstClientScore > secondClientScore) ? secondClient : firstClient);
			LogMessage("%N has won the spycrab between them and %N.", (firstClientScore > secondClientScore) ? secondClient : firstClient, (firstClientScore > secondClientScore) ? firstClient : secondClient)
			SMTimer.Make(3.5, ResetCrabOnWinTimer);
		}
	}
}

public Action ResetCrabOnWinTimer(Handle timer)
{
	ResetSpycrabClients(true);
}

///////////////////////////////////
///////////// CHECKS //////////////
///////////////////////////////////

public Action OnPlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
{
	int userid = event.GetInt("userid");
	int client = GetClientOfUserId(userid);
	if (client == firstClient || client == secondClient)
	{
		if (spycrabMenuInUse) spycrabMenuInUse = false;
		if (spycrabInProgress)
		{
			LogMessage("%L may have just run from a spycrab!", IsValidClient(firstClient) ? secondClient : firstClient);
			CPrintToChatAll("{LIGHTGREEN}[Crab]{DEFAULT} %L just ran from a spycrab!", IsValidClient(firstClient) ? secondClient : firstClient);
			ResetSpycrabClients(true);
		}
	}
	return Plugin_Continue;
}

public void OnPlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
	if (!spycrabInProgress) return;
	int userid = event.GetInt("userid");
	int client = GetClientOfUserId(userid);
	if (client == firstClient || client == secondClient)
	{
		SMTimer.Make(0.2, OnPlayerDeathTimer, userid);
	}
}

public Action OnPlayerDeathTimer(Handle timer, any userid)
{
	float noMovement[3] = {0.0, 0.0, 0.0};
	int client = GetClientOfUserId(userid);
	if (!IsValidClient(client))
	{
		ResetSpycrabClients(false);
		return;
	}
	if (client == firstClient && TF2_GetClientTeam(client) != firstClientTeam)
	{
		TF2_ChangeClientTeam(client, firstClientTeam);
	}
	else if (client == secondClient && TF2_GetClientTeam(client) != secondClientTeam)
	{
		TF2_ChangeClientTeam(client, secondClientTeam);
	}
	TF2_SetPlayerClass(client, TFClass_Spy, false);
	TF2_RespawnPlayer(client);
	StripToPDA(client);
	if (client == firstClient) TeleportEntity(firstClient, firstClientOrigin, NULL_VECTOR, noMovement);
	else TeleportEntity(secondClient, secondClientOrigin, NULL_VECTOR, noMovement);
}

stock int abs(int x) { return RoundToNearest(FloatAbs(float(x))); }


Set default volume to 4

Set default volume to 4

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio_base
 * Handles http calls to the In Game Audio website to query and play songs to
 * users through the MOTD popup panel.
 *
 * Copyright 2013 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <in_game_audio>
#include <clientprefs>
#include <steamtools>
#include <smjansson>

#define PLUGIN_VERSION "1.2"

public Plugin:myinfo =
{
    name = "In Game Audio Base",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1] = {false, ...};
new bool:g_IsPallEnabled[MAXPLAYERS+1] = {false, ...};
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1] = {0, ...};
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1] = {4, ...};

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

    RegPluginLibrary("in_game_audio");

    CreateNative("AuthorizeUser", Native_AuthorizeUser);
    CreateNative("ClientHasPallEnabled", Native_ClientHasPallEnabled);
    CreateNative("SetPallEnabled", Native_SetPallEnabled);
    CreateNative("IsInP", Native_IsInP);
    CreateNative("IsInPall", Native_IsInPall);
    CreateNative("PlaySong", Native_PlaySong);
    CreateNative("PlaySongAll", Native_PlaySongAll);
    CreateNative("StopSong", Native_StopSong);
    CreateNative("StopSongAll", Native_StopSongAll);
    CreateNative("SongList", Native_SongList);
    CreateNative("QuerySong", Native_QuerySong);
    CreateNative("MapTheme", Native_MapTheme);
    CreateNative("UserTheme", Native_UserTheme);
    CreateNative("StartCoolDown", Native_StartCoolDown);
    CreateNative("IsClientInCooldown", Native_IsClientInCooldown);
    CreateNative("IsIGAEnabled", Native_IsIGAEnabled);

    return APLRes_Success;
}

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

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

}

public OnClientConnected(client)
{
    if(IsFakeClient(client))
    {
        return;
    }
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 4;
    g_IsPallEnabled[client] = true;

    //Disable pall by default for quickplayers
    new String:connect_method[5];
    GetClientInfo(client, "cl_connectmethod", connect_method, sizeof(connect_method));
    if( strncmp("quick", connect_method, 5, false) == 0 ||
        strncmp("match", connect_method, 5, false) == 0)
    {
        g_IsPallEnabled[client] = false;
    }

}

public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_Volume[client] = StringToInt(buffer);
    }

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_IsPallEnabled[client] = bool:StringToInt(buffer);
    }
}

public OnMapStart()
{
    g_PallNextFree = 0;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "\x04%t", "volume_usage", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "\x04%t", "volume_set", volume);
        }else{
            ReplyToCommand(client, "\x04%t", "volume_usage", g_Volume[client]);
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "\x04%t", "disabled_pall");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "\x04%t", "enabled_pall");
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if(InternalIsClientInCooldown(client))
    {
        ReplyToCommand(client, "\x04%t", "user_in_cooldown");
        return Plugin_Handled;
    }

    if (client && IsClientAuthorized(client))
    {
        InternalAuthorizeUser(client);
    }
    return Plugin_Handled;
}

Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public Native_StartCoolDown(Handle:plugin, args) { InternalStartCooldown(GetNativeCell(1)); }
InternalStartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public Native_IsIGAEnabled(Handle:plugin, args) { return _:InternalIsIGAEnabled(); }
bool:InternalIsIGAEnabled()
{
    return GetConVarBool(g_Cvar_IGAEnabled);
}
public Native_ClientHasPallEnabled(Handle:plugin, args) { return _:InternalClientHasPallEnabled(GetNativeCell(1)); }
bool:InternalClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public Native_SetPallEnabled(Handle:plugin, args) { InternalSetPallEnabled(GetNativeCell(1), GetNativeCell(2)); }
InternalSetPallEnabled(client, bool:val)
{
    g_IsPallEnabled[client] = val;
}

public Native_IsClientInCooldown(Handle:plugin, args) { return _:InternalIsClientInCooldown(GetNativeCell(1)); }
bool:InternalIsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public Native_IsInPall(Handle:plugin, args) { return _:InternalIsInPall(); }
bool:InternalIsInPall()
{
    return GetTime() < g_PallNextFree;
}

public Native_IsInP(Handle:plugin, args) { return _:InternalIsInP(GetNativeCell(1)); }
bool:InternalIsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

public Native_QuerySong(Handle:plugin, args) {
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new len;
    GetNativeStringLength(2, len);
    new String:path[len+1];
    GetNativeString(2, path, len+1);

    InternalQuerySong(GetNativeCell(1), path, GetNativeCell(3), GetNativeCell(4));
}
InternalQuerySong(client, String:path[], bool:pall, bool:force)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "\x04%t", "url_invalid");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    //Send caller's steamid64
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    InternalStartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64], String:access_token[128];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));
        json_object_get_string(json, "access_token", access_token, sizeof(access_token));

        if(pall)
        {
            if(!InternalIsInPall())
            {
                g_PNextFree[client]=0;
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("%t", "started_playing_to_all", description);
                PrintToChatAll("%t", "duration", duration_formated);
                PrintToChatAll("%t", "to_stop_all");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                InternalPlaySongAll(song_id, access_token, force);
            }else{
                new minutes = (g_PallNextFree - GetTime()) / 60;
                new seconds = (g_PallNextFree - GetTime());

                if (minutes > 1)
                    PrintToChat(client, "\x04%t", "pall_currently_playing", g_CurrentPallPath, g_CurrentPallDescription, minutes, "minutes");
                else
                    PrintToChat(client, "\x04%t", "pall_currently_playing", g_CurrentPallPath, g_CurrentPallDescription, seconds, "seconds");
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            PrintToChatAll("%t", "started_playing_to_self", name, description, full_path);
            PrintToChat(client, "%t", "duration", duration_formated);
            PrintToChat(client, "%t", "to_stop");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            InternalPlaySong(client, song_id, access_token);
        }
    }else{
        PrintToChat(client, "%t", "not_found");
    }

    CloseHandle(json);
}

public Native_UserTheme(Handle:plugin, args) { InternalUserTheme(GetNativeCell(1)); }
InternalUserTheme(client)
{
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "%t", "url_invalid");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public Native_MapTheme(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:map[len+1];
    GetNativeString(2, map, len+1);

    InternalMapTheme(GetNativeCell(1), map);
}
InternalMapTheme(bool:force=true, String:map[] ="")
{
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new HTTPRequestHandle:request = CreateIGARequest(MAP_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "%t", "url_invalid");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "map", map);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:access_token[128];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "access_token", access_token, sizeof(access_token));

        if(force || !InternalIsInPall())
        {
            g_PallNextFree = 0;
            InternalPlaySongAll(song_id, access_token, force);
        }
    }

    CloseHandle(json);
}

public Native_AuthorizeUser(Handle:plugin, args) { InternalAuthorizeUser(GetNativeCell(1)); }
InternalAuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "\x04%t", "url_invalid");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    InternalStartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "%t", "now_authorized_to_upload");
    }
}

public Native_PlaySongAll(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(1, len);
    new String:song[len+1];
    GetNativeString(1, song, len+1);

    GetNativeStringLength(2, len);
    new String:access_token[len+1];
    GetNativeString(2, access_token, len+1);

    InternalPlaySongAll(song, access_token, GetNativeCell(3));
}
InternalPlaySongAll(String:song[], String:access_token[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        //Ignore players who can't hear this
        if(!IsClientInGame(client) || IsFakeClient(client) || g_Volume[client] < 1)
            continue;

        if ( InternalClientHasPallEnabled(client) )
        {
            if(force || !InternalIsInP(client))
            {
                InternalPlaySong(client, song, access_token);
            }

        }else{
            //Mention that pall is not enabled
            PrintToChat(client, "\x04%t", "pall_not_enabled");
        }
    }
}

public Native_PlaySong(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:song[len+1];
    GetNativeString(2, song, len+1);

    GetNativeStringLength(3, len);
    new String:access_token[len+1];
    GetNativeString(3, access_token, len+1);

    InternalPlaySong(GetNativeCell(1), song, access_token);
}
InternalPlaySong(client, String:song_id[], String:access_token[])
{
    //Don't play song if client is a bot or has a muted volume
    if(!IsClientInGame(client) || IsFakeClient(client) || g_Volume[client] < 1)
    {
        return;
    }
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //TODO make a pop-under motd method
    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, access_token, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
}

public Native_StopSong(Handle:plugin, args) { InternalStopSong(GetNativeCell(1)); }
InternalStopSong(client)
{
    if(!IsClientInGame(client) || IsFakeClient(client))
    {
        return;
    }
    g_PNextFree[client] = 0;
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, STOP_ROUTE);

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
}

public Native_StopSongAll(Handle:plugin, args) { InternalStopSongAll(); }
InternalStopSongAll()
{
    g_PallNextFree = 0;
    for (new client=1; client <= MaxClients; client++)
    {
        if ( !InternalIsInP(client) )
        {
            InternalStopSong(client);
        }
    }
}

public Native_SongList(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:search[len+1];
    GetNativeString(2, search, len+1);

    InternalSongList(GetNativeCell(1), search);
}
public InternalSongList(client, String:search[])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    //Use a song search if given a search key
    if(strlen(search) > 0)
    {
        Format(url, sizeof(url),
                "%s%s?search=%s", base_url, SONGS_ROUTE, search);
    }else{
        Format(url, sizeof(url),
                "%s%s", base_url, DIRECTORIES_ROUTE);
    }

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);
    KvSetNum(panel, "customsvr", 1); //Sets motd to be fullscreen

    ShowVGUIPanel(client, "info", panel, true);
    CloseHandle(panel);

}

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio_base
 * Handles http calls to the In Game Audio website to query and play songs to
 * users through the MOTD popup panel.
 *
 * Copyright 2013 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <in_game_audio>
#include <clientprefs>
#include <steamtools>
#include <smjansson>

#define PLUGIN_VERSION "1.2"

public Plugin:myinfo =
{
    name = "In Game Audio Base",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1] = {false, ...};
new bool:g_IsPallEnabled[MAXPLAYERS+1] = {false, ...};
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1] = {0, ...};
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1] = {7, ...};

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

    RegPluginLibrary("in_game_audio");

    CreateNative("AuthorizeUser", Native_AuthorizeUser);
    CreateNative("ClientHasPallEnabled", Native_ClientHasPallEnabled);
    CreateNative("SetPallEnabled", Native_SetPallEnabled);
    CreateNative("IsInP", Native_IsInP);
    CreateNative("IsInPall", Native_IsInPall);
    CreateNative("PlaySong", Native_PlaySong);
    CreateNative("PlaySongAll", Native_PlaySongAll);
    CreateNative("StopSong", Native_StopSong);
    CreateNative("StopSongAll", Native_StopSongAll);
    CreateNative("SongList", Native_SongList);
    CreateNative("QuerySong", Native_QuerySong);
    CreateNative("MapTheme", Native_MapTheme);
    CreateNative("UserTheme", Native_UserTheme);
    CreateNative("StartCoolDown", Native_StartCoolDown);
    CreateNative("IsClientInCooldown", Native_IsClientInCooldown);
    CreateNative("IsIGAEnabled", Native_IsIGAEnabled);

    return APLRes_Success;
}

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

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

}

public OnClientConnected(client)
{
    if(IsFakeClient(client))
    {
        return;
    }
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 7;
    g_IsPallEnabled[client] = true;

    //Disable pall by default for quickplayers
    new String:connect_method[5];
    GetClientInfo(client, "cl_connectmethod", connect_method, sizeof(connect_method));
    if( strncmp("quick", connect_method, 5, false) == 0 ||
        strncmp("match", connect_method, 5, false) == 0)
    {
        g_IsPallEnabled[client] = false;
    }

}

public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_Volume[client] = StringToInt(buffer);
    }

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_IsPallEnabled[client] = bool:StringToInt(buffer);
    }
}

public OnMapStart()
{
    g_PallNextFree = 0;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "\x04%t", "volume_usage", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "\x04%t", "volume_set", volume);
        }else{
            ReplyToCommand(client, "\x04%t", "volume_usage", g_Volume[client]);
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "\x04%t", "disabled_pall");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "\x04%t", "enabled_pall");
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if(InternalIsClientInCooldown(client))
    {
        ReplyToCommand(client, "\x04%t", "user_in_cooldown");
        return Plugin_Handled;
    }

    if (client && IsClientAuthorized(client))
    {
        InternalAuthorizeUser(client);
    }
    return Plugin_Handled;
}

Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public Native_StartCoolDown(Handle:plugin, args) { InternalStartCooldown(GetNativeCell(1)); }
InternalStartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public Native_IsIGAEnabled(Handle:plugin, args) { return _:InternalIsIGAEnabled(); }
bool:InternalIsIGAEnabled()
{
    return GetConVarBool(g_Cvar_IGAEnabled);
}
public Native_ClientHasPallEnabled(Handle:plugin, args) { return _:InternalClientHasPallEnabled(GetNativeCell(1)); }
bool:InternalClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public Native_SetPallEnabled(Handle:plugin, args) { InternalSetPallEnabled(GetNativeCell(1), GetNativeCell(2)); }
InternalSetPallEnabled(client, bool:val)
{
    g_IsPallEnabled[client] = val;
}

public Native_IsClientInCooldown(Handle:plugin, args) { return _:InternalIsClientInCooldown(GetNativeCell(1)); }
bool:InternalIsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public Native_IsInPall(Handle:plugin, args) { return _:InternalIsInPall(); }
bool:InternalIsInPall()
{
    return GetTime() < g_PallNextFree;
}

public Native_IsInP(Handle:plugin, args) { return _:InternalIsInP(GetNativeCell(1)); }
bool:InternalIsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

public Native_QuerySong(Handle:plugin, args) {
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new len;
    GetNativeStringLength(2, len);
    new String:path[len+1];
    GetNativeString(2, path, len+1);

    InternalQuerySong(GetNativeCell(1), path, GetNativeCell(3), GetNativeCell(4));
}
InternalQuerySong(client, String:path[], bool:pall, bool:force)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "\x04%t", "url_invalid");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    //Send caller's steamid64
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    InternalStartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64], String:access_token[128];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));
        json_object_get_string(json, "access_token", access_token, sizeof(access_token));

        if(pall)
        {
            if(!InternalIsInPall())
            {
                g_PNextFree[client]=0;
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("%t", "started_playing_to_all", description);
                PrintToChatAll("%t", "duration", duration_formated);
                PrintToChatAll("%t", "to_stop_all");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                InternalPlaySongAll(song_id, access_token, force);
            }else{
                new minutes = (g_PallNextFree - GetTime()) / 60;
                new seconds = (g_PallNextFree - GetTime());

                if (minutes > 1)
                    PrintToChat(client, "\x04%t", "pall_currently_playing", g_CurrentPallPath, g_CurrentPallDescription, minutes, "minutes");
                else
                    PrintToChat(client, "\x04%t", "pall_currently_playing", g_CurrentPallPath, g_CurrentPallDescription, seconds, "seconds");
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            PrintToChatAll("%t", "started_playing_to_self", name, description, full_path);
            PrintToChat(client, "%t", "duration", duration_formated);
            PrintToChat(client, "%t", "to_stop");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            InternalPlaySong(client, song_id, access_token);
        }
    }else{
        PrintToChat(client, "%t", "not_found");
    }

    CloseHandle(json);
}

public Native_UserTheme(Handle:plugin, args) { InternalUserTheme(GetNativeCell(1)); }
InternalUserTheme(client)
{
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "%t", "url_invalid");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public Native_MapTheme(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:map[len+1];
    GetNativeString(2, map, len+1);

    InternalMapTheme(GetNativeCell(1), map);
}
InternalMapTheme(bool:force=true, String:map[] ="")
{
    if (!InternalIsIGAEnabled())
    {
        PrintToConsole(0, "%t", "not_enabled");
    }

    new HTTPRequestHandle:request = CreateIGARequest(MAP_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "%t", "url_invalid");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "map", map);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:access_token[128];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "access_token", access_token, sizeof(access_token));

        if(force || !InternalIsInPall())
        {
            g_PallNextFree = 0;
            InternalPlaySongAll(song_id, access_token, force);
        }
    }

    CloseHandle(json);
}

public Native_AuthorizeUser(Handle:plugin, args) { InternalAuthorizeUser(GetNativeCell(1)); }
InternalAuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "\x04%t", "url_invalid");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    InternalStartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "%t", "now_authorized_to_upload");
    }
}

public Native_PlaySongAll(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(1, len);
    new String:song[len+1];
    GetNativeString(1, song, len+1);

    GetNativeStringLength(2, len);
    new String:access_token[len+1];
    GetNativeString(2, access_token, len+1);

    InternalPlaySongAll(song, access_token, GetNativeCell(3));
}
InternalPlaySongAll(String:song[], String:access_token[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        //Ignore players who can't hear this
        if(!IsClientInGame(client) || IsFakeClient(client) || g_Volume[client] < 1)
            continue;

        if ( InternalClientHasPallEnabled(client) )
        {
            if(force || !InternalIsInP(client))
            {
                InternalPlaySong(client, song, access_token);
            }

        }else{
            //Mention that pall is not enabled
            PrintToChat(client, "\x04%t", "pall_not_enabled");
        }
    }
}

public Native_PlaySong(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:song[len+1];
    GetNativeString(2, song, len+1);

    GetNativeStringLength(3, len);
    new String:access_token[len+1];
    GetNativeString(3, access_token, len+1);

    InternalPlaySong(GetNativeCell(1), song, access_token);
}
InternalPlaySong(client, String:song_id[], String:access_token[])
{
    //Don't play song if client is a bot or has a muted volume
    if(!IsClientInGame(client) || IsFakeClient(client) || g_Volume[client] < 1)
    {
        return;
    }
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //TODO make a pop-under motd method
    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, access_token, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
}

public Native_StopSong(Handle:plugin, args) { InternalStopSong(GetNativeCell(1)); }
InternalStopSong(client)
{
    if(!IsClientInGame(client) || IsFakeClient(client))
    {
        return;
    }
    g_PNextFree[client] = 0;
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, STOP_ROUTE);

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
}

public Native_StopSongAll(Handle:plugin, args) { InternalStopSongAll(); }
InternalStopSongAll()
{
    g_PallNextFree = 0;
    for (new client=1; client <= MaxClients; client++)
    {
        if ( !InternalIsInP(client) )
        {
            InternalStopSong(client);
        }
    }
}

public Native_SongList(Handle:plugin, args)
{
    new len;
    GetNativeStringLength(2, len);
    new String:search[len+1];
    GetNativeString(2, search, len+1);

    InternalSongList(GetNativeCell(1), search);
}
public InternalSongList(client, String:search[])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    //Format http string
    //TODO store this on cvar change
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    //Use a song search if given a search key
    if(strlen(search) > 0)
    {
        Format(url, sizeof(url),
                "%s%s?search=%s", base_url, SONGS_ROUTE, search);
    }else{
        Format(url, sizeof(url),
                "%s%s", base_url, DIRECTORIES_ROUTE);
    }

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);
    KvSetNum(panel, "customsvr", 1); //Sets motd to be fullscreen

    ShowVGUIPanel(client, "info", panel, true);
    CloseHandle(panel);

}