#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <adminmenu>
#include <cstrike>
#include <clientprefs>

#undef REQUIRE_PLUGIN
#include <ccc>
#include <zombiereloaded>
#tryinclude <voiceannounce_ex>
#include <AdvancedTargeting>
#define REQUIRE_PLUGIN

#undef REQUIRE_EXTENSIONS
#tryinclude <voice>
#define REQUIRE_EXTENSIONS

#pragma newdecls required

bool g_Plugin_ccc = false;
bool g_Plugin_zombiereloaded = false;
bool g_Plugin_voiceannounce_ex = false;
bool g_Plugin_AdvancedTargeting = false;
bool g_Extension_Voice = false;
bool g_bIsProtoBuf = false;

Handle g_hCookieTorchMuted = null;

#define PLUGIN_VERSION "2.4"

public Plugin myinfo =
{
	name 			= "SelfMute",
	author 			= "BotoX",
	description 	= "Ignore other players in text and voicechat.",
	version 		= PLUGIN_VERSION,
	url 			= ""
};

enum
{
	MUTE_NONE = 0,
	MUTE_SPEC = 1,
	MUTE_CT = 2,
	MUTE_T = 4,
	MUTE_DEAD = 8,
	MUTE_ALIVE = 16,
	MUTE_NOTFRIENDS = 32,
	MUTE_ALL = 64,
	MUTE_LAST = 64
};

bool g_Ignored[(MAXPLAYERS + 1) * (MAXPLAYERS + 1)];
bool g_Exempt[MAXPLAYERS + 1][MAXPLAYERS + 1];
int g_SpecialMutes[MAXPLAYERS + 1];

char g_PlayerNames[MAXPLAYERS+1][MAX_NAME_LENGTH];

public void OnPluginStart()
{
	LoadTranslations("common.phrases");

	CreateConVar("sm_selfmute_version", PLUGIN_VERSION, "Version of Self-Mute", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);

	RegConsoleCmd("sm_sm", Command_SelfMute, "Mute player by typing !sm [playername]");
	RegConsoleCmd("sm_su", Command_SelfUnMute, "Unmute player by typing !su [playername]");
	RegConsoleCmd("sm_cm", Command_CheckMutes, "Check who you have self-muted");
	RegAdminCmd("sm_debugtorch", Command_CheckPermaTorchMutes, ADMFLAG_GENERIC, "Check who has permanently self-muted Torch");

	HookEvent("round_start", Event_Round);
	HookEvent("round_end", Event_Round);
	HookEvent("player_team", Event_TeamChange);

	if(GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf)
		g_bIsProtoBuf = true;

	UserMsg RadioText = GetUserMessageId("RadioText");
	if(RadioText == INVALID_MESSAGE_ID)
		SetFailState("This game doesn't support RadioText user messages.");

	HookUserMessage(RadioText, Hook_UserMessageRadioText, true);

	UserMsg SendAudio = GetUserMessageId("SendAudio");
	if(SendAudio == INVALID_MESSAGE_ID)
		SetFailState("This game doesn't support SendAudio user messages.");

	HookUserMessage(SendAudio, Hook_UserMessageSendAudio, true);

	g_hCookieTorchMuted = RegClientCookie("torch_muted", "is torch muted", CookieAccess_Protected);
}

public void OnAllPluginsLoaded()
{
	g_Plugin_ccc = LibraryExists("ccc");
	g_Plugin_zombiereloaded = LibraryExists("zombiereloaded");
	g_Plugin_voiceannounce_ex = LibraryExists("voiceannounce_ex");
	g_Plugin_AdvancedTargeting = LibraryExists("AdvancedTargeting");
	g_Extension_Voice = LibraryExists("Voice");

	LogMessage("SelfMute capabilities:\nProtoBuf: %s\nCCC: %s\nZombieReloaded: %s\nVoiceAnnounce: %s\nAdvancedTargeting: %s\nVoice: %s",
		(g_bIsProtoBuf ? "yes" : "no"),
		(g_Plugin_ccc ? "loaded" : "not loaded"),
		(g_Plugin_zombiereloaded ? "loaded" : "not loaded"),
		(g_Plugin_voiceannounce_ex ? "loaded" : "not loaded"),
		(g_Plugin_AdvancedTargeting ? "loaded" : "not loaded"),
		(g_Extension_Voice ? "loaded" : "not loaded"));
}

void OnLibrary(const char[] name, bool added)
{
	if(StrEqual(name, "ccc"))
		g_Plugin_ccc = added;
	else if(StrEqual(name, "zombiereloaded"))
		g_Plugin_zombiereloaded = added;
	else if(StrEqual(name, "voiceannounce_ex"))
		g_Plugin_voiceannounce_ex = added;
	else if(StrEqual(name, "AdvancedTargeting"))
		g_Plugin_AdvancedTargeting = added;
	else if(StrEqual(name, "Voice"))
		g_Extension_Voice = added;

}

public void OnLibraryAdded(const char[] name) { OnLibrary(name, true); }
public void OnLibraryRemoved(const char[] name) { OnLibrary(name, false); }

public void OnClientPutInServer(int client)
{
	g_SpecialMutes[client] = MUTE_NONE;
	for(int i = 1; i < MAXPLAYERS; i++)
	{
		SetIgnored(client, i, false);
		SetExempt(client, i, false);

		SetIgnored(i, client, false);
		SetExempt(i, client, false);
	}

	UpdateSpecialMutesOtherClients(client);
	UpdateIgnored();

	GetCookiesForTorch(client);
}

