#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;

bool g_bClientMuteTempOrPerm[MAXPLAYERS + 1];

Database g_hDatabase;

#define PLUGIN_VERSION "2.5"
//may 20th 2024 jenz edit to add option for perm sm, stored in database.
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];

/*
	CREATE TABLE SelfMute.unloze_selfmute (
  `client_name` varchar(64) NOT NULL,
  `client_steamid` varchar(32) NOT NULL,
  `target_name` varchar(64) NOT NULL,
  `target_steamid` varchar(32) NOT NULL,
  PRIMARY KEY (`client_steamid`, `target_steamid`)
)

CREATE TABLE SelfMute.is_online (
  `client_name` varchar(64) NOT NULL,
  `client_steamid` varchar(32) NOT NULL,
  PRIMARY KEY (`client_steamid`)
)
*/

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

    char sQuery[512];
    Format(sQuery, sizeof(sQuery), "truncate table `is_online`");
    g_hDatabase.Query(SQL_OnQueryCompletedTruncate, sQuery);
}

public void SQL_OnQueryCompletedTruncate(Database db, DBResultSet results, const char[] error, int iSerial)
{
    if (!db || strlen(error))
    {
        delete results;
        LogError("Query error 3: %s", error);
        return;
    }
    delete results;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i) && !IsFakeClient(i))
        {
            OnClientPostAdminCheck(i);
        }
    }
}

public void ReadClientsToPermMuteForClient(int client)
{
    char csSID[64];
    GetClientAuthId(client, AuthId_Steam2, csSID, sizeof(csSID));
    char sQuery[512];
    //limits this way to only select online clients to apply self mute for.
    Format(sQuery, sizeof(sQuery), "select target_steamid from `unloze_selfmute` where client_steamid = '%s' and target_steamid in (select client_steamid from `is_online`)", csSID);
    g_hDatabase.Query(SQL_GetClientsToSelfMuteForClient, sQuery, GetClientSerial(client), DBPrio_High);
}

public void SQL_GetClientsToSelfMuteForClient(Database db, DBResultSet results, const char[] error, int iSerial)
{
    if (!db || strlen(error))
    {
        delete results;
        LogError("Query error 3: %s", error);
        return;
    }
    int client;
    if ((client = GetClientFromSerial(iSerial)) == 0)
    {
        delete results;
        return;
    }

    while (results.RowCount && results.FetchRow())
    {
        char cSID[64];
        results.FetchString(0, cSID, sizeof(cSID));
        for (int i = 1; i <= MaxClients; i++)
        {
            if (IsValidClient(i) && !IsFakeClient(i))
            {
                char sAuth[64];
                GetClientAuthId(i, AuthId_Steam2, sAuth, sizeof(sAuth));
                if (StrEqual(cSID, sAuth))
                {
                    Ignore(client, i);
                    PrintToChat(client, "Player you permanent muted connected: %N", i);
                    UpdateIgnored(); //no clue if it needs to update that often.
                }
            }
        }
    }
    delete results;
}

public void OnClientPostAdminCheck(int client)
{
    g_bClientMuteTempOrPerm[client] = false;
    if(!IsValidClient(client) || IsFakeClient(client))
        return;
    if (!g_hDatabase)
    {
        return;
    }
    if (!IsFakeClient(client))
    {
        insert_client_as_online(client);
    }
}

public void delete_client_from_online(int client)
{
    char csSID[64];
    GetClientAuthId(client, AuthId_Steam2, csSID, sizeof(csSID));
    char sQuery[512];
    Format(sQuery, sizeof(sQuery), "delete from `is_online` where client_steamid = '%s'", csSID);
    g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low);
}

public void insert_client_as_online(int client)
{
    char csSID[64];
    GetClientAuthId(client, AuthId_Steam2, csSID, sizeof(csSID));
    char sName[MAX_NAME_LENGTH];
    GetClientName(client, sName, sizeof(sName));
    int size2 = 2 * strlen(sName) + 1;
    char[] sEscapedName = new char[size2 + 1];
    g_hDatabase.Escape(sName, sEscapedName, size2 + 1);
    char sQuery[512];
    Format(sQuery, sizeof(sQuery), "INSERT IGNORE INTO `is_online` (`client_name`, `client_steamid`) VALUES ('%s', '%s')", sEscapedName, csSID);
    g_hDatabase.Query(SQL_FinishedQueryUpdateMutes, sQuery, _, DBPrio_High);
}

