global function Leeching_Init

global function DoLeechAnimEvent
global function DoLeech
global function TryLeechAbortCallback
global function StartLeechingProgress
global function StopLeechingProgress
global function EnableLeeching
global function DisableLeeching
global function MarvinWeaponsFree
global function GetLeechedEnts
global function DataKnifeSuccessSounds
global function DataKnifeCanceledSounds

global function GetTeamLeechedEnts

// _leeching.nut
// sets up global stuff for leeching
global const MARVIN_EMOTE_SOUND_HAPPY 	= "diag_spectre_gs_LeechEnd_01_1"
global const MARVIN_EMOTE_SOUND_SAD 	= "diag_spectre_gs_LeechAborted_01_1"
global const MARVIN_EMOTE_SOUND_PAIN 	= "diag_spectre_gs_LeechStart_01_1"

#if MP
global const bool WIFI_HACK_OVERFLOW_DIES = false  // Kill a random leeched ent from within the team, exluding the current target, to create a new team member when hacked
#elseif SP
global const bool WIFI_HACK_OVERFLOW_DIES = true
#endif

struct LeechFuncInfo
{
	string classname
	void functionref(entity,entity) DoLeech
	void functionref(entity,entity) LeechStart
	void functionref(entity,entity) LeechAbort
}

struct
{
	table<string, LeechFuncInfo> leechFuncs
} file

void function Leeching_Init()
{
	RegisterSignal( "OnLeeched" )
	RegisterSignal( "OnStartLeech" )
	RegisterSignal( "OnStopLeeched" )
	RegisterSignal( "EnableLeeching" )

	// Spectre leech
	LeechFuncInfo spectre
	spectre.classname 	= "npc_spectre"
	spectre.DoLeech 	= LeechGeneric
	spectre.LeechStart 	= LeechStartGeneric
	spectre.LeechAbort 	= LeechAbortGeneric
	file.leechFuncs[spectre.classname] <- spectre

	// Reaper leech
	LeechFuncInfo reaper
	reaper.classname 	= "npc_super_spectre"
	reaper.DoLeech 		= LeechGeneric
	reaper.LeechStart 	= LeechStartGeneric
	reaper.LeechAbort 	= LeechAbortGeneric
	file.leechFuncs[reaper.classname] <- reaper

	// Drone leech
	LeechFuncInfo drone
	drone.classname 	= "npc_drone"
	drone.DoLeech 		= LeechGeneric
	drone.LeechStart 	= LeechStartGeneric
	drone.LeechAbort 	= LeechAbortGeneric
	file.leechFuncs[drone.classname] <- drone

	// Gunship leech
	LeechFuncInfo gunship
	gunship.classname 	= "npc_gunship"
	gunship.DoLeech 	= LeechGeneric
	gunship.LeechStart 	= LeechStartGeneric
	gunship.LeechAbort 	= LeechAbortGeneric
	file.leechFuncs[gunship.classname] <- gunship

	LeechFuncInfo relay
	relay.classname 	= "logic_relay"
	relay.DoLeech 		= Leech_LogicRelay
	file.leechFuncs[relay.classname] <- relay

	LeechFuncInfo physbox
	physbox.classname 	= "func_physbox"
	physbox.DoLeech 	= Leech_FuncPhysbox
	file.leechFuncs[physbox.classname] <- physbox
}

void function EnableLeeching( entity self )
{
	self.SetUsePrompts( "#DEFAULT_HACK_HOLD_PROMPT", "#DEFAULT_HACK_HOLD_PROMPT" )

	Leech_SetLeechable( self )
}

void function DisableLeeching( entity self )
{
	if ( !IsValid_ThisFrame( self ) )
		return

	self.SetUsePrompts( " ", " " )

	Leech_ClearLeechable( self )
}

void function StartLeechingProgress( entity self, entity leecher )
{
	self.Signal( "OnStartLeech" )
	leecher.Signal( "OnStartLeech" )
	self.ai.leechInProgress = true
	self.ai.leechStartTime = Time()

	TryLeechStartCallback( self, leecher )
}

void function StopLeechingProgress( entity self )
{
	self.ai.leechInProgress = false
	self.ai.leechStartTime = -1
}

// called when any entity gets leeched
void function DoLeechAnimEvent( entity self )
{
	entity leecher = expect entity( GetOptionalAnimEventVar( self, "leech_switchteam" ) )

	DoLeech( self, leecher )
}