public void OnClientDisconnect(int client)
{
	g_SpecialMutes[client] = MUTE_NONE;
	for(int i = 1; i < MAXPLAYERS; i++)
	{
		SetIgnored(client, i, false);
		SetExempt(client, i, false);

		SetIgnored(i, client, false);
		SetExempt(i, client, false);

		if(IsClientInGame(i) && i != client)
			SetListenOverride(i, client, Listen_Yes);
	}

	UpdateIgnored();
}

public void GetCookiesForTorch(int client)
{
	char sBuffer[2];

	GetClientCookie(client, g_hCookieTorchMuted, sBuffer, sizeof(sBuffer));
	if(sBuffer[0] != '\0')
	{
		for(int i = 1; i <= MaxClients; i++)
		{
			if(IsClientInGame(i) && IsClientSourceTV(i))
				Ignore(client, i);
		}
	}

	UpdateIgnored();
}

public void Event_Round(Handle event, const char[] name, bool dontBroadcast)
{
	for(int i = 1; i <= MaxClients; i++)
	{
		if(IsClientInGame(i))
			UpdateSpecialMutesThisClient(i);
	}
}

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

	UpdateSpecialMutesOtherClients(client);
}

public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn)
{
	UpdateSpecialMutesOtherClients(client);
}

public void ZR_OnClientHumanPost(int client, bool respawn, bool protect)
{
	UpdateSpecialMutesOtherClients(client);
}

/*
 * Mutes this client on other players
*/
void UpdateSpecialMutesOtherClients(int client)
{
	bool Alive = IsPlayerAlive(client);
	int Team = GetClientTeam(client);

	for(int i = 1; i <= MaxClients; i++)
	{
		if(i == client || !IsClientInGame(i))
			continue;

		int Flags = MUTE_NONE;

		if(g_SpecialMutes[i] & MUTE_SPEC && Team == CS_TEAM_SPECTATOR)
			Flags |= MUTE_SPEC;

		else if(g_SpecialMutes[i] & MUTE_CT && Alive &&
			((g_Plugin_zombiereloaded && ZR_IsClientHuman(client)) || (!g_Plugin_zombiereloaded && Team == CS_TEAM_CT)))
			Flags |= MUTE_CT;

		else if(g_SpecialMutes[i] & MUTE_T && Alive &&
			((g_Plugin_zombiereloaded && ZR_IsClientZombie(client)) || (!g_Plugin_zombiereloaded && Team == CS_TEAM_T)))
			Flags |= MUTE_T;

		else if(g_SpecialMutes[i] & MUTE_DEAD && !Alive)
			Flags |= MUTE_DEAD;

		else if(g_SpecialMutes[i] & MUTE_ALIVE && Alive)
			Flags |= MUTE_ALIVE;

		else if(g_SpecialMutes[i] & MUTE_NOTFRIENDS &&
			g_Plugin_AdvancedTargeting && IsClientFriend(i, client) == 0)
			Flags |= MUTE_NOTFRIENDS;

		else if(g_SpecialMutes[i] & MUTE_ALL)
			Flags |= MUTE_ALL;

		if(Flags && !GetExempt(i, client))
			SetListenOverride(i, client, Listen_No);
		else if(!GetIgnored(i, client))
			SetListenOverride(i, client, Listen_Yes);
	}
}

/*
 * Mutes other players on this client
*/
void UpdateSpecialMutesThisClient(int client)
{
	for(int i = 1; i <= MaxClients; i++)
	{
		if(i == client || !IsClientInGame(i))
			continue;

		bool Alive = IsPlayerAlive(i);
		int Team = GetClientTeam(i);

		int Flags = MUTE_NONE;

		if(g_SpecialMutes[client] & MUTE_SPEC && Team == CS_TEAM_SPECTATOR)
			Flags |= MUTE_SPEC;

		else if(g_SpecialMutes[client] & MUTE_CT && Alive &&
			((g_Plugin_zombiereloaded && ZR_IsClientHuman(i) || (!g_Plugin_zombiereloaded) && Team == CS_TEAM_CT)))
			Flags |= MUTE_CT;

		else if(g_SpecialMutes[client] & MUTE_T && Alive &&
			((g_Plugin_zombiereloaded && ZR_IsClientZombie(i) || (!g_Plugin_zombiereloaded) && Team == CS_TEAM_T)))
			Flags |= MUTE_T;

		else if(g_SpecialMutes[client] & MUTE_DEAD && !Alive)
			Flags |= MUTE_DEAD;

		else if(g_SpecialMutes[client] & MUTE_ALIVE && Alive)
			Flags |= MUTE_ALIVE;

		else if(g_SpecialMutes[client] & MUTE_NOTFRIENDS &&
			g_Plugin_AdvancedTargeting && IsClientFriend(client, i) == 0)
			Flags |= MUTE_NOTFRIENDS;

		else if(g_SpecialMutes[client] & MUTE_ALL)
			Flags |= MUTE_ALL;

		if(Flags && !GetExempt(client, i))
			SetListenOverride(client, i, Listen_No);
		else if(!GetIgnored(client, i))
			SetListenOverride(client, i, Listen_Yes);
	}
}

