#include <sourcemod>
#include <sdktools>
#include <BossHP>
#include <zombiereloaded>
#include <clientprefs>
#include <multicolors>
#include <dhooks>
#include <outputinfo>

#pragma newdecls required
#pragma	semicolon 1

#define SPECMODE_NONE           0
#define SPECMODE_FIRSTPERSON    4
#define SPECMODE_THIRDPERSON    5
#define SPECMODE_FREELOOK       6

#define MAX_EDICTS 2048
bool g_bHasOutputs[MAX_EDICTS];

bool g_bShowBossHitmarker[MAXPLAYERS+1];
bool g_bShowZombieHitmarker[MAXPLAYERS+1];
bool g_bHitmarkerSound[MAXPLAYERS+1];

int g_iHitmarkerSoundVolume[MAXPLAYERS+1] = {100,...};
int g_iHitmarkerSkin[MAXPLAYERS+1];

#define MAXIMUMSKINS 6
char g_sHitmarkerSkins[MAXIMUMSKINS][PLATFORM_MAX_PATH];
char g_sHitmarkerSkinsNames[MAXIMUMSKINS][PLATFORM_MAX_PATH];

Handle g_hTimer[MAXPLAYERS+1] = {null,...};

Handle g_hCookie_ShowBossHitmarker;
Handle g_hCookie_ShowZombieHitmarker;
Handle g_hCookie_HitmarkerSound;
Handle g_hCookie_HitmarkerSoundVolume;
Handle g_hCookie_HitmarkerSkin;

ConVar g_hCVar_Debug;