void function DoLeech( entity self, entity leecher )
{
	if ( !IsLeechable( self ) )
		EnableLeeching( self )

	Assert( "s" in self, "Self " + self + " has no .s" )
	Assert( leecher )

	// DEPRECATED- no scripts are currently using the results.player functionality- slayback
	// logic_relays get Triggered when the object is leeched
	//local results = {}
	//results.player <- leecher
	//self.Signal( "OnLeeched", results )
	//leecher.Signal( "OnLeeched", results )

	Signal( self, "OnLeeched" )
	Signal( leecher, "OnLeeched" )

	//DisableLeeching( self )

	//_EnableLeechedPointMessage()

	if ( leecher.IsPlayer() )
	{
		if ( self.IsNPC() )
			self.SetBossPlayer( leecher )

		TableRemoveDeadByKey( leecher.p.leechedEnts )

		leecher.p.leechedEnts[ self ] <- self

		// this will kill a random leeched ent from within the team, exluding the current target.
		if ( WIFI_HACK_OVERFLOW_DIES )
			ReleaseLeechOverflow( leecher, self )
	}

	if ( self.IsNPC() )
	{
		SetTeam( self, leecher.GetTeam() )
		SetSquad( self, "" )
		self.SetAutoSquad()
		self.ClearPotentialThreatPos()
		self.DisableBehavior( "Assault" )

		foreach ( trigger in self.e.sonarTriggers )
		{
			OnSonarTriggerLeaveInternal( trigger, self )
		}

		#if DEV
		// if crosshair spawned, switch squad so he isn't mixed in a squad with opposing team spectres
		string squadname = expect string( self.kv.squadname )
		if ( squadname.find( "crosshairSpawnSquad" ) != null )
			self.SetSquad( "crosshairSpawnSquad_team_" + self.GetTeam() + "_" + self.GetClassName() )
		#endif
	}

	// call a class specific leeching function for custom behavior
	string targetCN = self.GetClassName()
	if ( targetCN in file.leechFuncs )
	{
		LeechFuncInfo info = file.leechFuncs[ targetCN ]

		// Assert( "DoLeech" in file.leechFuncs[ targetCN ] )  // not sure how to check legit functionref -slayback
		void functionref(entity,entity) classLeechingFunc = file.leechFuncs[ targetCN ].DoLeech
		thread classLeechingFunc( self, leecher )
	}
}

array<entity> function GetTeamLeechedEnts( int team )
{
	array<entity> players = GetPlayerArrayOfTeam( team )
	int totalCount = 0

	array<entity> leechedArray
	foreach ( player in players )
	{
		if ( IsValid( player ) && !player.IsBot() )
			leechedArray.extend( GetLeechedEnts( player ) )
	}

	return leechedArray
}

array<entity> function GetLeechedEnts( entity leecher = null )
{
	array<entity> ents

	foreach ( entity ent in leecher.p.leechedEnts )
	{
		if ( IsAlive( ent ) )
			ents.append( ent )
	}

	return ents
}

void function TryLeechStartCallback( entity self, entity leecher )
{
	string leechtargetCN = self.GetClassName()
	if ( leechtargetCN in file.leechFuncs )
	{
		if ( "LeechStart" in file.leechFuncs[ leechtargetCN ] )
		{
			void functionref(entity,entity) leechStartFunc = file.leechFuncs[ leechtargetCN ].LeechStart
			thread leechStartFunc( self, leecher )
		}
	}
}

void function TryLeechAbortCallback( entity self, entity leecher )
{
	string leechtargetCN = self.GetClassName()
	if ( leechtargetCN in file.leechFuncs )
	{
		if ( "LeechAbort" in file.leechFuncs[ leechtargetCN ] )
		{
			void functionref(entity,entity) leechAbortFunc = file.leechFuncs[ leechtargetCN ].LeechAbort
			thread leechAbortFunc( self, leecher )
		}
	}
}

void function DataKnifeSuccessSounds( entity player )
{
	EmitDifferentSoundsOnEntityForPlayerAndWorld( "dataknife_complete", "dataknife_complete_3p", player, player )
}

void function DataKnifeCanceledSounds( entity player )
{
	EmitDifferentSoundsOnEntityForPlayerAndWorld( "dataknife_aborted", "dataknife_aborted_3p", player, player )
}


// --- CLASS SPECIFIC LEECH FUNCTIONS ---
void function Leech_LogicRelay( entity self, entity leecher )
{
	Assert( self.GetClassName() == "logic_relay" )

	// logic_relays get Triggered when the object is leeched
	EntFire( self, "Trigger" )
}

void function Leech_FuncPhysbox( entity self, entity leecher )
{
	Assert( self.GetClassName() == "func_physbox" )

	EntFire( self, "FireUser1" )
}


void function MarvinWeaponsFree( entity self )
{
	Assert( IsAlive( self ), self + " is dead, not alive!" )

	// already have a weapon
	if ( !self.GetActiveWeapon() )
		return

	self.EndSignal( "OnStopLeeched" )
	self.EndSignal( "OnDeath" )
	self.EndSignal( "OnTakeWeapon" )

	OnThreadEnd(
		function () : ( self )
		{
			if ( !IsAlive( self ) )
				return
		}
	)

	// its combat, time to get the hate on
	EntFire( self, "UnholsterWeapon" )

	WaitForever()
}


void function LeechStart_Marvin( entity self, entity leecher )
{
	//self.SetSkin( 4 )
	EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_PAIN )
}

void function LeechAbort_Marvin( entity self, entity leecher )
{
	//self.SetSkin( 1 )  // happy
	EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_SAD )
}


// Spectre leech