int GetSpecialMutesFlags(char[] Argument)
{
	int SpecialMute = MUTE_NONE;
	if(StrEqual(Argument, "@spec", false) || StrEqual(Argument, "@!ct", false) || StrEqual(Argument, "@!t", false))
		SpecialMute |= MUTE_SPEC;
	if(StrEqual(Argument, "@ct", false) || StrEqual(Argument, "@!t", false) || StrEqual(Argument, "@!spec", false))
		SpecialMute |= MUTE_CT;
	if(StrEqual(Argument, "@t", false) || StrEqual(Argument, "@!ct", false) || StrEqual(Argument, "@!spec", false))
		SpecialMute |= MUTE_T;
	if(StrEqual(Argument, "@dead", false) || StrEqual(Argument, "@!alive", false))
		SpecialMute |= MUTE_DEAD;
	if(StrEqual(Argument, "@alive", false) || StrEqual(Argument, "@!dead", false))
		SpecialMute |= MUTE_ALIVE;
	if(g_Plugin_AdvancedTargeting && StrEqual(Argument, "@!friends", false))
		SpecialMute |= MUTE_NOTFRIENDS;
	if(StrEqual(Argument, "@all", false))
		SpecialMute |= MUTE_ALL;

	return SpecialMute;
}

void FormatSpecialMutes(int SpecialMute, char[] aBuf, int BufLen)
{
	if(!SpecialMute)
	{
		StrCat(aBuf, BufLen, "none");
		return;
	}

	bool Status = false;
	if(SpecialMute & MUTE_ALL)
	{
		StrCat(aBuf, BufLen, "Everyone, ");
		Status = true;
	}
	if(SpecialMute & MUTE_SPEC)
	{
		StrCat(aBuf, BufLen, "Spectators, ");
		Status = true;
	}
	if(SpecialMute & MUTE_CT)
	{
		StrCat(aBuf, BufLen, "Counter-Terrorists, ");
		Status = true;
	}
	if(SpecialMute & MUTE_T)
	{
		StrCat(aBuf, BufLen, "Terrorists, ");
		Status = true;
	}
	if(SpecialMute & MUTE_DEAD)
	{
		StrCat(aBuf, BufLen, "Dead players, ");
		Status = true;
	}
	if(SpecialMute & MUTE_ALIVE)
	{
		StrCat(aBuf, BufLen, "Alive players, ");
		Status = true;
	}
	if(SpecialMute & MUTE_NOTFRIENDS)
	{
		StrCat(aBuf, BufLen, "Not Steam friends, ");
		Status = true;
	}

	// Cut off last ', '
	if(Status)
		aBuf[strlen(aBuf) - 2] = 0;
}

bool MuteSpecial(int client, char[] Argument)
{
	bool RetValue = false;
	int SpecialMute = GetSpecialMutesFlags(Argument);

	if(SpecialMute & MUTE_NOTFRIENDS && g_Plugin_AdvancedTargeting && ReadClientFriends(client) != 1)
	{
		PrintToChat(client, "\x04[Self-Mute]\x01 Could not read your friendslist, your profile must be set to public!");
		SpecialMute &= ~MUTE_NOTFRIENDS;
		RetValue = true;
	}

	if(SpecialMute)
	{
		if(SpecialMute & MUTE_ALL || g_SpecialMutes[client] & MUTE_ALL)
		{
			g_SpecialMutes[client] = MUTE_ALL;
			SpecialMute = MUTE_ALL;
		}
		else
			g_SpecialMutes[client] |= SpecialMute;

		UpdateSpecialMutesThisClient(client);

		char aBuf[128];
		FormatSpecialMutes(SpecialMute, aBuf, sizeof(aBuf));

		PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted group:\x04 %s", aBuf);
		RetValue = true;
	}

	return RetValue;
}

bool UnMuteSpecial(int client, char[] Argument)
{
	int SpecialMute = GetSpecialMutesFlags(Argument);

	if(SpecialMute)
	{
		if(SpecialMute & MUTE_ALL)
		{
			if(g_SpecialMutes[client])
			{
				SpecialMute = g_SpecialMutes[client];
				g_SpecialMutes[client] = MUTE_NONE;
			}
			else
			{
				for(int i = 1; i <= MaxClients; i++)
				{
					if(IsClientInGame(i) && !IsClientSourceTV(i))
						UnIgnore(client, i);

					PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 all players");
					return true;
				}
			}
		}
		else
			g_SpecialMutes[client] &= ~SpecialMute;

		UpdateSpecialMutesThisClient(client);

		char aBuf[256];
		FormatSpecialMutes(SpecialMute, aBuf, sizeof(aBuf));

		PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted group:\x04 %s", aBuf);
		return true;
	}

	return false;
}

void Ignore(int client, int target)
{
	SetIgnored(client, target, true);
	SetListenOverride(client, target, Listen_No);
}

void UnIgnore(int client, int target)
{
	SetIgnored(client, target, false);
	SetListenOverride(client, target, Listen_Yes);
}

void Exempt(int client, int target)
{
	SetExempt(client, target, true);
	UpdateSpecialMutesThisClient(client);
}

void UnExempt(int client, int target)
{
	SetExempt(client, target, false);
	UpdateSpecialMutesThisClient(client);
}

/*
 * CHAT COMMANDS
*/