bool g_bLate;

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
	name         = "Hitmarker",
	author       = "Neon",
	description  = "Players can enable or disable their hitmarkers while shooting zombies or bosses",
	version      = "3.1",
};

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int errorSize)
{
	g_bLate = bLate;
	return APLRes_Success;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
	Handle g_hOnTakeDamageDetour;
	Handle hGameData = LoadGameConfigFile("Hitmarker.games");
	if(!hGameData)
		SetFailState("Failed to load Hitmarker gamedata.");

	g_hOnTakeDamageDetour = DHookCreateFromConf(hGameData, "CBaseEntity__OnTakeDamage");
	if(!g_hOnTakeDamageDetour)
		SetFailState("Failed to setup detour for CBaseEntity__OnTakeDamage");

	delete hGameData;

	if(!DHookEnableDetour(g_hOnTakeDamageDetour, true, Detour_OnTakeDamage))
		SetFailState("Failed to detour CBaseEntity__OnTakeDamage.");

	g_hCookie_ShowBossHitmarker = RegClientCookie("hitmarker_boss", "", CookieAccess_Private);
	g_hCookie_ShowZombieHitmarker = RegClientCookie("hitmarker_zombie", "", CookieAccess_Private);
	g_hCookie_HitmarkerSound = RegClientCookie("hitmarker_sound", "", CookieAccess_Private);
	g_hCookie_HitmarkerSoundVolume = RegClientCookie("hitmarker_sound_volume", "", CookieAccess_Private);
	g_hCookie_HitmarkerSkin = RegClientCookie("hitmarker_skin", "", CookieAccess_Private);

	g_hCVar_Debug = CreateConVar("sm_hitmarker_debug", "0", "", FCVAR_NONE, true, 0.0, true, 1.0);
	AutoExecConfig();

	RegConsoleCmd("sm_hm", OnHitmarkerSettings);
	RegConsoleCmd("sm_hitmarker", OnHitmarkerSettings);
	RegConsoleCmd("sm_bhm", OnToggleBossHitmarker);
	RegConsoleCmd("sm_zhm", OnToggleZombieHitmarker);
	RegConsoleCmd("sm_hmsound", OnToggleHitmarkerSound);

	SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Hitmarker");

	HookEvent("player_hurt", OnClientHurt);

	if (g_bLate)
	{
		for (int i = 1; i <= MaxClients; i++)
		{
			if (IsValidClient(i) && AreClientCookiesCached(i))
				OnClientCookiesCached(i);
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnMapStart()
{
	for (int i = 0; i < MAXIMUMSKINS; i++)
	{
		g_sHitmarkerSkins[i] = "";
		g_sHitmarkerSkinsNames[i] = "";
	}

	char sConfigFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/Hitmarkers.cfg");

	if (!FileExists(sConfigFile))
		SetFailState("Could not find: \"%s\"", sConfigFile);

	KeyValues Hitmarkers = new KeyValues("Hitmarkers");

	if (!Hitmarkers.ImportFromFile(sConfigFile))
	{
		delete Hitmarkers;
		SetFailState("ImportFromFile() failed!");
	}

	if (!Hitmarkers.GotoFirstSubKey())
	{
		delete Hitmarkers;
		SetFailState("Unable to goto first sub key in: \"%s\"", sConfigFile);
	}

	int i = 0;
	do
	{
		char sPath[PLATFORM_MAX_PATH];
		Hitmarkers.GetString("Path", sPath, sizeof(sPath), "error");
		if (StrEqual(sPath, "error"))
		{
			delete Hitmarkers;
			SetFailState("Unable to read Path");
		}

		char sName[32];
		Hitmarkers.GetString("Name", sName, sizeof(sName), "error");
		if (StrEqual(sName, "error"))
		{
			delete Hitmarkers;
			SetFailState("Unable to read Name");
		}

		char sBuffer[PLATFORM_MAX_PATH];

		Format(sBuffer, sizeof(sBuffer), "materials/%s.vmt", sPath);
		PrecacheGeneric(sBuffer, true);
		AddFileToDownloadsTable(sBuffer);

		Format(sBuffer, sizeof(sBuffer), "materials/%s.vtf", sPath);
		PrecacheGeneric(sBuffer, true);
		AddFileToDownloadsTable(sBuffer);

		g_sHitmarkerSkins[i] = sPath;
		g_sHitmarkerSkinsNames[i] = sName;
		i++;
	} while(Hitmarkers.GotoNextKey());
	delete Hitmarkers;

	PrecacheSound("unloze/hm_v3.mp3");
	AddFileToDownloadsTable("sound/unloze/hm_v3.mp3");
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientCookiesCached(int client)
{
	char sBuffer[PLATFORM_MAX_PATH];

	GetClientCookie(client, g_hCookie_ShowBossHitmarker, sBuffer, sizeof(sBuffer));
	if (sBuffer[0])
		g_bShowBossHitmarker[client] = true;
	else
		g_bShowBossHitmarker[client] = false;

	GetClientCookie(client, g_hCookie_ShowZombieHitmarker, sBuffer, sizeof(sBuffer));
	if (sBuffer[0])
		g_bShowZombieHitmarker[client] = true;
	else
		g_bShowZombieHitmarker[client] = false;

	GetClientCookie(client, g_hCookie_HitmarkerSound, sBuffer, sizeof(sBuffer));
	if (sBuffer[0])
		g_bHitmarkerSound[client] = true;
	else
		g_bHitmarkerSound[client] = false;

	GetClientCookie(client, g_hCookie_HitmarkerSoundVolume, sBuffer, sizeof(sBuffer));
	if (sBuffer[0])
		g_iHitmarkerSoundVolume[client] = StringToInt(sBuffer);
	else
		g_iHitmarkerSoundVolume[client] = 100;

	GetClientCookie(client, g_hCookie_HitmarkerSkin, sBuffer, sizeof(sBuffer));
	if (sBuffer[0])
	{
		for (int i = 0; i < MAXIMUMSKINS; i++)
		{
			if (StrEqual(g_sHitmarkerSkins[i], sBuffer))
			{
				g_iHitmarkerSkin[client] = i;
				break;
			}
		}
	}
	else
		g_iHitmarkerSkin[client] = 0;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
	g_bShowBossHitmarker[client] = false;
	g_bShowZombieHitmarker[client] = false;
	g_bHitmarkerSound[client] = false;
	g_iHitmarkerSoundVolume[client] = 100;
	g_iHitmarkerSkin[client] = 0;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnHitmarkerSettings(int client, int args)
{
	ShowSettingsMenu(client);
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnToggleBossHitmarker(int client, int args)
{
	ToggleBossHitmarker(client);
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ToggleBossHitmarker(int client)
{
	g_bShowBossHitmarker[client] = !g_bShowBossHitmarker[client];
	SetClientCookie(client, g_hCookie_ShowBossHitmarker, g_bShowBossHitmarker[client] ? "1" : "");

	CPrintToChat(client, "{cyan}[Hitmarker] {white}%s.", g_bShowBossHitmarker[client] ? "Boss Hitmarker Enabled" : "Boss Hitmarker Disabled");

	if (g_bShowBossHitmarker[client])
		ShowOverlay(client, 2.0);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnToggleZombieHitmarker(int client, int args)
{
	ToggleZombieHitmarker(client);
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ToggleZombieHitmarker(int client)
{
	g_bShowZombieHitmarker[client] = !g_bShowZombieHitmarker[client];
	SetClientCookie(client, g_hCookie_ShowZombieHitmarker, g_bShowZombieHitmarker[client] ? "1" : "");

	CPrintToChat(client, "{cyan}[Hitmarker] {white}%s.", g_bShowZombieHitmarker[client] ? "Zombie Hitmarker Enabled" : "Zombie Hitmarker Disabled");

	if (g_bShowZombieHitmarker[client])
		ShowOverlay(client, 2.0);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnToggleHitmarkerSound(int client, int args)
{
	ToggleHitmarkerSound(client);
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSound(int client)
{
	g_bHitmarkerSound[client] = !g_bHitmarkerSound[client];
	SetClientCookie(client, g_hCookie_HitmarkerSound, g_bHitmarkerSound[client] ? "1" : "");

	CPrintToChat(client, "{cyan}[Hitmarker] {white}%s.", g_bHitmarkerSound[client] ? "Hitmarker Sound Enabled" : "Hitmarker Sound Disabled");

	if (g_bHitmarkerSound[client])
		ShowOverlay(client, 2.0);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSoundVolume(int client)
{
	g_iHitmarkerSoundVolume[client] += 25;

	if(g_iHitmarkerSoundVolume[client] > 100)
		g_iHitmarkerSoundVolume[client] = 25;

	char sBuffer[16];
	IntToString(g_iHitmarkerSoundVolume[client], sBuffer, sizeof(sBuffer));

	SetClientCookie(client, g_hCookie_HitmarkerSoundVolume, sBuffer);

	ShowOverlay(client, 2.0);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSkin(int client)
{
	g_iHitmarkerSkin[client]++;

	if (StrEqual(g_sHitmarkerSkins[g_iHitmarkerSkin[client]], ""))
		g_iHitmarkerSkin[client] = 0;

	SetClientCookie(client, g_hCookie_HitmarkerSkin, g_sHitmarkerSkins[g_iHitmarkerSkin[client]]);

	ShowOverlay(client, 2.0);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ShowSettingsMenu(int client)
{
	Menu menu = new Menu(MenuHandler_MainMenu);

	menu.SetTitle("Hitmarker Settings", client);

	char sBuffer[128];

	Format(sBuffer, sizeof(sBuffer), "Boss Hitmarker: %s", g_bShowBossHitmarker[client] ? "Enabled" : "Disabled");
	menu.AddItem("0", sBuffer);

	Format(sBuffer, sizeof(sBuffer), "Zombie Hitmarker: %s", g_bShowZombieHitmarker[client] ? "Enabled" : "Disabled");
	menu.AddItem("1", sBuffer);

	Format(sBuffer, sizeof(sBuffer), "Hitmarker Sound: %s", g_bHitmarkerSound[client] ? "Enabled" : "Disabled");
	menu.AddItem("2", sBuffer);

	Format(sBuffer, sizeof(sBuffer), "Hitmarker Sound Volume: %d\%", g_iHitmarkerSoundVolume[client]);
	menu.AddItem("3", sBuffer);

	Format(sBuffer, sizeof(sBuffer), "Hitmarker Skin: %s", g_sHitmarkerSkinsNames[g_iHitmarkerSkin[client]]);
	menu.AddItem("4", sBuffer);

	menu.ExitBackButton = true;

	menu.Display(client, MENU_TIME_FOREVER);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen)
{
	switch(action)
	{
		case(CookieMenuAction_DisplayOption):
		{
			Format(buffer, maxlen, "Hitmarker", client);
		}
		case(CookieMenuAction_SelectOption):
		{
			ShowSettingsMenu(client);
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
{
	switch(action)
	{
		case(MenuAction_Select):
		{
			switch(selection)
			{
				case(0): ToggleBossHitmarker(client);
				case(1): ToggleZombieHitmarker(client);
				case(2): ToggleHitmarkerSound(client);
				case(3): ToggleHitmarkerSoundVolume(client);
				case(4): ToggleHitmarkerSkin(client);
			}

			ShowSettingsMenu(client);
		}
		case(MenuAction_Cancel):
		{
			ShowCookieMenu(client);
		}
		case(MenuAction_End):
		{
			delete menu;
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnEntitySpawned(int Entity, const char[] sClassname)
{
	int ent = EntRefToEntIndex(Entity);

	if (0 < ent < MAX_EDICTS)
	{
		if ((GetOutputCount(ent, "m_OnDamaged") > 0) || (GetOutputCount(ent, "m_OnHealthChanged") > 0))
			g_bHasOutputs[ent] = true;
		else
			g_bHasOutputs[ent] = false;
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
/*
public void OnBossDamaged(CBoss Boss, CConfig Config, int client, float damage)
{
	if (!IsValidClient(client))
		return;

	if (g_bShowBossHitmarker[client])
		ShowOverlay(client);

	for (int spec = 1; spec <= MaxClients; spec++)
	{
		if (!IsClientInGame(spec) || !IsClientObserver(spec) || !g_bShowBossHitmarker[spec])
			continue;

		int specMode   = GetClientSpectatorMode(spec);
		int specTarget = GetClientSpectatorTarget(spec);

		if ((specMode == SPECMODE_FIRSTPERSON) && specTarget == client)
			ShowOverlay(spec);
	}
}
*/
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public MRESReturn Detour_OnTakeDamage(int entity, Handle hReturn, Handle hParams)
{
	//https://github.com/alliedmodders/hl2sdk/blob/css/game/shared/takedamageinfo.h#L115
	int iHealth = GetEntProp(entity, Prop_Data, "m_iHealth");
	int client = DHookGetParamObjectPtrVar(hParams, 1, 3*(3*4) + 0, ObjectValueType_Ehandle);

	if (g_hCVar_Debug.BoolValue)
	{
		float dmg = DHookGetParamObjectPtrVar(hParams, 1, 48, ObjectValueType_Float);
		PrintToChatAll("Detour_OnTakeDamage: ent: %d; client: %d; m_iHealth: %d; output: %d; dmg: %f", entity, client, iHealth, g_bHasOutputs[entity], dmg);
	}

	if (!iHealth && !g_bHasOutputs[entity])
		return MRES_Ignored;

	if (!IsValidClient(client))
		return MRES_Ignored;

	if (g_bShowBossHitmarker[client])
		ShowOverlay(client);

	for (int spec = 1; spec <= MaxClients; spec++)
	{
		if (!IsClientInGame(spec) || !IsClientObserver(spec) || !g_bShowBossHitmarker[spec])
			continue;

		int specMode   = GetClientSpectatorMode(spec);
		int specTarget = GetClientSpectatorTarget(spec);

		if ((specMode == SPECMODE_FIRSTPERSON) && specTarget == client)
			ShowOverlay(spec);
	}

	return MRES_Ignored;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
	int client = GetClientOfUserId(hEvent.GetInt("attacker"));
	int victim = GetClientOfUserId(hEvent.GetInt("userid"));

	if (client < 1 || client > MaxClients || victim < 1 || victim > MaxClients)
		return;

	if (client == victim || (IsPlayerAlive(client) && ZR_IsClientZombie(client)))
		return;

	if (g_bShowZombieHitmarker[client])
		ShowOverlay(client);

	for (int spec = 1; spec <= MaxClients; spec++)
	{
		if (!IsClientInGame(spec) || !IsClientObserver(spec) || !g_bShowZombieHitmarker[spec])
			continue;

		int specMode   = GetClientSpectatorMode(spec);
		int specTarget = GetClientSpectatorTarget(spec);

		if (specMode == SPECMODE_FIRSTPERSON && specTarget == client)
			ShowOverlay(spec);
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
void ShowOverlay(int client, float fTime = 0.3)
{
	if (g_bHitmarkerSound[client])
	{
		float fVolume = g_iHitmarkerSoundVolume[client] / 100.0;
		EmitSoundToClient(client, "unloze/hm_v3.mp3", .volume=fVolume);
	}

	if (g_hTimer[client] != null)
	{
		delete g_hTimer[client];
		g_hTimer[client] = null;
	}
	ClientCommand(client, "r_screenoverlay \"%s\"", g_sHitmarkerSkins[g_iHitmarkerSkin[client]]);
	g_hTimer[client] = CreateTimer(fTime, ClearOverlay, client);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action ClearOverlay(Handle timer, int client)
{
	g_hTimer[client] = null;
	if (IsClientConnected(client))
		ClientCommand(client, "r_screenoverlay \"\"");
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock int IsValidClient(int client, bool nobots = true)
{
	if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (nobots && IsFakeClient(client)))
		return false;

	return IsClientInGame(client);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
int GetClientSpectatorMode(int client)
{
	return GetEntProp(client, Prop_Send, "m_iObserverMode");
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
int GetClientSpectatorTarget(int client)
{
	return GetEntPropEnt(client, Prop_Send, "m_hObserverTarget");
}