void function Leech_Spectre( entity self, entity leecher )
{
	thread Leech_SpectreThread( self, leecher )
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void function Leech_SpectreThread( entity self, entity leecher )
{
	Assert( self.GetClassName() == "npc_spectre" )

	self.EndSignal( "OnDestroy" )
	self.EndSignal( "OnDeath" )
	self.EndSignal( "OnLeeched" )

	EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_HAPPY )

	Assert( leecher.IsPlayer() )

	leecher.EndSignal( "OnDestroy" )

	AddPlayerScore( leecher, "LeechSpectre" )

	float timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
	if ( PlayerHasServerFlag( leecher, SFLAG_HUNTER_SPECTRE ) )
		timerCredit *= 2.5
	DecrementBuildTimer( leecher, timerCredit )

	NPCFollowsPlayer( self, leecher )

	OnThreadEnd(
		function() : ( self, leecher )
		{
			// leecher is still connected so don't kill the spectre
			if ( IsValid( leecher ) && !IsDisconnected( leecher ) )
				return

			// leecher is disconnected so kill the spectre
			if ( IsAlive( self ) )
				self.Die()
		}
	)

	foreach ( callbackFunc in svGlobal.onLeechedCustomCallbackFunc )
	{
		callbackFunc( self, leecher )
	}

	WaitForever()
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void function LeechGeneric( entity self, entity leecher )
{
	thread LeechGenericThread( self, leecher )
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Run after the npc is successfully leeched
// HACK: Should make this used by all leeched ents to avoid copy/pasted duplication. Will switch Spectre over to use this soon
void function LeechGenericThread( entity self, entity leecher )
{
	Assert( IsValid( self ) )
	self.EndSignal( "OnDestroy" )
	self.EndSignal( "OnDeath" )
	self.EndSignal( "OnLeeched" )

	Assert( leecher.IsPlayer() )
	leecher.EndSignal( "OnDestroy" )

	string leechSound
	float timerCredit

	//--------------------------------------------
	// Handle class-specific stuff
	//---------------------------------------------
	switch ( self.GetClassName() )
	{
		case "npc_spectre":
			leechSound = MARVIN_EMOTE_SOUND_HAPPY
			AddPlayerScore( leecher, "LeechSpectre" )
			timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
			if ( PlayerHasServerFlag( leecher, SFLAG_HUNTER_SPECTRE ) )
				timerCredit *= 2.5
			break
		case "npc_super_spectre":
			leechSound = MARVIN_EMOTE_SOUND_HAPPY
			AddPlayerScore( leecher, "LeechSuperSpectre" )
			timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
			break
		case "npc_drone":
			leechSound = MARVIN_EMOTE_SOUND_HAPPY
			AddPlayerScore( leecher, "LeechDrone" )
			timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
			break
		case "npc_gunship":
			leechSound = MARVIN_EMOTE_SOUND_HAPPY
			timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
			break
		default:
			Assert( 0, "Unhandled hacked entity: " + self.GetClassName() )
	}

	EmitSoundOnEntity( self, leechSound )
	DecrementBuildTimer( leecher, timerCredit )

	// Multiplayer the leeched NPCs still follow the player, but in SP we don't want them to
	if ( IsMultiplayer() )
		NPCFollowsPlayer( self, leecher )

	//--------------------------------------------
	// Any leech custom callback funcs?
	//---------------------------------------------
	foreach ( callbackFunc in svGlobal.onLeechedCustomCallbackFunc )
	{
		callbackFunc( self, leecher )
	}

}

/////////////////////////////////////////////////////////////////////////////////////
void function LeechStartGeneric( entity self, entity leecher )
{
	string leechStartSound

	switch( self.GetClassName() )
	{
		case "npc_spectre":
			leechStartSound = MARVIN_EMOTE_SOUND_PAIN
			break
		case "npc_super_spectre":
			leechStartSound = MARVIN_EMOTE_SOUND_PAIN
			break
		case "npc_drone":
			leechStartSound = MARVIN_EMOTE_SOUND_PAIN
			break
		case "npc_gunship":
			leechStartSound = MARVIN_EMOTE_SOUND_PAIN
			break
	}
	Assert( leechStartSound != "", "Couldn't find leechStartSound for: " + self )

	EmitSoundOnEntity( self, leechStartSound )
}
/////////////////////////////////////////////////////////////////////////////////////
void function LeechAbortGeneric( entity self, entity leecher )
{
	string leechAbortSound

	switch( self.GetClassName() )
	{
		case "npc_spectre":
			leechAbortSound = MARVIN_EMOTE_SOUND_SAD
			break
		case "npc_super_spectre":
			leechAbortSound = MARVIN_EMOTE_SOUND_SAD
			break
		case "npc_drone":
			leechAbortSound = MARVIN_EMOTE_SOUND_SAD
			break
		case "npc_gunship":
			leechAbortSound = MARVIN_EMOTE_SOUND_SAD
			break
	}
	Assert( leechAbortSound != "", "Couldn't find leechAbortSound for: " + self )

	EmitSoundOnEntity( self, leechAbortSound )

}

// --- END CLASS SPECIFIC LEECH FUNCTIONS ---