public Action Command_CheckPermaTorchMutes(int client, int args)
{
	int iTorchPermMuted;
	int iPlayers;
	char sBuffer[2];

	for(int i = 1; i <= MaxClients; i++)
	{
		if(!IsClientInGame(i) || IsFakeClient(i))
			continue;

		GetClientCookie(i, g_hCookieTorchMuted, sBuffer, sizeof(sBuffer));

		if(sBuffer[0] != '\0')
			iTorchPermMuted++;

		iPlayers++;
	}

	ReplyToCommand(client, "[SM] There are currently %d out of %d Players who've got Torch permanently self-muted.", iTorchPermMuted, iPlayers);

	return Plugin_Handled;
}

public Action Command_SelfMute(int client, int args)
{
	if(client == 0)
	{
		PrintToServer("[SM] Cannot use command from server console.");
		return Plugin_Handled;
	}

	if(args < 1)
	{
		DisplayMuteMenu(client);
		return Plugin_Handled;
	}

	char Argument[65];
	GetCmdArg(1, Argument, sizeof(Argument));

	char Filtered[65];
	strcopy(Filtered, sizeof(Filtered), Argument);
	StripQuotes(Filtered);
	TrimString(Filtered);

	if(MuteSpecial(client, Filtered))
		return Plugin_Handled;

	char sTargetName[MAX_TARGET_LENGTH];
	int aTargetList[MAXPLAYERS];
	int TargetCount;
	bool TnIsMl;

	if((TargetCount = ProcessTargetString(
			Argument,
			client,
			aTargetList,
			MAXPLAYERS,
			COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_IMMUNITY,
			sTargetName,
			sizeof(sTargetName),
			TnIsMl)) <= 0)
	{
		ReplyToTargetError(client, TargetCount);
		return Plugin_Handled;
	}

	if(TargetCount == 1)
	{
		if(aTargetList[0] == client)
		{
			PrintToChat(client, "\x04[Self-Mute]\x01 You can't mute yourself, don't be silly.");
			return Plugin_Handled;
		}

		if(IsClientSourceTV(aTargetList[0]))
		{
			Ignore(client, aTargetList[0]);
			SetClientCookie(client, g_hCookieTorchMuted, "1");
			PrintToChat(client, "\x04[Self-Mute]\x01 You have permanently self-muted:\x04 %s", sTargetName);
			return Plugin_Handled;
		}

		if(GetExempt(client, aTargetList[0]))
		{
			UnExempt(client, aTargetList[0]);

			PrintToChat(client, "\x04[Self-Mute]\x01 You have removed exempt from self-mute:\x04 %s", sTargetName);

			return Plugin_Handled;
		}
	}

	for(int i = 0; i < TargetCount; i++)
	{
		if(aTargetList[i] == client)
			continue;

		Ignore(client, aTargetList[i]);
	}
	UpdateIgnored();

	PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted:\x04 %s", sTargetName);

	return Plugin_Handled;
}

public Action Command_SelfUnMute(int client, int args)
{
	if(client == 0)
	{
		PrintToServer("[SM] Cannot use command from server console.");
		return Plugin_Handled;
	}

	if(args < 1)
	{
		DisplayUnMuteMenu(client);
		return Plugin_Handled;
	}

	char Argument[65];
	GetCmdArg(1, Argument, sizeof(Argument));

	char Filtered[65];
	strcopy(Filtered, sizeof(Filtered), Argument);
	StripQuotes(Filtered);
	TrimString(Filtered);

	if(UnMuteSpecial(client, Filtered))
		return Plugin_Handled;

	char sTargetName[MAX_TARGET_LENGTH];
	int aTargetList[MAXPLAYERS];
	int TargetCount;
	bool TnIsMl;

	if((TargetCount = ProcessTargetString(
			Argument,
			client,
			aTargetList,
			MAXPLAYERS,
			COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_IMMUNITY,
			sTargetName,
			sizeof(sTargetName),
			TnIsMl)) <= 0)
	{
		ReplyToTargetError(client, TargetCount);
		return Plugin_Handled;
	}

	if(TargetCount == 1)
	{
		if(aTargetList[0] == client)
		{
			PrintToChat(client, "\x04[Self-Mute]\x01 Unmuting won't work either.");
			return Plugin_Handled;
		}

		if(IsClientSourceTV(aTargetList[0]))
		{
			UnIgnore(client, aTargetList[0]);
			SetClientCookie(client, g_hCookieTorchMuted, "");
			PrintToChat(client, "\x04[Self-Mute]\x01 You have permanently self-unmuted:\x04 %s", sTargetName);
			return Plugin_Handled;
		}

		if(!GetIgnored(client, aTargetList[0]))
		{
			Exempt(client, aTargetList[0]);

			PrintToChat(client, "\x04[Self-Mute]\x01 You have exempted from self-mute:\x04 %s", sTargetName);

			return Plugin_Handled;
		}
	}

	for(int i = 0; i < TargetCount; i++)
	{
		if(aTargetList[i] == client)
			continue;

		UnIgnore(client, aTargetList[i]);
	}
	UpdateIgnored();

	PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 %s", sTargetName);

	return Plugin_Handled;
}

