/*
* 怪物生命值监控器 / Monster health monitor
* 作者(Author): Ice Box
* Steam: https://steamcommunity.com/id/ibox666/
* 如需使用此脚本请联系作者 / If you need to use this script, please contact the author
*/

namespace PointMonsterWatcher
{

const int SF_START_OFF = 1 << 0;
const int SF_PASS_ACTIVATOR = 1 << 1;
const int SF_TRIGGER_ONCE = 1 << 2;
const int SF_REMOVE_ON_TRIGGER = 1 << 3;

const int MAX_HP_THRESHOLDS = 5;

class HPThreshold
{
	int percent;
	string target;
	float delay;
	bool triggered;

	HPThreshold()
	{
		percent = 0;
		target = "";
		delay = 2.0;
		triggered = false;
	}

	HPThreshold(int p, const string& in t, float d = 2.0)
	{
		percent = p;
		target = t;
		delay = d;
		triggered = false;
	}

	bool IsValid()
	{
		return percent >= 10 && percent <= 90 && target != "";
	}
}

class CPointMonsterWatcher : ScriptBaseEntity
{
	private bool m_bEnabled = true;
	private bool m_bHasTriggered = false;
	private bool m_bWatching = false;
	private float m_flDelay = 0.0;
	private float m_flCheckInterval = 0.1;
	private string m_szMonsterTargetname = "";
	private string m_szKillTarget = "";
	private array<HPThreshold> m_HPThresholds;
	private EHandle m_hActivator;
	private EHandle m_hMonster;
	private CScheduledFunction@ m_pWatchThink;
	private CScheduledFunction@ m_pDelayedTrigger;

	bool KeyValue(const string& in szKey, const string& in szValue)
	{
		if(szKey == "monster_targetname")
		{
			m_szMonsterTargetname = szValue;
			return true;
		}
		else if(szKey == "delay")
		{
			m_flDelay = atof(szValue);
			if(m_flDelay < 0.0)
				m_flDelay = 0.0;
			return true;
		}
		else if(szKey == "check_interval")
		{
			m_flCheckInterval = atof(szValue);
			if(m_flCheckInterval <= 0.0)
				m_flCheckInterval = 0.1;
			return true;
		}
		else if(szKey == "killtarget")
		{
			m_szKillTarget = szValue;
			return true;
		}
		else if(szKey.Length() >= 10)
		{
			if(szKey.SubString(0, 10) == "hp_percent")
			{
				string numStr = szKey.SubString(10);
				int num = atoi(numStr);

				if(num >= 1 && num <= MAX_HP_THRESHOLDS)
				{
					while(int(m_HPThresholds.length()) < num)
					{
						m_HPThresholds.insertLast(HPThreshold());
					}

					int percent = atoi(szValue);
					if(percent < 10 || percent > 90)
						percent = 0;

					m_HPThresholds[num - 1].percent = percent;
					return true;
				}
			}
			else if(szKey.SubString(0, 9) == "hp_target")
			{
				string numStr = szKey.SubString(9);
				int num = atoi(numStr);

				if(num >= 1 && num <= MAX_HP_THRESHOLDS)
				{
					while(int(m_HPThresholds.length()) < num)
					{
						m_HPThresholds.insertLast(HPThreshold());
					}

					m_HPThresholds[num - 1].target = szValue;
					return true;
				}
			}
			else if(szKey.SubString(0, 8) == "hp_delay")
			{
				string numStr = szKey.SubString(8);
				int num = atoi(numStr);

				if(num >= 1 && num <= MAX_HP_THRESHOLDS)
				{
					while(int(m_HPThresholds.length()) < num)
					{
						m_HPThresholds.insertLast(HPThreshold());
					}

					float delayValue = atof(szValue);
					if(delayValue < 0.0)
						delayValue = 0.0;

					m_HPThresholds[num - 1].delay = delayValue;
					return true;
				}
			}
		}

		return BaseClass.KeyValue(szKey, szValue);
	}

	void Spawn()
	{
		self.pev.solid = SOLID_NOT;
		self.pev.movetype = MOVETYPE_NONE;
		self.pev.effects |= EF_NODRAW;

		if((self.pev.spawnflags & SF_START_OFF) != 0)
		{
			m_bEnabled = false;
		}
		else
		{
			StartWatching(null);
		}
	}

	void UpdateOnRemove()
	{
		if(m_pWatchThink !is null)
		{
			g_Scheduler.RemoveTimer(m_pWatchThink);
			@m_pWatchThink = null;
		}

		if(m_pDelayedTrigger !is null)
		{
			g_Scheduler.RemoveTimer(m_pDelayedTrigger);
			@m_pDelayedTrigger = null;
		}

		BaseClass.UpdateOnRemove();
	}

	void Use(CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float flValue)
	{
		switch(useType)
		{
			case USE_ON:
			{
				m_bEnabled = true;
				if(!m_bWatching)
					StartWatching(pActivator);
				return;
			}
			case USE_OFF:
			{
				m_bEnabled = false;
				StopWatching();
				return;
			}
			case USE_TOGGLE:
			{
				if(m_bEnabled && m_bWatching)
				{
					m_bEnabled = false;
					StopWatching();
				}
				else
				{
					m_bEnabled = true;
					if(!m_bWatching)
						StartWatching(pActivator);
				}
				return;
			}
			case USE_KILL:
			{
				g_EntityFuncs.Remove(self);
				return;
			}
			case USE_SET:
			{
				if(!m_bEnabled)
					return;

				m_bHasTriggered = false;

				for(uint i = 0; i < m_HPThresholds.length(); i++)
				{
					m_HPThresholds[i].triggered = false;
				}

				StartWatching(pActivator);
				return;
			}
		}
	}

