get fixes

get fixes

enum {
	//GET_SYNC=1 //TODO
};
enum {
	GETMODE_SINGLE=1,
	GETMODE_PACKAGE=2
};

//-------------------------------------------------------------------------------------------------
public Action:Command_remove( args ) {
	if( args == 0 ) {
		PrintToServer( "[ST] Usage: st_remove <target>" );
		return Plugin_Handled;
	}

	decl String:arg[128];
	GetCmdArg( 1, arg ,sizeof arg );
	if( !FormatLocalPath( arg, sizeof arg, arg ) ) {
		PrintToServer( "[ST] st_remove - bad path" );
		return Plugin_Handled;
	}

	if( !FileExists( arg ) ) {
		PrintToServer( "[ST] File doesn't exist." );
		return Plugin_Handled;
	}
	if( !DeleteFile( arg ) ) {
		PrintToServer( "[ST] Couldn't delete file." );
		return Plugin_Handled;
	}

	PrintToServer( "[ST] File deleted." );
	return Plugin_Handled;
}

//-------------------------------------------------------------------------------------------------
public Action:Command_get( args ) {
	if( args == 0 ) {
		PrintToServer( "[ST] Usage: st_get <target> [remote]" );
		//PrintToServer( "[ST]   -s : create sync file" );
		return Plugin_Handled;
	}
	if( g_remote_url[0] == 0 ) {
		PrintToServer( "[ST] st_get - remote is not set up." );
		return Plugin_Handled;
	}

	decl String:target[128];
	target[0] = 0;
	decl String:remote[128];
	remote[0] = 0;
	new flags;
	for( new i = 0; i < args; i++ ) {
		decl String:arg[128];
		GetCmdArg( 1+i, arg, sizeof arg );
		if( arg[0] == '-' ) {
			//if( arg[1] == 's' ) {
		//		flags |= GET_SYNC;
		//	} else {
			PrintToServer( "[ST] st_get - Unknown option: \"%c\"", arg[1] );
			return Plugin_Handled;
		//	}
		} else {
			if( target[0] == 0 ){
				strcopy( target, sizeof target, arg );
			} else if( remote[0] == 0 ) {
				strcopy( remote, sizeof remote, arg );
			} else {
				PrintToServer( "[ST] st_get - bad args" );
				return Plugin_Handled;
			}
		}
	}

	// validate target
	if( target[0] == 0 ) {
		PrintToServer( "[ST] st_get - missing target" );
		return Plugin_Handled;
	}

	if( strncmp( target, "pkg/", 4 ) == 0 ){
		// package is desired
		Format( target, sizeof target, "%s", target[4] );
		if( target[0] == 0 ) return Plugin_Handled;
		DoGetPackage( target, flags );
		return Plugin_Handled;
	}

	if( !FormatLocalPath( target, sizeof target, target ) ) {
		PrintToServer( "[ST] st_get - bad path" );
		return Plugin_Handled;
	}

	if( !TranslateRemotePath( target, remote, sizeof remote ) ) {
		PrintToServer( "[ST] st_get - bad remote path" );
		return Plugin_Handled;
	}

	DoGet( target, remote, flags );
	return Plugin_Handled;
}

//-------------------------------------------------------------------------------------------------
DoGetPackage( const String:package[], flags ) {

	LoadIDConfig();

	new Handle:op = CreateOperation( "Server Get", OnGetStart );
	KvSetNum( op, "user/mode", GETMODE_PACKAGE );
	KvSetString( op, "user/target", package );
	KvSetNum( op, "user/flags", flags );
	StartOperation(op);

}

//-------------------------------------------------------------------------------------------------
DoGet( const String:localfile[], const String:remotefile[], flags ) {

	LoadIDConfig();

	new Handle:op = CreateOperation( "Server Get", OnGetStart );
	KvSetNum( op, "user/mode", GETMODE_SINGLE );
	KvSetString( op, "user/target", localfile );
	KvSetString( op, "user/remote", remotefile );
	KvSetNum( op, "user/flags", flags );

	StartOperation(op);
}

//-------------------------------------------------------------------------------------------------
CleanupGetOp( Handle:op ) {
	CloseHandle( KvGetHandle( op, "user/downloadlist" ) );
}

//-------------------------------------------------------------------------------------------------
public OnGetStart( Handle:op ) {
	new mode = KvGetNum( op, "user/mode" );
	new Handle:downloads = CreateDataPack();
	KvSetHandle( op, "user/downloadlist", downloads );

	if( mode == GETMODE_SINGLE ) {
		decl String:path[128];
		KvGetString( op, "user/target", path, sizeof path );
		WritePackString( downloads, path );
		KvGetString( op, "user/remote", path, sizeof path );
		WritePackString( downloads, path );

		ResetPack( downloads );
		GetDownload( op );
	} else if( mode == GETMODE_PACKAGE ) {
		decl String:url[512];
		decl String:target[128];
		KvGetString( op, "user/target", target, sizeof target );
		FormatEx( url, sizeof url, "%spackages/%s.package?%s", g_remote_url, target, g_url_request_params );
		RemoteTransfer( url, OnGetPackage, op );
	}
}

//-------------------------------------------------------------------------------------------------
public OnGetPackage( Handle:hndl, bool:success, any:data ) {
	new Handle:op = data;
	if( !success ) {

		decl String:package[64];
		KvGetString( op, "user/target", package, sizeof package );
		if( KvGetNum( hndl, "notfound" ) ) {

			OperationError( op, "Package doesn't exist. \"%s.package\"", package );
		} else {
			OperationError( op, "Couldn't retrieve package. \"%s.package\"", package );
		}
		CleanupGetOp( op );
		EndOperation(op);
		return;
	}

	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	new Handle:kv = CreateKeyValues("ServerToolsPackage");
	decl String:file[128];
	KvGetString( hndl, "file", file, sizeof file );
	if( !FileToKeyValues( kv, file ) ) {
		OperationError( op, "bad package file." );
		CloseHandle(kv);
		CleanupGetOp(op);
		EndOperation(op);
		return;
	}

	if( !KvGotoFirstSubKey( kv ) ) {
		OperationError( op,  "bad package file." );
		CloseHandle(kv);
		CleanupGetOp(op);
		EndOperation(op);
		return;
	}

	new filecount = 0;
	do {
		decl String:target[64];
		KvGetString( kv, "target", target, sizeof target );
		TrimString(target);
		if( target[0] == 0 ) {
			continue;
		}
		if(!IsTarget(target) ) {
			continue;
		}
		decl String:name[64];
		KvGetSectionName( kv, name, sizeof name );
		OperationLog( op, "Getting sub-package: %s", name );
		if( !KvJumpToKey( kv, "files" ) ) {
			OperationLog( op, "Sub-package has no files." );
			continue;
		}

		if( !KvGotoFirstSubKey( kv, false ) ) {
			OperationLog( op, "Sub-package has no files." );
			KvGoBack( kv );
			continue;
		}

		do {
			decl String:localfile[128];
			decl String:remotefile[128];
			KvGetSectionName( kv, localfile, sizeof localfile );

			TrimString( localfile );
			if( localfile[0] == 0 ) continue;
			if( !FormatLocalPath( localfile, sizeof localfile, localfile ) ) {
				OperationError( op, "Bad local file path: %s", localfile );
				continue;
			}
			KvGetString( kv, NULL_STRING, remotefile, sizeof remotefile );

			if( !TranslateRemotePath( localfile, remotefile, sizeof remotefile ) ) {
				OperationError( op, "Bad remote file path: %s", remotefile );
				continue;
			}

			WritePackString( downloads, remotefile );
			WritePackString( downloads, localfile );
			filecount++;
		} while( KvGotoNextKey( kv, false ) );

		KvGoBack( kv );
		KvGoBack( kv );
	} while( KvGotoNextKey( kv ) );
	CloseHandle( kv );

	if( filecount == 0 ) {
		OperationLog( op, "No files to download."  );
		CleanupGetOp( op );
		EndOperation( op );
		return;
	}
	ResetPack( downloads );
	GetDownload( op );
}

//-------------------------------------------------------------------------------------------------
GetDownload( Handle:op ) {

	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	if( !IsPackReadable( downloads, 1 ) ) {
		// we are done.

		new count = KvGetNum( op, "user/count" );
		if( count != 0 ) {
			if( count > 1 ) {
				OperationLog( op, "Get complete. %d files transferred.", count );
			} else {
				OperationLog( op, "Get complete."  );
			}
		} else {
			OperationLog( op, "Get failed." );
		}

		CleanupGetOp( op );
		EndOperation( op );
		return;
	}

	decl String:remotefile[128];
	decl String:localfile[128];
	ReadPackString( downloads, remotefile, sizeof remotefile );
	KvSetString( op, "user/currentfile", remotefile );
	ReadPackString( downloads, localfile, sizeof localfile );
	KvSetString( op, "user/currentlocalfile", localfile );
	KvSetNum( op, "user/downloadmode", 0 );
	decl String:url[512];
	FormatEx( url, sizeof url, "%s%s/%s?%s", g_remote_url, g_remote_dir, remotefile, g_url_request_params );

	OperationLog( op, "getting \"%s\"...", remotefile );

	RemoteTransfer( url, OnGetFile, op );
}