public Action Command_CheckMutes(int client, int args)
{
	if(client == 0)
	{
		PrintToServer("[SM] Cannot use command from server console.");
		return Plugin_Handled;
	}

	char aMuted[1024];
	char aExempted[1024];
	char aName[MAX_NAME_LENGTH];
	for(int i = 1; i <= MaxClients; i++)
	{
		if(!IsClientInGame(i))
			continue;

		GetClientName(i, aName, sizeof(aName));

		if(GetIgnored(client, i))
		{
			StrCat(aMuted, sizeof(aMuted), aName);
			StrCat(aMuted, sizeof(aMuted), ", ");
		}

		if(GetExempt(client, i))
		{
			StrCat(aExempted, sizeof(aExempted), aName);
			StrCat(aExempted, sizeof(aExempted), ", ");
		}
	}

	if(strlen(aMuted))
	{
		aMuted[strlen(aMuted) - 2] = 0;
		PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted:\x04 %s", aMuted);
	}

	if(g_SpecialMutes[client] != MUTE_NONE)
	{
		aMuted[0] = 0;
		FormatSpecialMutes(g_SpecialMutes[client], aMuted, sizeof(aMuted));
		PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted group:\x04 %s", aMuted);
	}
	else if(!strlen(aMuted) && !strlen(aExempted))
		PrintToChat(client, "\x04[Self-Mute]\x01 You have not self-muted anyone!");

	if(strlen(aExempted))
	{
		aExempted[strlen(aExempted) - 2] = 0;
		PrintToChat(client, "\x04[Self-Mute]\x01 You have exempted from self-mute:\x04 %s", aExempted);
	}

	return Plugin_Handled;
}

stock bool _IsClientSpeaking(int client)
{
	#if defined _voiceannounceex_included_
	if(g_Plugin_voiceannounce_ex)
		return IsClientSpeaking(client);
	#endif

	#if defined _voice_included
	if(g_Extension_Voice)
		return IsClientTalking(client);
	#endif

	return false;
}

/*
 * MENU
*/
void DisplayMuteMenu(int client)
{
	Menu menu = new Menu(MenuHandler_MuteMenu, MenuAction_Select|MenuAction_Cancel|MenuAction_End|MenuAction_DrawItem|MenuAction_DisplayItem);
	menu.ExitButton = true;

	int[] aClients = new int[MaxClients + 1];

	{
		// Count talking players and insert id's into aClients array
		int CurrentlyTalking = 0;
		for(int i = 1; i <= MaxClients; i++)
		{
			if(i != client && IsClientInGame(i) && _IsClientSpeaking(i))
				aClients[CurrentlyTalking++] = i;
		}

		if(CurrentlyTalking > 0)
		{
			// insert player names into g_PlayerNames array
			for(int i = 0; i < CurrentlyTalking; i++)
				GetClientName(aClients[i], g_PlayerNames[aClients[i]], sizeof(g_PlayerNames[]));

			// sort aClients array by player name
			SortCustom1D(aClients, CurrentlyTalking, SortByPlayerName);

			// insert players sorted
			char aBuf[12];
			for(int i = 0; i < CurrentlyTalking; i++)
			{
				IntToString(GetClientUserId(aClients[i]), aBuf, sizeof(aBuf));
				menu.AddItem(aBuf, g_PlayerNames[aClients[i]]);
			}

			// insert spacers
			int Entries = 7 - CurrentlyTalking % 7;
			while(Entries--)
				menu.AddItem("", "", ITEMDRAW_RAWLINE);
		}
	}

	menu.AddItem("@all", "Everyone");
	menu.AddItem("@spec", "Spectators");
	menu.AddItem("@ct", "Counter-Terrorists");
	menu.AddItem("@t", "Terrorists");
	menu.AddItem("@dead", "Dead players");
	menu.AddItem("@alive", "Alive players");
	if(g_Plugin_AdvancedTargeting)
		menu.AddItem("@!friends", "Not Steam friends");
	else
		menu.AddItem("", "", ITEMDRAW_RAWLINE);

	// Count valid players and insert id's into aClients array
	int Players = 0;
	for(int i = 1; i <= MaxClients; i++)
	{
		if(i != client && IsClientInGame(i))
			aClients[Players++] = i;
	}

	// insert player names into g_PlayerNames array
	for(int i = 0; i < Players; i++)
		GetClientName(aClients[i], g_PlayerNames[aClients[i]], sizeof(g_PlayerNames[]));

	// sort aClients array by player name
	SortCustom1D(aClients, Players, SortByPlayerName);

	// insert players sorted
	char aBuf[12];
	for(int i = 0; i < Players; i++)
	{
		IntToString(GetClientUserId(aClients[i]), aBuf, sizeof(aBuf));
		menu.AddItem(aBuf, g_PlayerNames[aClients[i]]);
	}

	menu.Display(client, MENU_TIME_FOREVER);
}