	void StartWatching(CBaseEntity@ pActivator)
	{
		if(!m_bEnabled)
			return;

		if(m_szMonsterTargetname == "")
			return;

		CBaseEntity@ pMonster = g_EntityFuncs.FindEntityByTargetname(null, m_szMonsterTargetname);

		if(pMonster is null)
			return;

		string classname = pMonster.pev.classname;
		if(classname.Length() < 8 || classname.SubString(0, 8) != "monster_")
			return;

		if(pActivator !is null)
		{
			m_hActivator = EHandle(pActivator);
		}

		m_hMonster = EHandle(pMonster);
		StopWatching();
		m_bWatching = true;

		@m_pWatchThink = g_Scheduler.SetInterval(this, "WatchThink", m_flCheckInterval);
	}

	void StopWatching()
	{
		if(!m_bWatching)
			return;

		m_bWatching = false;

		if(m_pWatchThink !is null)
		{
			g_Scheduler.RemoveTimer(m_pWatchThink);
			@m_pWatchThink = null;
		}

		if(m_pDelayedTrigger !is null)
		{
			g_Scheduler.RemoveTimer(m_pDelayedTrigger);
			@m_pDelayedTrigger = null;
		}
	}

	void WatchThink()
	{
		if(!m_bWatching)
		{
			StopWatching();
			return;
		}

		if((self.pev.spawnflags & SF_TRIGGER_ONCE) != 0 && m_bHasTriggered)
		{
			StopWatching();
			return;
		}

		CBaseEntity@ pMonster = m_hMonster.GetEntity();

		if((pMonster is null || pMonster.pev.deadflag != DEAD_NO) && !m_bHasTriggered)
		{
			TriggerAllRemainingThresholds(pMonster);
			m_bHasTriggered = true;
			StopWatching();

			if(m_flDelay > 0.0)
			{
				@m_pDelayedTrigger = g_Scheduler.SetTimeout(this, "DelayedTrigger", m_flDelay);
			}
			else
			{
				DelayedTrigger();
			}
			return;
		}

		if(pMonster !is null)
		{
			float maxHealth = pMonster.pev.max_health;
			if(maxHealth > 0)
			{
				float currentPercent = (pMonster.pev.health / maxHealth) * 100.0;

				for(uint i = 0; i < m_HPThresholds.length(); i++)
				{
					if(!m_HPThresholds[i].IsValid() || m_HPThresholds[i].triggered)
						continue;

					if(currentPercent <= float(m_HPThresholds[i].percent))
					{
						m_HPThresholds[i].triggered = true;

						CBaseEntity@ pActivator = null;
						if((self.pev.spawnflags & SF_PASS_ACTIVATOR) != 0)
						{
							@pActivator = m_hActivator.GetEntity();
						}

						g_Scheduler.SetTimeout(this, "DelayedTriggerThreshold", m_HPThresholds[i].delay, m_HPThresholds[i].target, @pActivator);
					}
				}
			}
		}
	}

	void TriggerAllRemainingThresholds(CBaseEntity@ pMonster)
	{
		if(m_HPThresholds.length() == 0)
			return;

		CBaseEntity@ pActivator = null;
		if((self.pev.spawnflags & SF_PASS_ACTIVATOR) != 0)
		{
			@pActivator = m_hActivator.GetEntity();
		}

		float accumulatedDelay = 0.0;
		float maxDelay = 0.0;

		for(uint i = 0; i < m_HPThresholds.length(); i++)
		{
			if(!m_HPThresholds[i].IsValid() || m_HPThresholds[i].triggered)
				continue;

			m_HPThresholds[i].triggered = true;

			g_Scheduler.SetTimeout(this, "DelayedTriggerThreshold", accumulatedDelay, m_HPThresholds[i].target, @pActivator);

			accumulatedDelay += m_HPThresholds[i].delay;
			maxDelay = accumulatedDelay;
		}

		if((self.pev.spawnflags & SF_REMOVE_ON_TRIGGER) != 0 && maxDelay > 0.0)
		{
			g_Scheduler.SetTimeout(this, "RemoveSelfSafely", maxDelay + 0.1);
		}
	}

	void DelayedTriggerThreshold(string targetName, CBaseEntity@ pActivator)
	{
		if(self is null || self.pev is null)
			return;

		g_EntityFuncs.FireTargets(targetName, pActivator, self, USE_TOGGLE, 0.0);
	}

	void DelayedTrigger()
	{
		if(self is null || self.pev is null)
			return;

		CBaseEntity@ pActivator = null;
		if((self.pev.spawnflags & SF_PASS_ACTIVATOR) != 0)
		{
			@pActivator = m_hActivator.GetEntity();
		}

		string szMessage = string(self.pev.message);
		if(szMessage != "")
		{
			g_PlayerFuncs.ClientPrintAll(HUD_PRINTTALK, szMessage + "\n");
		}

		if(self.pev.target != "")
		{
			g_EntityFuncs.FireTargets(string(self.pev.target), pActivator, self, USE_TOGGLE, 0.0);
		}

		if(m_szKillTarget != "")
		{
			CBaseEntity@ pKillTarget = null;
			while((@pKillTarget = g_EntityFuncs.FindEntityByTargetname(pKillTarget, m_szKillTarget)) !is null)
			{
				g_EntityFuncs.Remove(pKillTarget);
			}
		}

		if((self.pev.spawnflags & SF_REMOVE_ON_TRIGGER) != 0 && m_HPThresholds.length() == 0)
		{
			g_EntityFuncs.Remove(self);
		}
	}

	void RemoveSelfSafely()
	{
		if(self is null || self.pev is null)
			return;

		g_EntityFuncs.Remove(self);
	}
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity("PointMonsterWatcher::CPointMonsterWatcher", "point_monster_watcher");
}

}