//-------------------------------------------------------------------------------------------------
public OnGetFile( Handle:hndl, bool:success, any:data ) {
	new Handle:op = data;
	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	decl String:remotefile[128];
	KvGetString( op, "user/currentfile", remotefile, sizeof remotefile );
	new downloadmode = KvGetNum( op, "user/downloadmode" );

	if( !success ) {

		if( downloadmode == 0 ) {
			KvSetNum( op, "user/downloadmode", 1 );

			// try to get from nonsync folder.
			decl String:url[512];
			FormatEx( url, sizeof url, "%s%s/%s?%s", g_remote_url, g_remote_dirns, remotefile, g_url_request_params );
			RemoteTransfer( url, OnGetFile, op );

		} else if( downloadmode == 1 ) {
			OperationError( op, "Couldn't retrieve file: %s", remotefile );
			ReadPackString( downloads, remotefile, sizeof remotefile ); // skip local file path
			GetDownload( op );

		} else {
			// assumed downloadmode=2
			// sync file doesnt exist; this is not an error.

			GetDownload( op );
		}
	} else {
		decl String:localfile[128];
		KvGetString( op, "user/currentlocalfile", localfile, sizeof localfile );
		if( downloadmode == 2 ) {
			StrCat( localfile, sizeof localfile, ".sync" );
		}

		decl String:outfile[128];
		KvGetString( hndl, "file", outfile, sizeof outfile );

		if( downloadmode != 2 ) {
			decl String:ext[32];
			GetFileExt( ext, sizeof ext, localfile );
			if( IsTrieSet( g_text_extensions, ext ) ) {
				PreprocessFile( op, outfile, sizeof outfile );
			}
		}

		OperationLog( op, "saving \"%s\"...", localfile );
		PrimeFileTarget( localfile );
		if( RenameFile( localfile, outfile ) ) {
			KvSetNum( op, "user/count", KvGetNum( op, "user/count" ) + 1 );
		} else {
			OperationError( op, "disk error." );
		}

		if( downloadmode == 0 ) {
			// this was downloaded from the normal files, try to get a sync file.
			KvSetNum( op, "user/downloadmode", 2 );
			decl String:url[512];
			FormatEx( url, sizeof url, "%s%s/%s.sync?%s", g_remote_url, g_remote_dir, remotefile, g_url_request_params );
			RemoteTransfer( url, OnGetFile, op );
			return;
		}

		GetDownload(op);
	}
}

enum {
	//GET_SYNC=1 //TODO
};
enum {
	GETMODE_SINGLE=1,
	GETMODE_PACKAGE=2
};

//-------------------------------------------------------------------------------------------------
public Action:Command_remove( args ) {
	if( args == 0 ) {
		PrintToServer( "[ST] Usage: st_remove <target>" );
		return Plugin_Handled;
	}

	decl String:arg[128];
	GetCmdArg( 1, arg ,sizeof arg );
	if( !FormatLocalPath( arg, sizeof arg, arg ) ) {
		PrintToServer( "[ST] st_remove - bad path" );
		return Plugin_Handled;
	}

	if( !FileExists( arg ) ) {
		PrintToServer( "[ST] File doesn't exist." );
		return Plugin_Handled;
	}
	if( !DeleteFile( arg ) ) {
		PrintToServer( "[ST] Couldn't delete file." );
		return Plugin_Handled;
	}

	PrintToServer( "[ST] File deleted." );
	return Plugin_Handled;
}

//-------------------------------------------------------------------------------------------------
public Action:Command_get( args ) {
	if( args == 0 ) {
		PrintToServer( "[ST] Usage: st_get <target> [remote]" );
		//PrintToServer( "[ST]   -s : create sync file" );
		return Plugin_Handled;
	}
	if( g_remote_url[0] == 0 ) {
		PrintToServer( "[ST] st_get - remote is not set up." );
		return Plugin_Handled;
	}

	decl String:target[128];
	target[0] = 0;
	decl String:remote[128];
	remote[0] = 0;
	new flags;
	for( new i = 0; i < args; i++ ) {
		decl String:arg[128];
		GetCmdArg( 1+i, arg, sizeof arg );
		if( arg[0] == '-' ) {
			//if( arg[1] == 's' ) {
		//		flags |= GET_SYNC;
		//	} else {
			PrintToServer( "[ST] st_get - Unknown option: \"%c\"", arg[1] );
			return Plugin_Handled;
		//	}
		} else {
			if( target[0] == 0 ){
				strcopy( target, sizeof target, arg );
			} else if( remote[0] == 0 ) {
				strcopy( remote, sizeof remote, arg );
			} else {
				PrintToServer( "[ST] st_get - bad args" );
				return Plugin_Handled;
			}
		}
	}

	// validate target
	if( target[0] == 0 ) {
		PrintToServer( "[ST] st_get - missing target" );
		return Plugin_Handled;
	}

	if( strncmp( target, "pkg/", 4 ) == 0 ){
		// package is desired
		Format( target, sizeof target, "%s", target[4] );
		if( target[0] == 0 ) return Plugin_Handled;
		DoGetPackage( target, flags );
		return Plugin_Handled;
	}

	if( !FormatLocalPath( target, sizeof target, target ) ) {
		PrintToServer( "[ST] st_get - bad path" );
		return Plugin_Handled;
	}

	if( !TranslateRemotePath( target, remote, sizeof remote ) ) {
		PrintToServer( "[ST] st_get - bad remote path" );
		return Plugin_Handled;
	}

	DoGet( target, remote, flags );
	return Plugin_Handled;
}

//-------------------------------------------------------------------------------------------------
DoGetPackage( const String:package[], flags ) {

	LoadIDConfig();

	new Handle:op = CreateOperation( "Server Get", OnGetStart );
	KvSetNum( op, "user/mode", GETMODE_PACKAGE );
	KvSetString( op, "user/target", package );
	KvSetNum( op, "user/flags", flags );
	StartOperation(op);

}

//-------------------------------------------------------------------------------------------------
DoGet( const String:localfile[], const String:remotefile[], flags ) {

	LoadIDConfig();

	new Handle:op = CreateOperation( "Server Get", OnGetStart );
	KvSetNum( op, "user/mode", GETMODE_SINGLE );
	KvSetString( op, "user/target", localfile );
	KvSetString( op, "user/remote", remotefile );
	KvSetNum( op, "user/flags", flags );

	StartOperation(op);
}

//-------------------------------------------------------------------------------------------------
CleanupGetOp( Handle:op ) {
	CloseHandle( KvGetHandle( op, "user/downloadlist" ) );
}

//-------------------------------------------------------------------------------------------------
public OnGetStart( Handle:op ) {
	new mode = KvGetNum( op, "user/mode" );
	new Handle:downloads = CreateDataPack();
	KvSetHandle( op, "user/downloadlist", downloads );

	if( mode == GETMODE_SINGLE ) {
		decl String:path[128];
		KvGetString( op, "user/target", path, sizeof path );
		WritePackString( downloads, path );
		KvGetString( op, "user/remote", path, sizeof path );
		WritePackString( downloads, path );

		ResetPack( downloads );
		GetDownload( op );
	} else if( mode == GETMODE_PACKAGE ) {
		decl String:url[512];
		decl String:target[128];
		KvGetString( op, "user/target", target, sizeof target );
		FormatEx( url, sizeof url, "%spackages/%s.package?%s", g_remote_url, target, g_url_request_params );
		RemoteTransfer( url, OnGetPackage, op );
	}
}

//-------------------------------------------------------------------------------------------------
public OnGetPackage( Handle:hndl, bool:success, any:data ) {
	new Handle:op = data;
	if( !success ) {

		decl String:package[64];
		KvGetString( op, "user/target", package, sizeof package );
		if( KvGetNum( hndl, "notfound" ) ) {

			OperationError( op, "Package doesn't exist. \"%s\"", package );
		} else {
			OperationError( op, "Couldn't retrieve package. \"%s\"", package );
		}
		CleanupGetOp( op );
		EndOperation(op);
		return;
	}

	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	new Handle:kv = CreateKeyValues("ServerToolsPackage");
	decl String:file[128];
	KvGetString( hndl, "file", file, sizeof file );
	if( !FileToKeyValues( kv, file ) ) {
		OperationError( op, "bad package file." );
		CloseHandle(kv);
		CleanupGetOp(op);
		EndOperation(op);
		return;
	}

	if( !KvGotoFirstSubKey( kv ) ) {
		OperationError( op,  "bad package file." );
		CloseHandle(kv);
		CleanupGetOp(op);
		EndOperation(op);
		return;
	}

	new filecount = 0;
	do {
		decl String:target[64];
		KvGetString( kv, "target", target, sizeof target );
		TrimString(target);
		if( target[0] == 0 ) {
			continue;
		}
		if(!IsTarget(target) ) {
			continue;
		}
		decl String:name[64];
		KvGetSectionName( kv, name, sizeof name );
		OperationLog( op, "Getting sub-package: %s", name );
		if( !KvJumpToKey( kv, "files" ) ) {
			OperationLog( op, "Sub-package has no files." );
			continue;
		}

		if( !KvGotoFirstSubKey( kv, false ) ) {
			OperationLog( op, "Sub-package has no files." );
			KvGoBack( kv );
			continue;
		}

		do {
			decl String:localfile[128];
			decl String:remotefile[128];
			KvGetSectionName( kv, localfile, sizeof localfile );

			TrimString( localfile );
			if( localfile[0] == 0 ) continue;
			if( !FormatLocalPath( localfile, sizeof localfile, localfile ) ) {
				OperationError( op, "Bad local file path: %s", localfile );
				continue;
			}
			KvGetString( kv, NULL_STRING, remotefile, sizeof remotefile );

			if( !TranslateRemotePath( localfile, remotefile, sizeof remotefile ) ) {
				OperationError( op, "Bad remote file path: %s", remotefile );
				continue;
			}

			WritePackString( downloads, remotefile );
			WritePackString( downloads, localfile );
			filecount++;
		} while( KvGotoNextKey( kv, false ) );

		KvGoBack( kv );
		KvGoBack( kv );
	} while( KvGotoNextKey( kv ) );
	CloseHandle( kv );

	if( filecount == 0 ) {
		OperationLog( op, "No files to download."  );
		CleanupGetOp( op );
		EndOperation( op );
		return;
	}
	ResetPack( downloads );
	GetDownload( op );
}

