/*
* 强制蹲下触发器实体 / Force duck trigger entity
* 作者(Author): Ice Box
* Steam: https://steamcommunity.com/id/ibox666/
* 如需使用此脚本请联系作者 / If you need to use this script, please contact the author
*/

namespace ForceDuck
{

const int SF_START_OFF = 1 << 0;
const int SF_INVERT_TARGET = 1 << 1;

class CTriggerForceDuck : ScriptBaseEntity
{
	private EHandle hActivator;
	private bool blShouldForceDuck;
	private float flWaitTime = -1;

	private CScheduledFunction@ fnWaitToggle;

	private dictionary dictPlayerStates;

	bool KeyValue(const string& in szKey, const string& in szValue)
	{
		if(szKey == "wait")
		{
			flWaitTime = atof(szValue);
			return true;
		}
		else
			return BaseClass.KeyValue(szKey, szValue);
	}

	void Spawn()
	{
		self.pev.movetype = MOVETYPE_NONE;
		self.pev.solid = SOLID_TRIGGER;

		g_EntityFuncs.SetModel(self, self.pev.model);
		g_EntityFuncs.SetSize(self.pev, self.pev.mins, self.pev.maxs);
		g_EntityFuncs.SetOrigin(self, self.pev.origin);

		self.pev.effects |= EF_NODRAW;

		SetTouch(TouchFunction(this.Touch));
		SetUse(UseFunction(this.Use));

		if(self.pev.SpawnFlagBitSet(SF_START_OFF))
		{
			blShouldForceDuck = false;
		}
		else
		{
			blShouldForceDuck = true;
		}

		BaseClass.Spawn();

		self.pev.nextthink = g_Engine.time + 0.1;
	}

	void Think()
	{
		array<string> @keys = dictPlayerStates.getKeys();
		for(uint i = 0; i < keys.length(); i++)
		{
			CBasePlayer@ pPlayer = g_PlayerFuncs.FindPlayerByName(keys[i]);
			if(pPlayer !is null && pPlayer.IsConnected())
			{
				if(!self.Intersects(pPlayer))
				{
					OnPlayerLeave(pPlayer);
				}
			}
			else
			{
				dictPlayerStates.delete(keys[i]);
			}
		}

		self.pev.nextthink = g_Engine.time + 0.1;
	}

	void AdjustPlayerToGround(CBasePlayer@ pPlayer)
	{
		if(pPlayer is null || !pPlayer.IsConnected())
			return;

		TraceResult trGround;
		Vector vecStart = pPlayer.pev.origin;
		Vector vecEnd = vecStart;
		vecEnd.z -= 100;

		g_Utility.TraceLine(vecStart, vecEnd, ignore_monsters, pPlayer.edict(), trGround);

		if(trGround.flFraction < 1.0f)
		{
			float idealOffset = 18.0f;
			float currentOffset = (vecStart.z - trGround.vecEndPos.z);

			if(currentOffset > idealOffset + 0.5f || (pPlayer.pev.flags & FL_ONGROUND) == 0)
			{
				Vector vecNewOrigin = pPlayer.pev.origin;
				vecNewOrigin.z = trGround.vecEndPos.z + idealOffset;
				g_EntityFuncs.SetOrigin(pPlayer, vecNewOrigin);
			}
		}
	}

	void OnPlayerLeave(CBasePlayer@ pPlayer)
	{
		if(pPlayer is null)
			return;

		string playerKey = pPlayer.pev.netname;

		if(dictPlayerStates.exists(playerKey))
		{
			pPlayer.pev.flDuckTime = 0;
			pPlayer.pev.bInDuck = 0;

			dictPlayerStates.delete(playerKey);
		}
	}

	void Touch(CBaseEntity@ pOther)
	{
		if(pOther is null || !pOther.IsPlayer() || !blShouldForceDuck)
			return;

		CBasePlayer@ pPlayer = cast<CBasePlayer@>(pOther);

		if(pPlayer is null || !pPlayer.IsConnected())
			return;

		if(!FCanDuckTarget(pPlayer))
			return;

		string playerKey = pPlayer.pev.netname;

		bool isFirstTouch = !dictPlayerStates.exists(playerKey);

		dictPlayerStates[playerKey] = true;

		pPlayer.pev.flags |= FL_DUCKING;

		pPlayer.pev.view_ofs = Vector(0, 0, 12);

		pPlayer.pev.bInDuck = 1;

		if(isFirstTouch)
			AdjustPlayerToGround(pPlayer);
	}

	bool FCanDuckTarget(EHandle hDuckTarget)
	{
		if(!hDuckTarget)
			return false;

		if(self.pev.target == "")
			return true;

		if(self.pev.target == "!activator" && hActivator)
		{
			return(self.pev.SpawnFlagBitSet(SF_INVERT_TARGET) ?
					hDuckTarget.GetEntity() !is hActivator.GetEntity() :
					hDuckTarget.GetEntity() is hActivator.GetEntity());
		}

		return(self.pev.SpawnFlagBitSet(SF_INVERT_TARGET) ?
				self.pev.target != hDuckTarget.GetEntity().GetTargetname() :
				self.pev.target == hDuckTarget.GetEntity().GetTargetname());
	}

	void TurnOff()
	{
		self.Use(self, self, USE_OFF);
	}

	void Use(CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float flValue)
	{
		switch(useType)
		{
			case USE_ON:
			{
				blShouldForceDuck = true;
				SetTouch(TouchFunction(this.Touch));
				hActivator = pActivator;
				break;
			}
			case USE_OFF:
			{
				blShouldForceDuck = false;
				SetTouch(null);
				hActivator = EHandle();
				break;
			}
			case USE_TOGGLE:
				self.Use(null, null, blShouldForceDuck ? USE_OFF : USE_ON);
				break;
			case USE_KILL:
			{
				g_EntityFuncs.Remove(self);
				break;
			}
			case USE_SET:
			{
				if(flValue > 0)
				{
					flWaitTime = flValue;
				}
				break;
			}
		}

		if(flWaitTime > 0 && blShouldForceDuck)
			@fnWaitToggle = g_Scheduler.SetTimeout(this, "TurnOff", flWaitTime);

		if(self.pev.message != "" && self.pev.message != self.GetTargetname() && blShouldForceDuck)
			g_EntityFuncs.FireTargets(self.pev.message, hActivator.GetEntity(), self, USE_TOGGLE);
	}

	void UpdateOnRemove()
	{
		blShouldForceDuck = false;
		SetTouch(null);

		dictPlayerStates.deleteAll();

		if(fnWaitToggle !is null)
			g_Scheduler.RemoveTimer(fnWaitToggle);

		BaseClass.UpdateOnRemove();
	}
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity("ForceDuck::CTriggerForceDuck", "trigger_forceduck");
}

}