public int MenuHandler_MuteMenu(Menu menu, MenuAction action, int param1, int param2)
{
	switch(action)
	{
		case MenuAction_End:
		{
			if(param1 != MenuEnd_Selected)
				CloseHandle(menu);
		}
		case MenuAction_Select:
		{
			int Style;
			char aItem[32];
			char aDisp[MAX_NAME_LENGTH + 4];
			menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp));

			if(Style != ITEMDRAW_DEFAULT || !aItem[0])
			{
				PrintToChat(param1, "Internal error: aItem[0] -> %d | Style -> %d", aItem[0], Style);
				return 0;
			}

			if(aItem[0] == '@')
			{
				int Flag = GetSpecialMutesFlags(aItem);
				if(Flag && g_SpecialMutes[param1] & Flag)
					UnMuteSpecial(param1, aItem);
				else
					MuteSpecial(param1, aItem);

				menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
				return 0;
			}

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client)
			{
				PrintToChat(param1, "\x04[Self-Mute]\x01 Player no longer available.");
				menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
				return 0;
			}

			if(GetIgnored(param1, client))
			{
				if(IsClientSourceTV(client))
				{
					UnIgnore(param1, client);
					SetClientCookie(param1, g_hCookieTorchMuted, "");
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have permanently self-unmuted:\x04 %N", client);
				}
				else
				{
					UnIgnore(param1, client);
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 %N", client);
				}
			}
			else if(GetExempt(param1, client))
			{
				UnExempt(param1, client);
				PrintToChat(param1, "\x04[Self-Mute]\x01 You have removed exempt from self-mute:\x04 %N", client);
			}
			else
			{
				if(IsClientSourceTV(client))
				{
					Ignore(param1, client);
					SetClientCookie(param1, g_hCookieTorchMuted, "1");
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have permanently self-muted:\x04 %N", client);
				}
				else
				{
					Ignore(param1, client);
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have self-muted:\x04 %N", client);
				}
			}
			menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
			return 0;
		}
		case MenuAction_DrawItem:
		{
			int Style;
			char aItem[32];
			menu.GetItem(param2, aItem, sizeof(aItem), Style);

			if(!aItem[0])
				return ITEMDRAW_DISABLED;

			if(aItem[0] == '@')
			{
				int Flag = GetSpecialMutesFlags(aItem);
				if(Flag & MUTE_ALL)
					return Style;
				else if(g_SpecialMutes[param1] & MUTE_ALL)
					return ITEMDRAW_DISABLED;

				return Style;
			}

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client) // Player disconnected
				return ITEMDRAW_DISABLED;

			return Style;
		}
		case MenuAction_DisplayItem:
		{
			int Style;
			char aItem[32];
			char aDisp[MAX_NAME_LENGTH + 4];
			menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp));

			// Start of current page
			if((param2 + 1) % 7 == 1)
			{
				if(aItem[0] == '@')
					menu.SetTitle("[Self-Mute] Groups");
				else if(param2 == 0)
					menu.SetTitle("[Self-Mute] Talking players");
				else
					menu.SetTitle("[Self-Mute] All players");
			}

			if(!aItem[0])
				return 0;

			if(aItem[0] == '@')
			{
				int Flag = GetSpecialMutesFlags(aItem);
				if(Flag && g_SpecialMutes[param1] & Flag)
				{
					char aBuf[32] = "[M] ";
					FormatSpecialMutes(Flag, aBuf, sizeof(aBuf));
					if(!StrEqual(aDisp, aBuf))
						return RedrawMenuItem(aBuf);
				}

				return 0;
			}

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client) // Player disconnected
			{
				char aBuf[MAX_NAME_LENGTH + 4] = "[D] ";
				StrCat(aBuf, sizeof(aBuf), aDisp);
				if(!StrEqual(aDisp, aBuf))
					return RedrawMenuItem(aBuf);
			}

			if(GetIgnored(param1, client))
			{
				char aBuf[MAX_NAME_LENGTH + 4] = "[M] ";
				GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[]));
				StrCat(aBuf, sizeof(aBuf), g_PlayerNames[client]);
				if(!StrEqual(aDisp, aBuf))
					return RedrawMenuItem(aBuf);
			}
			else if(GetExempt(param1, client))
			{
				char aBuf[MAX_NAME_LENGTH + 4] = "[E] ";
				GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[]));
				StrCat(aBuf, sizeof(aBuf), g_PlayerNames[client]);
				if(!StrEqual(aDisp, aBuf))
					return RedrawMenuItem(aBuf);
			}
			else
			{
				GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[]));
				if(!StrEqual(aDisp, g_PlayerNames[client]))
					return RedrawMenuItem(g_PlayerNames[client]);
			}

			return 0;
		}
	}

	return 0;
}

void DisplayUnMuteMenu(int client)
{
	Menu menu = new Menu(MenuHandler_UnMuteMenu, MenuAction_Select|MenuAction_Cancel|MenuAction_End|MenuAction_DrawItem|MenuAction_DisplayItem);
	menu.SetTitle("[Self-UnMute]");
	menu.ExitButton = true;

	if(g_SpecialMutes[client] & MUTE_ALL)
		menu.AddItem("@all", "Everyone");
	if(g_SpecialMutes[client] & MUTE_SPEC)
		menu.AddItem("@spec", "Spectators");
	if(g_SpecialMutes[client] & MUTE_CT)
		menu.AddItem("@ct", "Counter-Terrorists");
	if(g_SpecialMutes[client] & MUTE_T)
		menu.AddItem("@t", "Terrorists");
	if(g_SpecialMutes[client] & MUTE_DEAD)
		menu.AddItem("@dead", "Dead players");
	if(g_SpecialMutes[client] & MUTE_ALIVE)
		menu.AddItem("@alive", "Alive players");
	if(g_SpecialMutes[client] & MUTE_NOTFRIENDS)
		menu.AddItem("@!friends", "Not Steam friends");
	if(g_SpecialMutes[client])
		menu.AddItem("", "", ITEMDRAW_RAWLINE);

	int[] aClients = new int[MaxClients + 1];

	// Count valid players and insert id's into aClients array
	int Players = 0;
	for(int i = 1; i <= MaxClients; i++)
	{
		if(i != client && IsClientInGame(i) && (GetIgnored(client, i) || GetExempt(client, i)))
			aClients[Players++] = i;
	}

	// insert player names into g_PlayerNames array
	for(int i = 0; i < Players; i++)
	{
		GetClientName(aClients[i], g_PlayerNames[aClients[i]], sizeof(g_PlayerNames[]));
	}

	// sort aClients array by player name
	SortCustom1D(aClients, Players, SortByPlayerName);

	// insert players sorted
	char aBuf[12];
	for(int i = 0; i < Players; i++)
	{
		IntToString(GetClientUserId(aClients[i]), aBuf, sizeof(aBuf));
		menu.AddItem(aBuf, g_PlayerNames[aClients[i]]);
	}

	if(!menu.ItemCount)
	{
		delete menu;
		PrintToChat(client, "\x04[Self-Mute]\x01 You haven't muted or exempted anyone.");
		return;
	}

	menu.Display(client, MENU_TIME_FOREVER);
}