//-------------------------------------------------------------------------------------------------
GetDownload( Handle:op ) {

	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	if( !IsPackReadable( downloads, 1 ) ) {
		// we are done.

		new count = KvGetNum( op, "user/count" );
		if( count != 0 ) {
			if( count > 1 ) {
				OperationLog( op, "Get complete. %d files transferred.", count );
			} else {
				OperationLog( op, "Get complete."  );
			}
		} else {
			OperationLog( op, "Get failed." );
		}

		CleanupGetOp( op );
		EndOperation( op );
		return;
	}

	decl String:remotefile[128];
	decl String:localfile[128];
	ReadPackString( downloads, remotefile, sizeof remotefile );
	KvSetString( op, "user/currentfile", remotefile );
	ReadPackString( downloads, localfile, sizeof localfile );
	KvSetString( op, "user/currentlocalfile", localfile );
	KvSetNum( op, "user/downloadmode", 0 );
	decl String:url[512];
	FormatEx( url, sizeof url, "%s%s/%s?%s", g_remote_url, g_remote_dir, remotefile, g_url_request_params );

	OperationLog( op, "getting \"%s\"...", remotefile );

	RemoteTransfer( url, OnGetFile, op );
}

//-------------------------------------------------------------------------------------------------
public OnGetFile( Handle:hndl, bool:success, any:data ) {
	new Handle:op = data;
	new Handle:downloads = KvGetHandle( op, "user/downloadlist" );

	decl String:remotefile[128];
	KvGetString( op, "user/currentfile", remotefile, sizeof remotefile );
	new downloadmode = KvGetNum( op, "user/downloadmode" );

	if( !success ) {

		if( downloadmode == 0 ) {
			KvSetNum( op, "user/downloadmode", 1 );

			// try to get from nonsync folder.
			decl String:url[512];
			FormatEx( url, sizeof url, "%s%s/%s?%s", g_remote_url, g_remote_dirns, remotefile, g_url_request_params );
			RemoteTransfer( url, OnGetFile, op );

		} else if( downloadmode == 1 ) {
			OperationError( op, "Couldn't retrieve file: %s", remotefile );
			ReadPackString( downloads, remotefile, sizeof remotefile ); // skip local file path
			GetDownload( op );

		} else {
			// assumed downloadmode=2
			// sync file doesnt exist; this is not an error.

			GetDownload( op );
		}
	} else {
		if( downloadmode != 2 ) {
			decl String:localfile[128];
			KvGetString( op, "user/currentlocalfile", localfile, sizeof localfile );
			decl String:outfile[128];
			KvGetString( hndl, "file", outfile, sizeof outfile );
			PrimeFileTarget( localfile );

			decl String:ext[32];
			GetFileExt( ext, sizeof ext, localfile );
			if( IsTrieSet( g_text_extensions, ext ) ) {
				PreprocessFile( op, outfile, sizeof outfile );
			}
			OperationLog( op, "saving \"%s\"...", localfile );
			if( RenameFile( localfile, outfile ) ) {
				KvSetNum( op, "user/count", KvGetNum( op, "user/count" ) + 1 );
			} else {
				OperationError( op, "disk error." );
			}

			if( downloadmode == 0 ) {
				// this was downloaded from the normal files, try to get a sync file.
				KvSetNum( op, "user/downloadmode", 2 );
				decl String:url[512];
				FormatEx( url, sizeof url, "%s%s/%s.sync?%s", g_remote_url, g_remote_dir, remotefile, g_url_request_params );
				RemoteTransfer( url, OnGetFile, op );
				return;
			}
		}
		GetDownload(op);
	}
}


Fix issues

Fix issues

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <steamtools>
#include <base64>
#include <smjansson>

#define PLUGIN_VERSION "0.1"

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

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define RANDOM_SONG_ROUTE ""
#define SONGS_ROUTE "/songs"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

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

new bool:g_IsInCooldown[MAXPLAYERS+1];
new g_PallNextFree = 0;

public OnPluginStart()
{

    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_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    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_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegConsoleCmd("sm_fstop", Command_Fstop, "[ADMIN] Stop the current pall for everyone");
    RegConsoleCmd("sm_fpall", Command_Fpall, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");

    HookEvent("map_change", Event_MapChange);
    HookEvent("client_connect", Event_ClientConnect);
}

public Action:Command_P(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[IGA] Usage: !p <song>");
        return Plugin_Handled;
    }
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:song[MAX_SONG_LENGTH];
        GetCmdArgString(song, sizeof(song));
        QuerySong(client, song);
    }

    return Plugin_Handled;
}

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

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

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

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

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

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

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

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

public Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO
}

public Event_ClientConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO
}

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

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

public 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 StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

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

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

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

public QuerySong(client, String:song[MAX_SONG_LENGTH], bool:pall = false, client_theme = 0, String:map_theme[] ="")
{
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));

    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "player", GetClientUserId(client));
    Steam_SetHTTPRequestGetOrPostParameter(request, "song", song);

    if(client_theme > 0)
    {
        //Find the user's theme
        decl String:uid_theme[MAX_COMMUNITYID_LENGTH];
        Steam_GetCSteamIDForClient(client, uid_theme, sizeof(uid_theme));
        Steam_SetHTTPRequestGetOrPostParameter(request, "uid_theme", uid_theme);
    }else if(strlen(map_theme) > 0){
        //Find the map's theme
        Steam_SetHTTPRequestGetOrPostParameter(request, "map_theme", map_theme);
    }

    Steam_SendHTTPRequest(request, ReceiveQuerySong, GetClientUserId(client));

    StartCooldown(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 String:song[MAX_SONG_LENGTH], String:description[64];
        json_object_get_string(json, "song", song, sizeof(song));
        json_object_get_string(json, "description", description, sizeof(description));

        if(pall)
        {
            PrintToChatAll("Now Playing: %s", description);
            PlaySongAll(song);
        }else if(client > 0){
            PrintToChat(client, "Now Playing: %s", description);
            PlaySong(client, song);
        }
    }

    CloseHandle(json);
    Steam_ReleaseHTTPRequest(request);
}

public PlaySongAll(song[])
{
    //TODO update PALL duration
    for (new client=1; client <= MaxClients; client++)
    {
        if (ClientHasPallEnabled(client))
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, song[])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    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", base_url, SONGS_ROUTE, song);

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

public bool:ClientHasPallEnabled(client)
{
    //TODO do cookie check
    return IsClientAuthorized(client);
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <steamtools>
#include <base64>
#include <smjansson>

#define PLUGIN_VERSION "0.1"

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

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define RANDOM_SONG_ROUTE ""
#define SONGS_ROUTE "/songs"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

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

new bool:g_IsInCooldown[MAXPLAYERS+1];
new g_PallNextFree = 0;

public OnPluginStart()
{

    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_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    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_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegConsoleCmd("sm_fstop", Command_Fstop, "[ADMIN] Stop the current pall for everyone");
    RegConsoleCmd("sm_fpall", Command_Fpall, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");

    HookEvent("map_change", Event_MapChange);
    HookEvent("client_connect", Event_ClientConnect);
}

public Action:Command_P(client, args)
{
    if (args < 1)
    {
        ReplyToCommand(client, "[IGA] Usage: !p <song>");
        return Plugin_Handled;
    }
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:song[MAX_SONG_LENGTH];
        GetCmdArgString(song, sizeof(song));
        QuerySong(client, song);
    }

    return Plugin_Handled;
}

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

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

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

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

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

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

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

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

public Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO
}

public Event_ClientConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO
}

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

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

public 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 StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

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

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

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

public QuerySong(client, String:song[MAX_SONG_LENGTH], bool:pall = false, client_theme = 0, String:map_theme[] ="")
{
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));

    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "player", GetClientUserId(client));
    Steam_SetHTTPRequestGetOrPostParameter(request, "song", song);

    if(client_theme > 0)
    {
        //Find the user's theme
        decl String:uid_theme[MAX_COMMUNITYID_LENGTH];
        Steam_GetCSteamIDForClient(client, uid_theme, sizeof(uid_theme));
        Steam_SetHTTPRequestGetOrPostParameterInt(request, "uid_theme", uid_theme);
    }else if(strlen(map_theme) > 0){
        //Find the map's theme
        Steam_SetHTTPRequestGetOrPostParameter(request, "map_theme", map_theme);
    }

    Steam_SendHTTPRequest(request, ReceiveSongQuery, GetClientUserId(client));

    StartCooldown(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 String:song[MAX_SONG_LENGTH], String:description[64];
        json_object_get_string(json, "song", song, sizeof(song));
        json_object_get_string(json, "description", description, sizeof(description));

        if(pall)
        {
            PrintToChatAll("Now Playing: %s", description);
                PlaySongAll(song);
        }else if(client > 0){
            PrintToChat(client, "Now Playing: %s", description);
                PlaySong(client, song);
        }
    }

    CloseHandle(json);
    Steam_ReleaseHTTPRequest(request);
}