public void SQL_FinishedQueryUpdateMutes(Database db, DBResultSet results, const char[] error, any data)
{
    if (!db || strlen(error))
    {
        LogError("Query error 3: %s", error);
    }
    delete results;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i) && !IsFakeClient(i))
        {
            ReadClientsToPermMuteForClient(i);
        }
    }
}

public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, any data)
{
    if (!db || strlen(error))
    {
        LogError("Query error 3: %s", error);
    }
    delete results;
}

public void OnPluginStart()
{
    if (!g_hDatabase)
    {
        Database.Connect(SQL_OnDatabaseConnect, "SelfMute");
    }
    else
    {
        for (int i = 1; i <= MaxClients; i++)
        {
            if (IsValidClient(i) && !IsFakeClient(i))
            {
                OnClientPostAdminCheck(i);
            }
        }
        
    }
    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_psm", Command_SelfMutePerm, "Mute player by typing !psm [playername] but permanently");
    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 <= MaxClients; i++)
	{
		SetIgnored(client, i, false);
		SetExempt(client, i, false);

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

	UpdateSpecialMutesOtherClients(client);
	UpdateIgnored();

	GetCookiesForTorch(client);
}

public void OnMapEnd()
{
    char sQuery[512];
    Format(sQuery, sizeof(sQuery), "truncate table `is_online`");
    g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low);
}

public void OnClientDisconnect(int client)
{
    g_bClientMuteTempOrPerm[client] = false;
    if (!IsFakeClient(client))
    {
        delete_client_from_online(client);
    }
    g_SpecialMutes[client] = MUTE_NONE;
    for(int i = 1; i <= MaxClients; 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 whove got Torch permanently self-muted.", iTorchPermMuted, iPlayers);

	return Plugin_Handled;
}

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

    if(args < 1)
    {
        PrintToChat(client, "\x04[Self-Mute]\x01 Permanent mutes are dangerous. You need to specify the name of the target to permanently self mute.");
        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]);
        if (!IsFakeClient(aTargetList[i]))
        {
            AddClientMutingClient(client, aTargetList[i]);
        }
    }
    UpdateIgnored();

    PrintToChat(client, "\x04[Self-Mute]\x01 You have permanently self-muted:\x04 %s. !su can revert this decision.", sTargetName);

    return Plugin_Handled;
}

public void AddClientMutingClient(int client, int target)
{
    char csSID[64];
    GetClientAuthId(client, AuthId_Steam2, csSID, sizeof(csSID));
    char csSIDTarget[64];
    GetClientAuthId(target, AuthId_Steam2, csSIDTarget, sizeof(csSIDTarget));

    char sName[MAX_NAME_LENGTH];
    GetClientName(client, sName, sizeof(sName));
    int size2 = 2 * strlen(sName) + 1;
    char[] sEscapedName = new char[size2 + 1];
    g_hDatabase.Escape(sName, sEscapedName, size2 + 1);

    GetClientName(target, sName, sizeof(sName));
    size2 = 2 * strlen(sName) + 1;
    char[] sEscapedNameTarget = new char[size2 + 1];
    g_hDatabase.Escape(sName, sEscapedNameTarget, size2 + 1);

    char sQuery[512];

    Format(sQuery, sizeof(sQuery), "insert ignore into `unloze_selfmute` (`client_name`, `client_steamid`, `target_name`, `target_steamid`)  VALUES ('%s', '%s', '%s', '%s')", sEscapedName, csSID, sEscapedNameTarget, csSIDTarget);
    g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low);
}

public void delete_client_from_permMute(int client, int target)
{
    if (IsFakeClient(target))
    {
        return;
    }
    char csSID[64];
    GetClientAuthId(client, AuthId_Steam2, csSID, sizeof(csSID));

    char csSIDTarget[64];
    GetClientAuthId(target, AuthId_Steam2, csSIDTarget, sizeof(csSIDTarget));

    char sQuery[512];
    Format(sQuery, sizeof(sQuery), "delete from `unloze_selfmute` where client_steamid = '%s' and target_steamid = '%s'", csSID, csSIDTarget);
    g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low);
}

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

    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. \x01 You can use !psm <playername> for permanent self mute now.", 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 wont 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]);
        delete_client_from_permMute(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);
                    delete_client_from_permMute(param1, 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. \x01 You can now use !psm to permanent self mute a player.", 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);
                    delete_client_from_permMute(param1, 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;
}

stock bool IsValidClient(int client)
{
    if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
        return true;
    return false;
}