public int MenuHandler_UnMuteMenu(Menu menu, MenuAction action, int param1, int param2)
{
	switch(action)
	{
		case MenuAction_End:
		{
			if(param1 != MenuEnd_Selected)
				CloseHandle(menu);
		}
		case MenuAction_Select:
		{
			int Style;
			char aItem[32];
			char aDisp[MAX_NAME_LENGTH + 4];
			menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp));

			if(Style != ITEMDRAW_DEFAULT || !aItem[0])
			{
				PrintToChat(param1, "Internal error: aItem[0] -> %d | Style -> %d", aItem[0], Style);
				return 0;
			}

			if(aItem[0] == '@')
			{
				int Flag = GetSpecialMutesFlags(aItem);
				if(Flag && g_SpecialMutes[param1] & Flag)
					UnMuteSpecial(param1, aItem);

				menu.RemoveItem(GetMenuSelectionPosition());
				menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
				return 0;
			}

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client)
			{
				PrintToChat(param1, "\x04[Self-Mute]\x01 Player no longer available.");
				menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
				return 0;
			}

			if(GetIgnored(param1, client))
			{
				if(IsClientSourceTV(client))
				{
					UnIgnore(param1, client);
					SetClientCookie(param1, g_hCookieTorchMuted, "");
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have permanently self-unmuted:\x04 %N", client);
				}
				else
				{
					UnIgnore(param1, client);
					PrintToChat(param1, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 %N", client);
				}
			}
			else if(GetExempt(param1, client))
			{
				UnExempt(param1, client);
				PrintToChat(param1, "\x04[Self-Mute]\x01 You have removed exempt from self-mute:\x04 %N", client);
			}

			menu.RemoveItem(GetMenuSelectionPosition());
			menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
			return 0;
		}
		case MenuAction_DrawItem:
		{
			int Style;
			char aItem[32];
			menu.GetItem(param2, aItem, sizeof(aItem), Style);

			if(!aItem[0])
				return ITEMDRAW_DISABLED;

			if(aItem[0] == '@')
				return Style;

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client) // Player disconnected
				return ITEMDRAW_DISABLED;

			return Style;
		}
		case MenuAction_DisplayItem:
		{
			int Style;
			char aItem[32];
			char aDisp[MAX_NAME_LENGTH + 4];
			menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp));

			if(!aItem[0])
				return 0;

			int UserId = StringToInt(aItem);
			int client = GetClientOfUserId(UserId);
			if(!client) // Player disconnected
			{
				char aBuf[MAX_NAME_LENGTH + 4] = "[D] ";
				StrCat(aBuf, sizeof(aBuf), aDisp);
				if(!StrEqual(aDisp, aBuf))
					return RedrawMenuItem(aBuf);
			}

			if(GetExempt(param1, client))
			{
				char aBuf[MAX_NAME_LENGTH + 4] = "[E] ";
				GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[]));
				StrCat(aBuf, sizeof(aBuf), g_PlayerNames[client]);
				if(!StrEqual(aDisp, aBuf))
					return RedrawMenuItem(aBuf);
			}

			return 0;
		}
	}

	return 0;
}

/*
 * HOOKS
*/
int g_MsgDest;
int g_MsgClient;
char g_MsgName[256];
char g_MsgParam1[256];
char g_MsgParam2[256];
char g_MsgParam3[256];
char g_MsgParam4[256];
char g_MsgRadioSound[256];
int g_MsgPlayersNum;
int g_MsgPlayers[MAXPLAYERS + 1];

public Action Hook_UserMessageRadioText(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init)
{
	if(g_bIsProtoBuf)
	{
		g_MsgDest = PbReadInt(bf, "msg_dst");
		g_MsgClient = PbReadInt(bf, "client");
		PbReadString(bf, "msg_name", g_MsgName, sizeof(g_MsgName));
		PbReadString(bf, "params", g_MsgParam1, sizeof(g_MsgParam1), 0);
		PbReadString(bf, "params", g_MsgParam2, sizeof(g_MsgParam2), 1);
		PbReadString(bf, "params", g_MsgParam3, sizeof(g_MsgParam3), 2);
		PbReadString(bf, "params", g_MsgParam4, sizeof(g_MsgParam4), 3);
	}
	else
	{
		g_MsgDest = BfReadByte(bf);
		g_MsgClient = BfReadByte(bf);
		BfReadString(bf, g_MsgName, sizeof(g_MsgName), false);
		BfReadString(bf, g_MsgParam1, sizeof(g_MsgParam1), false);
		BfReadString(bf, g_MsgParam2, sizeof(g_MsgParam2), false);
		BfReadString(bf, g_MsgParam3, sizeof(g_MsgParam3), false);
		BfReadString(bf, g_MsgParam4, sizeof(g_MsgParam4), false);
	}

	// Check which clients need to be excluded.
	g_MsgPlayersNum = 0;
	for(int i = 0; i < playersNum; i++)
	{
		int client = players[i];
		if(!GetIgnored(client, g_MsgClient))
			g_MsgPlayers[g_MsgPlayersNum++] = client;
	}

	// No clients were excluded.
	if(g_MsgPlayersNum == playersNum)
	{
		g_MsgClient = -1;
		return Plugin_Continue;
	}
	else if(g_MsgPlayersNum == 0) // All clients were excluded and there is no need to broadcast.
	{
		g_MsgClient = -2;
		return Plugin_Handled;
	}

	return Plugin_Handled;
}