public PlaySongAll(song[MAX_SONG_LENGTH])
{
    //TODO update PALL duration
    for (new client=1; client <= MaxClients; client++)
    {
        if (ClientHasPallEnabled(client))
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, song[MAX_SONG_LENGTH])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    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", base_url, SONGS_ROUTE, song);

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

public bool:ClientHasPallEnabled(client)
{
    //TODO do cookie check
    return IsClientAuthorized(client);
}


Nothing yet

Nothing yet

/**
 * vim: set ts=4 :
 * =============================================================================
 * sm_twitch_recognition
 * Idea is to scan a player's steam profile for a url to twitch.tv.  It would
 * be assumed that they put their own url on their profile.  Then that twitch
 * channel is queried to check if it is currently streaming. If it is then
 * that player is streaming live on the server right now.
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <twitch_recognition>
#include <regex>
#include <steamtools>
#include <smjansson>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "Twitch Recognition",
    author = "CrimsonTautology",
    description = "Recognize which players on the server are livestreaming to twitch.tv",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_twitch_recognition"
};

new bool:g_DoTwitchCheck[MAXPLAYERS+1] = {false, ...};
new bool:g_HasTwitchChannel[MAXPLAYERS+1] = {false, ...};
new bool:g_IsStreaming[MAXPLAYERS+1] = {false, ...};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;

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

    RegPluginLibrary("twitch_recognition");

    return APLRes_Success;
}

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

    g_Cvar_Enabled = CreateConVar("sm__plugin_name__enabled", "1", "Enabled");

    HookEvent("player_spawn", Event_PlayerSpawn);

    RegConsoleCmd("sm_test", Command_Test, "TODO: TEST");
}

public OnClientConnected(client)
{
    g_DoTwitchCheck[client] = true;
    g_HasTwitchChannel[client] = false;
    g_IsStreaming[client] = false;
}

public OnClientDisconnect(client)
{
    g_DoTwitchCheck[client] = false;
    g_HasTwitchChannel[client] = false;
    g_IsStreaming[client] = false;
}

public Action:Command_Test(client, args)
{
    return Plugin_Handled;
}

public Event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
{
    new client = GetClientOfUserId(GetEventInt(event, "userid"));
    PrintToChatAll("player spawn %d", client); //TODO
    //if(g_DoTwitchCheck[client])
    {
        QuerySteamWorksApi(client);
        //QueryTwitchApi(client, "360Chrism");
        g_DoTwitchCheck[client] = false;
    }
}

QuerySteamWorksApi(client)
{
    if (!IsEnabled()) return;

    PrintToChatAll("QuerySteamWorksApi: %d", client); //TODO

    //Build steamworks url
    decl String:url[512], String:uid[128];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));

    //uid = "76561197967876119";
    Format(url, sizeof(url), "http://steamcommunity.com/profiles/%s/?xml=1", uid);
    //uid = "hereIsAModelOfACalciumMolecule";
    //Format(url, sizeof(url), "http://steamcommunity.com/id/%s/?xml=1", uid);
    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_GET, url);
    if(request == INVALID_HTTP_HANDLE) return;

    //Steam_SetHTTPRequestHeaderValue(request, "Content-Length", "0");
    Steam_SetHTTPRequestNetworkActivityTimeout(request, 5);
    Steam_SendHTTPRequest(request, ReceiveSteamWorksApi, GetClientUserId(client));

    PrintToChatAll("%s", url);//TODO

}

public ReceiveSteamWorksApi(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(successful && code == HTTPStatusCode_Invalid)
    {
        new location_size = Steam_GetHTTPResponseHeaderSize(request, "Location:");
        PrintToChatAll("LocationSize: %d", location_size);//TODO
        new body_size = Steam_GetHTTPResponseBodySize(request);
        PrintToChatAll("BodySize: %d", body_size);//TODO
        decl String:location[location_size];
        Steam_ReleaseHTTPRequest(request);
        return;

    }else if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[Twitch] Error at RecieveSteamWorksApi (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:channel[256];
    //Steam_GetHTTPResponseHeaderValue(request, "Location", location, location_size);
    //PrintToChatAll("Location: %s", location);

    new body_size = Steam_GetHTTPResponseBodySize(request);
    PrintToChatAll("BodySize: %d", body_size);//TODO
    decl String:data[body_size];

    Steam_GetHTTPResponseBodyData(request, data, body_size);
    Steam_ReleaseHTTPRequest(request);

    //NOTE : This is very sloppy; the text returned could well beover 20kb;
    //most of the group and owned game stuff is useless or could contain
    //false-positive twitch urls.  However the more useful SteamApi does not
    //query a steam profile's summary, headline or links fields.

    //Search user's steamworks data for twitch.tv urls.
    //We're assuming they would only put their own url on their page
    new Handle:channel_regex = CompileRegex("twitch.tv/(\\w+)");
    MatchRegex(channel_regex, data);

    if(GetRegexSubString(channel_regex, 1, channel, sizeof(channel)))
    {
        PrintToChatAll("%d is a twitch user", client); //TODO
        //A twitch url was found; assume it is the player's
        g_HasTwitchChannel[client] = true;
        QueryTwitchApi(client, channel);
    }else{
        PrintToChatAll("%d is not a twitch user", client); //TODO
    }
}

QueryTwitchApi(client, String:channel[])
{
    if (!IsEnabled()) return;

    PrintToChatAll("QueryTwitchApi: %d - %s", client, channel); //TODO
    //Build twitch url
    decl String:url[256];
    Format(url, sizeof(url),
            "%s%s", TWITCH_ROUTE, channel);

    PrintToChatAll("%s", url);//TODO
    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_GET, url);

    if(request == INVALID_HTTP_HANDLE)
    {
        return;
    }

    new player = GetClientUserId(client);
    Steam_SendHTTPRequest(request, ReceiveTwitchApi, player);
}

public ReceiveTwitchApi(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);

    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[Twitch] Error at RecieveTwitchApi (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    new  body_size = Steam_GetHTTPResponseBodySize(request);
    PrintToChatAll("BodySize: %d", body_size);//TODO
    decl String:data[body_size];

    Steam_GetHTTPResponseBodyData(request, data, body_size);

    //parse JSON response
    new Handle:json = json_load(data);
    new Handle:stream = json_object_get(json, "stream");

    if(stream == INVALID_HANDLE)
    {
        //json is wrong
        PrintToChatAll("%d ruh-roh", client); //TODO
    }else if(json_is_null(stream))
    {
        //Stream object is null, thus player is not streaming
        g_IsStreaming[client] = false;
        PrintToChatAll("%d is not streaming", client); //TODO
    }else{
        //Player is streaming
        g_IsStreaming[client] = true;

        //Get more data
        new String:display_name[128], String:status[128], String:url[128];
        new viewers = json_object_get_int(stream, "viewers");

        new Handle:channel = json_object_get(stream, "channel");
        json_object_get_string(channel, "display_name", display_name, sizeof(display_name));
        json_object_get_string(channel, "status", status, sizeof(status));
        json_object_get_string(channel, "url", url, sizeof(url));
        CloseHandle(channel);

        //Do whatever
        PrintToChatAll("%d people are watching %s stream \"%s\"", viewers, display_name, status);//TODO
        PrintToChatAll("visit %s", url);//TODO

    }
    CloseHandle(stream);
    CloseHandle(json);
}

bool:IsEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * sm_twitch_recognition
 * Idea is to scan a player's steam profile for a url to twitch.tv.  It would
 * be assumed that they put their own url on their profile.  Then that twitch
 * channel is queried to check if it is currently streaming. If it is then
 * that player is streaming live on the server right now.
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <twitch_recognition>
#include <regex>
#include <steamtools>
#include <smjansson>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "Twitch Recognition",
    author = "CrimsonTautology",
    description = "Recognize which players on the server are livestreaming to twitch.tv",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_twitch_recognition"
};

new bool:g_DoTwitchCheck[MAXPLAYERS+1] = {false, ...};
new bool:g_HasTwitchChannel[MAXPLAYERS+1] = {false, ...};
new bool:g_IsStreaming[MAXPLAYERS+1] = {false, ...};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;

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

    RegPluginLibrary("twitch_recognition");

    return APLRes_Success;
}

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

    g_Cvar_Enabled = CreateConVar("sm__plugin_name__enabled", "1", "Enabled");

    HookEvent("player_spawn", Event_PlayerSpawn);

    RegConsoleCmd("sm_test", Command_Test, "TODO: TEST");
}

public OnClientConnected(client)
{
    g_DoTwitchCheck[client] = true;
    g_HasTwitchChannel[client] = false;
    g_IsStreaming[client] = false;
}

public OnClientDisconnect(client)
{
    g_DoTwitchCheck[client] = false;
    g_HasTwitchChannel[client] = false;
    g_IsStreaming[client] = false;
}

public Action:Command_Test(client, args)
{
    return Plugin_Handled;
}

public Event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
{
    new client = GetClientOfUserId(GetEventInt(event, "userid"));
    PrintToChatAll("player spawn %d", client); //TODO
    //if(g_DoTwitchCheck[client])
    {
        QuerySteamWorksApi(client);
        //QueryTwitchApi(client, "360Chrism");
        g_DoTwitchCheck[client] = false;
    }
}

QuerySteamWorksApi(client)
{
    if (!IsEnabled()) return;

    PrintToChatAll("QuerySteamWorksApi: %d", client); //TODO

    //Build steamworks url
    decl String:url[512], String:uid[128];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));

    //uid = "76561197967876119";
    //Format(url, sizeof(url), "http://steamcommunity.com/profiles/%s/?xml=1", uid);
    uid = "hereIsAModelOfACalciumMolecule";
    Format(url, sizeof(url), "http://steamcommunity.com/id/%s/?xml=1", uid);
    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_GET, url);
    if(request == INVALID_HTTP_HANDLE) return;

    Steam_SetHTTPRequestHeaderValue(request, "Content-Length", "0");
    Steam_SetHTTPRequestNetworkActivityTimeout(request, 5);
    Steam_SendHTTPRequest(request, ReceiveSteamWorksApi, GetClientUserId(client));

    PrintToChatAll("%s", url);//TODO

}

public ReceiveSteamWorksApi(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(successful && code == HTTPStatusCode_Invalid)
    {
        new location_size = Steam_GetHTTPResponseHeaderSize(request, "Location:");
        PrintToChatAll("LocationSize: %d", location_size);//TODO
        decl String:location[location_size];
        Steam_ReleaseHTTPRequest(request);
        return;

    }else if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[Twitch] Error at RecieveSteamWorksApi (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:channel[256];
    //Steam_GetHTTPResponseHeaderValue(request, "Location", location, location_size);
    //PrintToChatAll("Location: %s", location);

    new body_size = Steam_GetHTTPResponseBodySize(request);
    PrintToChatAll("BodySize: %d", body_size);//TODO
    decl String:data[body_size];

    Steam_GetHTTPResponseBodyData(request, data, body_size);
    Steam_ReleaseHTTPRequest(request);

    //NOTE : This is very sloppy; the text returned could well beover 20kb;
    //most of the group and owned game stuff is useless or could contain
    //false-positive twitch urls.  However the more useful SteamApi does not
    //query a steam profile's summary, headline or links fields.

    //Search user's steamworks data for twitch.tv urls.
    //We're assuming they would only put their own url on their page
    new Handle:channel_regex = CompileRegex("twitch.tv/(\\w+)");
    MatchRegex(channel_regex, data);

    if(GetRegexSubString(channel_regex, 1, channel, sizeof(channel)))
    {
        PrintToChatAll("%d is a twitch user", client); //TODO
        //A twitch url was found; assume it is the player's
        g_HasTwitchChannel[client] = true;
        QueryTwitchApi(client, channel);
    }else{
        PrintToChatAll("%d is not a twitch user", client); //TODO
    }
}

QueryTwitchApi(client, String:channel[])
{
    if (!IsEnabled()) return;

    PrintToChatAll("QueryTwitchApi: %d - %s", client, channel); //TODO
    //Build twitch url
    decl String:url[256];
    Format(url, sizeof(url),
            "%s%s", TWITCH_ROUTE, channel);

    PrintToChatAll("%s", url);//TODO
    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_GET, url);

    if(request == INVALID_HTTP_HANDLE)
    {
        return;
    }

    new player = GetClientUserId(client);
    Steam_SendHTTPRequest(request, ReceiveTwitchApi, player);
}

public ReceiveTwitchApi(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);

    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[Twitch] Error at RecieveTwitchApi (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    new  body_size = Steam_GetHTTPResponseBodySize(request);
    PrintToChatAll("BodySize: %d", body_size);//TODO
    decl String:data[body_size];

    Steam_GetHTTPResponseBodyData(request, data, body_size);

    //parse JSON response
    new Handle:json = json_load(data);
    new Handle:stream = json_object_get(json, "stream");

    if(stream == INVALID_HANDLE)
    {
        //json is wrong
        PrintToChatAll("%d ruh-roh", client); //TODO
    }else if(json_is_null(stream))
    {
        //Stream object is null, thus player is not streaming
        g_IsStreaming[client] = false;
        PrintToChatAll("%d is not streaming", client); //TODO
    }else{
        //Player is streaming
        g_IsStreaming[client] = true;

        //Get more data
        new String:display_name[128], String:status[128], String:url[128];
        new viewers = json_object_get_int(stream, "viewers");

        new Handle:channel = json_object_get(stream, "channel");
        json_object_get_string(channel, "display_name", display_name, sizeof(display_name));
        json_object_get_string(channel, "status", status, sizeof(status));
        json_object_get_string(channel, "url", url, sizeof(url));
        CloseHandle(channel);

        //Do whatever
        PrintToChatAll("%d people are watching %s stream \"%s\"", viewers, display_name, status);//TODO
        PrintToChatAll("visit %s", url);//TODO

    }
    CloseHandle(stream);
    CloseHandle(json);
}

bool:IsEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}


fix semicolon

fix semicolon

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * Handles the sounds and particle effects for the jetpacks.  Also allows
 * donators to customize their jetpack.
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <jetpack_plus>
#include <clientprefs>
#include <sdktools>
#include <particle>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

#define MAX_JETPACK_TYPES 64

public Plugin:myinfo =
{
    name = "Jetpack Plus (Bling)",
    author = "CrimsonTautology",
    description = "Handles sounds and particle effects for jetpacks",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new bool:g_DonatorLibraryExists = false;

//Player options
new Handle:g_Cookie_SelectedJetpack = INVALID_HANDLE;
new g_SelectedJetpack[MAXPLAYERS+1] = {0, ...};
new g_JetpackParticle[MAXPLAYERS+1];

//Parallel arrays to store types of jetpacks
new String:g_JetpackTypeName[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeParticle[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeSound[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new g_JetpackTypeCount = 0;

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    MarkNativeAsOptional("IsPlayerDonator");
    MarkNativeAsOptional("Donator_RegisterMenuItem");

    return APLRes_Success;
}

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

    g_Cookie_SelectedJetpack = RegClientCookie("selected_jetpack", "Selected jetpack type", CookieAccess_Private);
    g_DonatorLibraryExists = LibraryExists("donator.core");
}

public OnAllPluginsLoaded()
{
    if (g_DonatorLibraryExists)
    {
        Donator_RegisterMenuItem("Jetpack Bling", JetpackBlingMenu);
    }
}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = false;
    }
}

public OnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = true;
    }
}

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

    GetClientCookie(client, g_Cookie_SelectedJetpack, buffer, sizeof(buffer));
    new type_of = StringToInt(buffer);
    if (strlen(buffer) > 0 && type_of < g_JetpackTypeCount){
        g_SelectedJetpack[client] = type_of;
    }
}

public OnMapStart()
{
    ReadJetpacks();
}

ReadJetpacks()
{
    //Create default jetpack
    g_JetpackTypeName[0] = "Default";
    g_JetpackTypeParticle[0] = "burninggibs";
    g_JetpackTypeSound[0] = "vehicles/airboat/fan_blade_fullthrottle_loop1.wav";
    PrecacheSound(g_JetpackTypeSound[0], true);
    g_JetpackTypeCount = 1;

    new Handle:kv = CreateKeyValues("Jetpacks");

    decl String:path[PLATFORM_MAX_PATH], String:tmp[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "configs/jetpacks.cfg");

    if(FileExists(path))
    {
        FileToKeyValues(kv, path);
        KvGotoFirstSubKey(kv);

        do
        {
            KvGetSectionName(kv, tmp, sizeof(tmp));
            if(StrEqual(tmp, "Jetpack") )
            {
                KvGetString(kv, "name", g_JetpackTypeName[g_JetpackTypeCount], PLATFORM_MAX_PATH);
                KvGetString(kv, "particle", g_JetpackTypeParticle[g_JetpackTypeCount], PLATFORM_MAX_PATH);
                KvGetString(kv, "sound", g_JetpackTypeSound[g_JetpackTypeCount], PLATFORM_MAX_PATH);

                PrecacheSound(g_JetpackTypeSound[g_JetpackTypeCount], true);
                g_JetpackTypeCount++;
            }

        } while(KvGotoNextKey(kv) && g_JetpackTypeCount < MAX_JETPACK_TYPES);

    } else {
        LogError("File Not Found: %s", path);
    }

    CloseHandle(kv);
}

public Action:OnStartJetpack(client)
{
    ApplyJetpackEffects(client);
    return Plugin_Continue;
}

public Action:OnStopJetpack(client)
{
    ClearJetpackEffects(client);

    return Plugin_Continue;
}

GetSelectedJetpackOfClient(client)
{
    return g_SelectedJetpack[client];
}

SetSelectedJetpackOfClient(client, type_of)
{
    new String:tmp[11];
    IntToString(type_of, tmp, sizeof(tmp));
    SetClientCookie(client, g_Cookie_SelectedJetpack, tmp);
    g_SelectedJetpack[client] = type_of;
}

ApplyJetpackEffects(client)
{
    new selected = GetSelectedJetpackOfClient(client);
    EmitSoundToAll(g_JetpackTypeSound[selected], client, SNDCHAN_AUTO);

    static const Float:ang[3] = { -25.0, 90.0, 0.0 };
    static const Float:pos[3] = {   0.0, 10.0, 1.0 };
    g_JetpackParticle[client] = CreateParticle(g_JetpackTypeParticle[selected], 0.0, client, Attach, "flag", pos, ang);
}

ClearJetpackEffects(client)
{
    new selected = GetSelectedJetpackOfClient(client);
    StopSound(client, SNDCHAN_AUTO, g_JetpackTypeSound[selected]);
    DeleteParticle(g_JetpackParticle[client]);
}

//Menus
public DonatorMenu:JetpackBlingMenu(client) ChangeJetpackMenu;
ChangeJetpackMenu(client)
{
    new Handle:menu = CreateMenu(ChangeJetpackMenuHandler);
    new selected = GetSelectedJetpackOfClient(client);

    SetMenuTitle(menu, "Choose your jetpack");

    decl String:buf[16];
    for(new i=0; i < g_JetpackTypeCount && i < MAX_JETPACK_TYPES; i++)
    {
        IntToString(i, buf, sizeof(buf));
        AddMenuItem(menu,
                buf,
                g_JetpackTypeName[i],
                i == selected ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
    }

    DisplayMenu(menu, client, 20);
}

public ChangeJetpackMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    switch (action)
    {
        case MenuAction_Select:
            {
                new client = param1;
                new String:info[32];
                GetMenuItem(menu, param2, info, sizeof(info));
                new type_of = StringToInt(info);

                //Clear running particle and sound to prevent overlap
                ClearJetpackEffects(client);

                SetSelectedJetpackOfClient(client, type_of);
            }
        case MenuAction_End: CloseHandle(menu);
    }
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * Handles the sounds and particle effects for the jetpacks.  Also allows
 * donators to customize their jetpack.
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <jetpack_plus>
#include <clientprefs>
#include <sdktools>
#include <particle>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

#define MAX_JETPACK_TYPES 64

public Plugin:myinfo =
{
    name = "Jetpack Plus (Bling)",
    author = "CrimsonTautology",
    description = "Handles sounds and particle effects for jetpacks",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new bool:g_DonatorLibraryExists = false;

//Player options
new Handle:g_Cookie_SelectedJetpack = INVALID_HANDLE;
new g_SelectedJetpack[MAXPLAYERS+1] = {0, ...};
new g_JetpackParticle[MAXPLAYERS+1];

//Parallel arrays to store types of jetpacks
new String:g_JetpackTypeName[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeParticle[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeSound[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new g_JetpackTypeCount = 0;

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    MarkNativeAsOptional("IsPlayerDonator");
    MarkNativeAsOptional("Donator_RegisterMenuItem");

    return APLRes_Success;
}

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

    g_Cookie_SelectedJetpack = RegClientCookie("selected_jetpack", "Selected jetpack type", CookieAccess_Private);
    g_DonatorLibraryExists = LibraryExists("donator.core");
}

public OnAllPluginsLoaded()
{
    if (g_DonatorLibraryExists)
    {
        Donator_RegisterMenuItem("Jetpack Bling", JetpackBlingMenu);
    }
}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = false;
    }
}

public OnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = true;
    }
}

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

    GetClientCookie(client, g_Cookie_SelectedJetpack, buffer, sizeof(buffer));
    new type_of = StringToInt(buffer)
    if (strlen(buffer) > 0 && type_of < g_JetpackTypeCount){
        g_SelectedJetpack[client] = type_of;
    }
}

public OnMapStart()
{
    ReadJetpacks();
}

ReadJetpacks()
{
    //Create default jetpack
    g_JetpackTypeName[0] = "Default";
    g_JetpackTypeParticle[0] = "burninggibs";
    g_JetpackTypeSound[0] = "vehicles/airboat/fan_blade_fullthrottle_loop1.wav";
    PrecacheSound(g_JetpackTypeSound[0], true);
    g_JetpackTypeCount = 1;

    new Handle:kv = CreateKeyValues("Jetpacks");

    decl String:path[PLATFORM_MAX_PATH], String:tmp[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "configs/jetpacks.cfg");

    if(FileExists(path))
    {
        FileToKeyValues(kv, path);
        KvGotoFirstSubKey(kv);

        do
        {
            KvGetSectionName(kv, tmp, sizeof(tmp));
            if(StrEqual(tmp, "Jetpack") )
            {
                KvGetString(kv, "name", g_JetpackTypeName[g_JetpackTypeCount], PLATFORM_MAX_PATH);
                KvGetString(kv, "particle", g_JetpackTypeParticle[g_JetpackTypeCount], PLATFORM_MAX_PATH);
                KvGetString(kv, "sound", g_JetpackTypeSound[g_JetpackTypeCount], PLATFORM_MAX_PATH);

                PrecacheSound(g_JetpackTypeSound[g_JetpackTypeCount], true);
                g_JetpackTypeCount++;
            }

        } while(KvGotoNextKey(kv) && g_JetpackTypeCount < MAX_JETPACK_TYPES);

    } else {
        LogError("File Not Found: %s", path);
    }

    CloseHandle(kv);
}

public Action:OnStartJetpack(client)
{
    ApplyJetpackEffects(client);
    return Plugin_Continue;
}

public Action:OnStopJetpack(client)
{
    ClearJetpackEffects(client);

    return Plugin_Continue;
}

GetSelectedJetpackOfClient(client)
{
    return g_SelectedJetpack[client];
}

SetSelectedJetpackOfClient(client, type_of)
{
    new String:tmp[11];
    IntToString(type_of, tmp, sizeof(tmp));
    SetClientCookie(client, g_Cookie_SelectedJetpack, tmp);
    g_SelectedJetpack[client] = type_of;
}

ApplyJetpackEffects(client)
{
    new selected = GetSelectedJetpackOfClient(client);
    EmitSoundToAll(g_JetpackTypeSound[selected], client, SNDCHAN_AUTO);

    static const Float:ang[3] = { -25.0, 90.0, 0.0 };
    static const Float:pos[3] = {   0.0, 10.0, 1.0 };
    g_JetpackParticle[client] = CreateParticle(g_JetpackTypeParticle[selected], 0.0, client, Attach, "flag", pos, ang);
}

ClearJetpackEffects(client)
{
    new selected = GetSelectedJetpackOfClient(client);
    StopSound(client, SNDCHAN_AUTO, g_JetpackTypeSound[selected]);
    DeleteParticle(g_JetpackParticle[client]);
}

//Menus
public DonatorMenu:JetpackBlingMenu(client) ChangeJetpackMenu;
ChangeJetpackMenu(client)
{
    new Handle:menu = CreateMenu(ChangeJetpackMenuHandler);
    new selected = GetSelectedJetpackOfClient(client);

    SetMenuTitle(menu, "Choose your jetpack");

    decl String:buf[16];
    for(new i=0; i < g_JetpackTypeCount && i < MAX_JETPACK_TYPES; i++)
    {
        IntToString(i, buf, sizeof(buf));
        AddMenuItem(menu,
                buf,
                g_JetpackTypeName[i],
                i == selected ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
    }

    DisplayMenu(menu, client, 20);
}

public ChangeJetpackMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
    switch (action)
    {
        case MenuAction_Select:
            {
                new client = param1;
                new String:info[32];
                GetMenuItem(menu, param2, info, sizeof(info));
                new type_of = StringToInt(info);

                //Clear running particle and sound to prevent overlap
                ClearJetpackEffects(client);

                SetSelectedJetpackOfClient(client, type_of);
            }
        case MenuAction_End: CloseHandle(menu);
    }
}


nomination log steamid

nomination log steamid

#include <mapchooser_redux>

#undef REQUIRE_PLUGIN
#include <store>
#include <shop>

#pragma newdecls required

ArrayList g_aMapList;
Handle g_hMapMenu;
int g_iMapFileSerial = -1;
bool g_bIncludeName = false;
bool g_pStore;
bool g_pShop;

#define MAPSTATUS_ENABLED (1<<0)
#define MAPSTATUS_DISABLED (1<<1)
#define MAPSTATUS_EXCLUDE_CURRENT (1<<2)
#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3)
#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4)

Handle g_aMapTrie;
Handle g_aNominated_Auth;
Handle g_aNominated_Name;

public Plugin myinfo =
{
    name        = "Nominations Redux",
    author      = "Kyle",
    description = "Provides Map Nominations",
    version     = MCR_VERSION,
    url         = "http://steamcommunity.com/id/_xQy_/"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Store_GetClientCredits");
    MarkNativeAsOptional("Store_SetClientCredits");

    MarkNativeAsOptional("MG_Shop_ClientEarnMoney");
    MarkNativeAsOptional("MG_Shop_ClientCostMoney");

    return APLRes_Success;
}

public void OnPluginStart()
{
    int arraySize = ByteCountToCells(256);
    g_aMapList = CreateArray(arraySize);

    g_aMapTrie = CreateTrie();
    g_aNominated_Auth = CreateTrie();
    g_aNominated_Name = CreateTrie();
}

public void OnLibraryAdded(const char[] name)
{
    if(strcmp(name, "store") == 0)
        g_pStore = true;
    else if(strcmp(name, "shop-core") == 0)
        g_pShop = true;
}

public void OnLibraryRemoved(const char[] name)
{
    if(strcmp(name, "store") == 0)
        g_pStore = false;
    else if(strcmp(name, "shop-core") == 0)
        g_pShop = false;
}

public void OnConfigsExecuted()
{
    g_pStore = LibraryExists("store");
    g_pShop = LibraryExists("shop-core");

    if(ReadMapList(g_aMapList, g_iMapFileSerial, "nominations", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) == INVALID_HANDLE)
        if(g_iMapFileSerial == -1)
            SetFailState("Unable to create a valid map list.");

    CreateTimer(90.0, Timer_Broadcast, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
    g_bIncludeName = (FindPluginByFile("zombiereloaded.smx") != INVALID_HANDLE);
}

public void OnMapEnd()
{
    char map[128];
    GetCurrentMap(map, 128);
    RemoveFromTrie(g_aNominated_Auth, map);
    RemoveFromTrie(g_aNominated_Name, map);

    if(g_hKvMapData != null)
        CloseHandle(g_hKvMapData);
    g_hKvMapData = null;
}

public void OnNominationRemoved(const char[] map, int owner)
{
    int status;

    if(!GetTrieValue(g_aMapTrie, map, status))
        return;

    if((status & MAPSTATUS_EXCLUDE_NOMINATED) != MAPSTATUS_EXCLUDE_NOMINATED)
        return;

    SetTrieValue(g_aMapTrie, map, MAPSTATUS_ENABLED);
}

public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs)
{
    if(!client)
        return;

    if(StrContains(sArgs, "nominat", false) == -1 && strcmp(sArgs, "nextmap", false) != 0)
        return;

    if(!IsNominateAllowed(client))
        return;

    AttemptNominate(client);
}

void AttemptNominate(int client)
{
    SetMenuTitle(g_hMapMenu, "预定地图\n ", client);
    DisplayMenu(g_hMapMenu, client, MENU_TIME_FOREVER);
}

void BuildMapMenu()
{
    if(g_hMapMenu != INVALID_HANDLE)
    {
        CloseHandle(g_hMapMenu);
        g_hMapMenu = INVALID_HANDLE;
    }

    ClearTrie(g_aMapTrie);

    g_hMapMenu = CreateMenu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem);

    char map[128];

    ArrayList excludeMaps = CreateArray(ByteCountToCells(128));
    GetExcludeMapList(excludeMaps);

    char currentMap[32];
    GetCurrentMap(currentMap, 32);

    for(int i = 0; i < GetArraySize(g_aMapList); i++)
    {
        int status = MAPSTATUS_ENABLED;

        GetArrayString(g_aMapList, i, map, 128);

        if(StrEqual(map, currentMap))
            status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_CURRENT;

        if(status == MAPSTATUS_ENABLED)
            if(FindStringInArray(excludeMaps, map) != -1)
            status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS;

        char szTrans[256];
        if(GetMapDesc(map, szTrans, 256, true, g_bIncludeName))
            AddMenuItem(g_hMapMenu, map, szTrans);
        else
            AddMenuItem(g_hMapMenu, map, map);

        SetTrieValue(g_aMapTrie, map, status);
    }

    SetMenuExitButton(g_hMapMenu, true);

    if(excludeMaps != INVALID_HANDLE)
        CloseHandle(excludeMaps);
}

public int Handler_MapSelectMenu(Handle menu, MenuAction action, int param1, int param2)
{
    switch (action)
    {
        case MenuAction_Select:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            NominateResult result = NominateMap(map, false, param1);

            if(result == NominateResult_NoCredits)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x04你的余额不足,预定[\x0C%s\x04]失败", map);
                return 0;
            }

            if(result == NominateResult_InvalidMap)
            {
                PrintToChat(param1, "[\x04MCR\x01]  预定[\x04%s\x01]失败", map);
                return 0;
            }

            if(result == NominateResult_AlreadyInVote)
            {
                PrintToChat(param1, "[\x04MCR\x01]  该地图已被预定");
                return 0;
            }

            if(result == NominateResult_VoteFull)
            {
                PrintToChat(param1, "[\x04MCR\x01]  投票池已满");
                return 0;
            }

            if(result == NominateResult_OnlyAdmin)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图只有管理员才能直接更换");
                return 0;
            }

            if(result == NominateResult_OnlyVIP)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图只有VIP才能预定");
                return 0;
            }

            if(result == NominateResult_MinPlayers)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图需要当前服务器人数大于\x04%d人\x07才能预定", GetMinPlayers(map));
                return 0;
            }

            if(result == NominateResult_MaxPlayers)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图需要当前服务器人数小于\x04%d人\x07才能预定", GetMaxPlayers(map));
                return 0;
            }

            SetTrieValue(g_aMapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);

            if(g_pStore)
            {
                int credits = GetMapPrice(map);
                Store_SetClientCredits(param1, Store_GetClientCredits(param1)-credits, "nomination-预定");
                PrintToChat(param1, "[\x04MCR\x01]  \x04你预定[\x0C%s\x04]花费了%d信用点", map, credits);
            }
            else if(g_pShop)
            {
                int credits = GetMapPrice(map);
                MG_Shop_ClientCostMoney(param1, credits, "nomination-预定");
                PrintToChat(param1, "[\x04MCR\x01]  \x04你预定[\x0C%s\x04]花费了%dG", map, credits);
            }

            char m_szAuth[32], m_szName[32];
            GetClientAuthId(param1, AuthId_Steam2, m_szAuth, 32, true);
            GetClientName(param1, m_szName, 32)
            SetTrieString(g_aNominated_Auth, map, m_szAuth, true);
            SetTrieString(g_aNominated_Name, map, m_szName, true);

            LogMessage("[MCR]  \"%L\" nominated %s", param1, map);

            if(result == NominateResult_Replaced)
                PrintToChatAll("[\x04MCR\x01]  \x0C%N\x01更改预定地图为[\x05%s\x01]", param1, map);
            else
                PrintToChatAll("[\x04MCR\x01]  \x0C%N\x01预定了地图[\x05%s\x01]", param1, map);
        }

        case MenuAction_DrawItem:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            int status;

            if(!GetTrieValue(g_aMapTrie, map, status))
            {
                LogError("case MenuAction_DrawItem: Menu selection of item not in trie. Major logic problem somewhere.");
                return ITEMDRAW_DEFAULT;
            }

            if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
                return ITEMDRAW_DISABLED;

            return ITEMDRAW_DEFAULT;
        }

        case MenuAction_DisplayItem:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            int status;

            if(!GetTrieValue(g_aMapTrie, map, status))
            {
                LogError("case MenuAction_DisplayItem: Menu selection of item not in trie. Major logic problem somewhere.");
                return 0;
            }

            char buffer[100];
            char display[150];
            char trans[128];
            strcopy(buffer, 100, map);
            GetMapDesc(map, trans, 128, false, false);

            if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
            {
                if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
                {
                    Format(display, sizeof(display), "%s (当前地图)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }

                if((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS)
                {
                    Format(display, sizeof(display), "%s (最近玩过)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }

                if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED)
                {
                    Format(display, sizeof(display), "%s (已被预定)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }
            }

            return 0;
        }
    }

    return 0;
}

stock bool IsNominateAllowed(int client)
{
    CanNominateResult result = CanNominate();

    switch(result)
    {
        case CanNominate_No_VoteInProgress:
        {
            PrintToChat(client, "[\x04MCR\x01]  下幅地图投票已开始.");
            return false;
        }

        case CanNominate_No_VoteComplete:
        {
            char map[128];
            GetNextMap(map, 128);
            PrintToChat(client, "[\x04MCR\x01]  已投票出下一幅地图[\x05%s\x01]", map);
            return false;
        }

        case CanNominate_No_VoteFull:
        {
            PrintToChat(client, "[\x04MCR\x01]  投票池已满");
            return false;
        }
    }

    return true;
}

public void OnMapDataLoaded()
{
    if(g_hKvMapData != null)
        CloseHandle(g_hKvMapData);

    g_hKvMapData = CreateKeyValues("MapData", "", "");
    FileToKeyValues(g_hKvMapData, "addons/sourcemod/configs/mapdata.txt");
    KvRewind(g_hKvMapData);

    BuildMapMenu();
}

public Action Timer_Broadcast(Handle timer)
{
    char map[128];
    GetCurrentMap(map, 128);

    char m_szAuth[32];
    if(!GetTrieString(g_aNominated_Auth, map, m_szAuth, 32))
        return Plugin_Stop;

    char m_szName[32];
    if(!GetTrieString(g_aNominated_Name, map, m_szName, 32))
        return Plugin_Stop;

    int client = FindClientByAuth(m_szAuth);

    ReplaceString(m_szAuth, 32, "STEAM_1:", "");

    if(!client)
        PrintToChatAll("[\x04MCR\x01]   当前地图是\x0C%s\x01(\x04%s\x01)预定的", m_szName, m_szAuth);
    else
        PrintToChatAll("[\x04MCR\x01]   当前地图是\x0C%N\x01(\x04%s\x01)预定的", client, m_szAuth);

    return Plugin_Continue;
}

int FindClientByAuth(const char[] steamid)
{
    char m_szAuth[32];
    for(int client = 1; client <= MaxClients; ++client)
        if(IsClientAuthorized(client))
            if(GetClientAuthId(client, AuthId_Steam2, m_szAuth, 32, true))
                if(StrEqual(m_szAuth, steamid))
                    return client;

    return 0;
}

#include <mapchooser_redux>

#undef REQUIRE_PLUGIN
#include <store>
#include <shop>

#pragma newdecls required

ArrayList g_aMapList;
Handle g_hMapMenu;
int g_iMapFileSerial = -1;
bool g_bIncludeName = false;
bool g_pStore;
bool g_pShop;

#define MAPSTATUS_ENABLED (1<<0)
#define MAPSTATUS_DISABLED (1<<1)
#define MAPSTATUS_EXCLUDE_CURRENT (1<<2)
#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3)
#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4)

Handle g_aMapTrie;
Handle g_aNominated_Auth;
Handle g_aNominated_Name;

public Plugin myinfo =
{
    name        = "Nominations Redux",
    author      = "Kyle",
    description = "Provides Map Nominations",
    version     = MCR_VERSION,
    url         = "http://steamcommunity.com/id/_xQy_/"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Store_GetClientCredits");
    MarkNativeAsOptional("Store_SetClientCredits");

    MarkNativeAsOptional("MG_Shop_ClientEarnMoney");
    MarkNativeAsOptional("MG_Shop_ClientCostMoney");

    return APLRes_Success;
}

public void OnPluginStart()
{
    int arraySize = ByteCountToCells(256);
    g_aMapList = CreateArray(arraySize);

    g_aMapTrie = CreateTrie();
    g_aNominated_Auth = CreateTrie();
    g_aNominated_Name = CreateTrie();
}

public void OnLibraryAdded(const char[] name)
{
    if(strcmp(name, "store") == 0)
        g_pStore = true;
    else if(strcmp(name, "shop-core") == 0)
        g_pShop = true;
}

public void OnLibraryRemoved(const char[] name)
{
    if(strcmp(name, "store") == 0)
        g_pStore = false;
    else if(strcmp(name, "shop-core") == 0)
        g_pShop = false;
}

public void OnConfigsExecuted()
{
    g_pStore = LibraryExists("store");
    g_pShop = LibraryExists("shop-core");

    if(ReadMapList(g_aMapList, g_iMapFileSerial, "nominations", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) == INVALID_HANDLE)
        if(g_iMapFileSerial == -1)
            SetFailState("Unable to create a valid map list.");

    CreateTimer(90.0, Timer_Broadcast, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
    g_bIncludeName = (FindPluginByFile("zombiereloaded.smx") != INVALID_HANDLE);
}

public void OnMapEnd()
{
    char map[128];
    GetCurrentMap(map, 128);
    RemoveFromTrie(g_aNominated_Auth, map);
    RemoveFromTrie(g_aNominated_Name, map);

    if(g_hKvMapData != null)
        CloseHandle(g_hKvMapData);
    g_hKvMapData = null;
}

public void OnNominationRemoved(const char[] map, int owner)
{
    int status;

    if(!GetTrieValue(g_aMapTrie, map, status))
        return;

    if((status & MAPSTATUS_EXCLUDE_NOMINATED) != MAPSTATUS_EXCLUDE_NOMINATED)
        return;

    SetTrieValue(g_aMapTrie, map, MAPSTATUS_ENABLED);
}

public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs)
{
    if(!client)
        return;

    if(StrContains(sArgs, "nominat", false) == -1 && strcmp(sArgs, "nextmap", false) != 0)
        return;

    if(!IsNominateAllowed(client))
        return;

    AttemptNominate(client);
}

void AttemptNominate(int client)
{
    SetMenuTitle(g_hMapMenu, "预定地图\n ", client);
    DisplayMenu(g_hMapMenu, client, MENU_TIME_FOREVER);
}

void BuildMapMenu()
{
    if(g_hMapMenu != INVALID_HANDLE)
    {
        CloseHandle(g_hMapMenu);
        g_hMapMenu = INVALID_HANDLE;
    }

    ClearTrie(g_aMapTrie);

    g_hMapMenu = CreateMenu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem);

    char map[128];

    ArrayList excludeMaps = CreateArray(ByteCountToCells(128));
    GetExcludeMapList(excludeMaps);

    char currentMap[32];
    GetCurrentMap(currentMap, 32);

    for(int i = 0; i < GetArraySize(g_aMapList); i++)
    {
        int status = MAPSTATUS_ENABLED;

        GetArrayString(g_aMapList, i, map, 128);

        if(StrEqual(map, currentMap))
            status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_CURRENT;

        if(status == MAPSTATUS_ENABLED)
            if(FindStringInArray(excludeMaps, map) != -1)
            status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS;

        char szTrans[256];
        if(GetMapDesc(map, szTrans, 256, true, g_bIncludeName))
            AddMenuItem(g_hMapMenu, map, szTrans);
        else
            AddMenuItem(g_hMapMenu, map, map);

        SetTrieValue(g_aMapTrie, map, status);
    }

    SetMenuExitButton(g_hMapMenu, true);

    if(excludeMaps != INVALID_HANDLE)
        CloseHandle(excludeMaps);
}

public int Handler_MapSelectMenu(Handle menu, MenuAction action, int param1, int param2)
{
    switch (action)
    {
        case MenuAction_Select:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            NominateResult result = NominateMap(map, false, param1);

            if(result == NominateResult_NoCredits)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x04你的余额不足,预定[\x0C%s\x04]失败", map);
                return 0;
            }

            if(result == NominateResult_InvalidMap)
            {
                PrintToChat(param1, "[\x04MCR\x01]  预定[\x04%s\x01]失败", map);
                return 0;
            }

            if(result == NominateResult_AlreadyInVote)
            {
                PrintToChat(param1, "[\x04MCR\x01]  该地图已被预定");
                return 0;
            }

            if(result == NominateResult_VoteFull)
            {
                PrintToChat(param1, "[\x04MCR\x01]  投票池已满");
                return 0;
            }

            if(result == NominateResult_OnlyAdmin)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图只有管理员才能直接更换");
                return 0;
            }

            if(result == NominateResult_OnlyVIP)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图只有VIP才能预定");
                return 0;
            }

            if(result == NominateResult_MinPlayers)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图需要当前服务器人数大于\x04%d人\x07才能预定", GetMinPlayers(map));
                return 0;
            }

            if(result == NominateResult_MaxPlayers)
            {
                PrintToChat(param1, "[\x04MCR\x01]  \x07该地图需要当前服务器人数小于\x04%d人\x07才能预定", GetMaxPlayers(map));
                return 0;
            }

            SetTrieValue(g_aMapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);

            if(g_pStore)
            {
                int credits = GetMapPrice(map);
                Store_SetClientCredits(param1, Store_GetClientCredits(param1)-credits, "nomination-预定");
                PrintToChat(param1, "[\x04MCR\x01]  \x04你预定[\x0C%s\x04]花费了%d信用点", map, credits);
            }
            else if(g_pShop)
            {
                int credits = GetMapPrice(map);
                MG_Shop_ClientCostMoney(param1, credits, "nomination-预定");
                PrintToChat(param1, "[\x04MCR\x01]  \x04你预定[\x0C%s\x04]花费了%dG", map, credits);
            }

            char m_szAuth[32], m_szName[32];
            GetClientAuthId(param1, AuthId_Steam2, m_szAuth, 32, true);
            GetClientName(param1, m_szName, 32)
            SetTrieString(g_aNominated_Auth, map, m_szAuth, true);
            SetTrieString(g_aNominated_Name, map, m_szName, true);

            LogMessage("%N nominated %s", param1, map);

            if(result == NominateResult_Replaced)
                PrintToChatAll("[\x04MCR\x01]  \x0C%N\x01更改预定地图为[\x05%s\x01]", param1, map);
            else
                PrintToChatAll("[\x04MCR\x01]  \x0C%N\x01预定了地图[\x05%s\x01]", param1, map);
        }

        case MenuAction_DrawItem:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            int status;

            if(!GetTrieValue(g_aMapTrie, map, status))
            {
                LogError("case MenuAction_DrawItem: Menu selection of item not in trie. Major logic problem somewhere.");
                return ITEMDRAW_DEFAULT;
            }

            if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
                return ITEMDRAW_DISABLED;

            return ITEMDRAW_DEFAULT;
        }

        case MenuAction_DisplayItem:
        {
            char map[128];
            GetMenuItem(menu, param2, map, 128);

            int status;

            if(!GetTrieValue(g_aMapTrie, map, status))
            {
                LogError("case MenuAction_DisplayItem: Menu selection of item not in trie. Major logic problem somewhere.");
                return 0;
            }

            char buffer[100];
            char display[150];
            char trans[128];
            strcopy(buffer, 100, map);
            GetMapDesc(map, trans, 128, false, false);

            if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
            {
                if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
                {
                    Format(display, sizeof(display), "%s (当前地图)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }

                if((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS)
                {
                    Format(display, sizeof(display), "%s (最近玩过)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }

                if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED)
                {
                    Format(display, sizeof(display), "%s (已被预定)\n%s", buffer, trans);
                    return RedrawMenuItem(display);
                }
            }

            return 0;
        }
    }

    return 0;
}

stock bool IsNominateAllowed(int client)
{
    CanNominateResult result = CanNominate();

    switch(result)
    {
        case CanNominate_No_VoteInProgress:
        {
            PrintToChat(client, "[\x04MCR\x01]  下幅地图投票已开始.");
            return false;
        }

        case CanNominate_No_VoteComplete:
        {
            char map[128];
            GetNextMap(map, 128);
            PrintToChat(client, "[\x04MCR\x01]  已投票出下一幅地图[\x05%s\x01]", map);
            return false;
        }

        case CanNominate_No_VoteFull:
        {
            PrintToChat(client, "[\x04MCR\x01]  投票池已满");
            return false;
        }
    }

    return true;
}

public void OnMapDataLoaded()
{
    if(g_hKvMapData != null)
        CloseHandle(g_hKvMapData);

    g_hKvMapData = CreateKeyValues("MapData", "", "");
    FileToKeyValues(g_hKvMapData, "addons/sourcemod/configs/mapdata.txt");
    KvRewind(g_hKvMapData);

    BuildMapMenu();
}

public Action Timer_Broadcast(Handle timer)
{
    char map[128];
    GetCurrentMap(map, 128);

    char m_szAuth[32];
    if(!GetTrieString(g_aNominated_Auth, map, m_szAuth, 32))
        return Plugin_Stop;

    char m_szName[32];
    if(!GetTrieString(g_aNominated_Name, map, m_szName, 32))
        return Plugin_Stop;

    int client = FindClientByAuth(m_szAuth);

    ReplaceString(m_szAuth, 32, "STEAM_1:", "");

    if(!client)
        PrintToChatAll("[\x04MCR\x01]   当前地图是\x0C%s\x01(\x04%s\x01)预定的", m_szName, m_szAuth);
    else
        PrintToChatAll("[\x04MCR\x01]   当前地图是\x0C%N\x01(\x04%s\x01)预定的", client, m_szAuth);

    return Plugin_Continue;
}

int FindClientByAuth(const char[] steamid)
{
    char m_szAuth[32];
    for(int client = 1; client <= MaxClients; ++client)
        if(IsClientAuthorized(client))
            if(GetClientAuthId(client, AuthId_Steam2, m_szAuth, 32, true))
                if(StrEqual(m_szAuth, steamid))
                    return client;

    return 0;
}