untyped

global function CloakerThink
global function CloakerShouldCloakGuy

global function CloakerCloaksGuy
global function CloakerDeCloaksGuy

function CloakerThink( entity cloaker, float radius, array<string> ents = [ "any" ], vector offset = Vector(0,0,0), var shouldCloakGuyFunc = null, float waitTime = 0.25 )
{
	OnThreadEnd(
		function() : ( cloaker )
		{
			local cloakList = clone cloaker.s.cloakList
			foreach ( entity guy, value in cloakList )
			{
				if ( !IsAlive( guy ) )
					continue

				CloakerDeCloaksGuy( guy )
			}
		}
	)

	cloaker.s.cloakList <- {}
	cloaker.s.decloakList <- {}

	while( 1 )
	{
		vector origin = cloaker.GetOrigin() + offset
		array<entity> guys

		foreach ( entType in ents )
		{
			switch ( entType )
			{
				case "player":
				case "players":
					guys.extend( GetPlayerArrayEx( "any", cloaker.GetTeam(), TEAM_ANY, origin, radius ) )
					break;
				default:
					guys.extend( GetNPCArrayEx( entType, cloaker.GetTeam(), TEAM_ANY, origin, radius ) )
					break
			}
		}
		int index = 0

		float startTime = Time()

		table cloakList = expect table( cloaker.s.cloakList )
		cloaker.s.decloakList = clone cloakList

		foreach ( guy in guys )
		{
			//only do 5 distanceSqr / cansee checks per frame
			if ( index++ > 5 )
			{
				wait 0.1
				index = 0
				origin = cloaker.GetOrigin() + offset
			}

			bool shouldCloakGuy = CloakerShouldCloakGuy( cloaker, guy )

			if ( shouldCloakGuy )
				shouldCloakGuy = expect bool( shouldCloakGuyFunc( cloaker, guy ) )

			if ( shouldCloakGuy )
			{
				if ( guy in cloaker.s.decloakList )
					delete cloaker.s.decloakList[ guy ]

				if ( IsCloaked( guy ) )
					continue

				cloakList[ guy ] <- true
				CloakerCloaksGuy( guy )
			}
		}

		foreach ( entity guy, value in cloaker.s.decloakList )
		{
			// any guys still in the decloakList shouldn't be decloaked ... if alive.
			Assert( guy in cloakList )
			delete cloakList[ guy ]

			if ( IsAlive( guy ) )
				CloakerDeCloaksGuy( guy )
		}

		float endTime = Time()
		float elapsedTime = endTime - startTime
		if ( elapsedTime < waitTime )
			wait waitTime - elapsedTime
	}
}

void function CloakerCloaksGuy( guy )
{
	guy.SetCloakDuration( 2.0, -1, 0 )
	EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_START_SFX )
	EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX )
	guy.Minimap_Hide( TEAM_IMC, null )
	guy.Minimap_Hide( TEAM_MILITIA, null )
}

void function CloakerDeCloaksGuy( guy )
{
	guy.SetCloakDuration( 0, 0, 1.5 )
	StopSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX )
	guy.Minimap_AlwaysShow( TEAM_IMC, null )
	guy.Minimap_AlwaysShow( TEAM_MILITIA, null )
}

bool function CloakerShouldCloakGuy( entity cloaker, entity guy )
{
	if ( !IsAlive( guy ) )
		return false

	return true
}