public Action Hook_UserMessageSendAudio(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init)
{
	if(g_MsgClient == -1)
		return Plugin_Continue;
	else if(g_MsgClient == -2)
		return Plugin_Handled;

	if(g_bIsProtoBuf)
		PbReadString(bf, "radio_sound", g_MsgRadioSound, sizeof(g_MsgRadioSound));
	else
		BfReadString(bf, g_MsgRadioSound, sizeof(g_MsgRadioSound), false);

	if(StrEqual(g_MsgRadioSound, "radio.locknload"))
		return Plugin_Continue;

	DataPack pack = new DataPack();
	pack.WriteCell(g_MsgDest);
	pack.WriteCell(g_MsgClient);
	pack.WriteString(g_MsgName);
	pack.WriteString(g_MsgParam1);
	pack.WriteString(g_MsgParam2);
	pack.WriteString(g_MsgParam3);
	pack.WriteString(g_MsgParam4);
	pack.WriteString(g_MsgRadioSound);
	pack.WriteCell(g_MsgPlayersNum);

	for(int i = 0; i < g_MsgPlayersNum; i++)
		pack.WriteCell(g_MsgPlayers[i]);

	RequestFrame(OnPlayerRadio, pack);

	return Plugin_Handled;
}

public void OnPlayerRadio(DataPack pack)
{
	pack.Reset();
	g_MsgDest = pack.ReadCell();
	g_MsgClient = pack.ReadCell();
	pack.ReadString(g_MsgName, sizeof(g_MsgName));
	pack.ReadString(g_MsgParam1, sizeof(g_MsgParam1));
	pack.ReadString(g_MsgParam2, sizeof(g_MsgParam2));
	pack.ReadString(g_MsgParam3, sizeof(g_MsgParam3));
	pack.ReadString(g_MsgParam4, sizeof(g_MsgParam4));
	pack.ReadString(g_MsgRadioSound, sizeof(g_MsgRadioSound));
	g_MsgPlayersNum = pack.ReadCell();

	int playersNum = 0;
	for(int i = 0; i < g_MsgPlayersNum; i++)
	{
		int client_ = pack.ReadCell();
		if(IsClientInGame(client_))
			g_MsgPlayers[playersNum++] = client_;
	}
	CloseHandle(pack);

	Handle RadioText = StartMessage("RadioText", g_MsgPlayers, playersNum, USERMSG_RELIABLE);
	if(g_bIsProtoBuf)
	{
		PbSetInt(RadioText, "msg_dst", g_MsgDest);
		PbSetInt(RadioText, "client", g_MsgClient);
		PbSetString(RadioText, "msg_name", g_MsgName);
		PbSetString(RadioText, "params", g_MsgParam1, 0);
		PbSetString(RadioText, "params", g_MsgParam2, 1);
		PbSetString(RadioText, "params", g_MsgParam3, 2);
		PbSetString(RadioText, "params", g_MsgParam4, 3);
	}
	else
	{
		BfWriteByte(RadioText, g_MsgDest);
		BfWriteByte(RadioText, g_MsgClient);
		BfWriteString(RadioText, g_MsgName);
		BfWriteString(RadioText, g_MsgParam1);
		BfWriteString(RadioText, g_MsgParam2);
		BfWriteString(RadioText, g_MsgParam3);
		BfWriteString(RadioText, g_MsgParam4);
	}
	EndMessage();

	Handle SendAudio = StartMessage("SendAudio", g_MsgPlayers, playersNum, USERMSG_RELIABLE);
	if(g_bIsProtoBuf)
		PbSetString(SendAudio, "radio_sound", g_MsgRadioSound);
	else
		BfWriteString(SendAudio, g_MsgRadioSound);
	EndMessage();
}

/*
 * HELPERS
*/
void UpdateIgnored()
{
	if(g_Plugin_ccc)
		CCC_UpdateIgnoredArray(g_Ignored);
}

public int SortByPlayerName(int elem1, int elem2, const int[] array, Handle hndl)
{
	return strcmp(g_PlayerNames[elem1], g_PlayerNames[elem2], false);
}

bool GetIgnored(int client, int target)
{
	return g_Ignored[(client * (MAXPLAYERS + 1) + target)];
}

void SetIgnored(int client, int target, bool ignored)
{
	g_Ignored[(client * (MAXPLAYERS + 1) + target)] = ignored;
}

bool GetExempt(int client, int target)
{
	return g_Exempt[client][target];
}

void SetExempt(int client, int target, bool exempt)
{
	g_Exempt[client][target] = exempt;
}
