#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
#include maps\mp\killstreaks\_orbital_util;

CONST_JUGG_EXO_SPEED_SCALE		  = 1; // animations are already 65% of normal player speed
CONST_JUGG_EXO_SPEED_SCALE_MANIAC = 1.15;
CONST_JUGG_EXO_TURRET_TOP_ARC = 55;
CONST_JUGG_EXO_TURRET_BOTTOM_ARC = 30;
CONST_JUGG_EXO_TURRET_HORZ_ARC = 180;
CONST_JUGG_EXO_ROCKET_SWARM_RELOAD_TIME = 10;
CONST_JUGG_EXO_ROCKET_RELOAD_TIME = 10;
CONST_JUGG_EXO_HEALTH = 125;
CONST_JUGG_EXO_HEALTH_HORDE = 300;
CONST_JUGG_EXO_LETHAL = "playermech_rocket_mp";
CONST_JUGG_EXO_TACTICAL = "playermech_rocket_swarm_mp";
CONST_JUGG_EXO_TACTICAL_MANIAC = "playermech_rocket_swarm_maniac_mp";
CONST_JUGG_EXO_TIMEOUT_SEC = 120;
CONST_JUGG_EXO_USABILITY_RADIUS_SQ = 6000;

HEAVY_EXO_PING_RANGE =				700;
HEAVY_EXO_PING_RANGE_SQ =			490000;
HEAVY_EXO_PING_DURATION =			1.5;
HEAVY_EXO_PING_THREAT_DURATION =	1.75;
MIN_TIME_BETWEEN_PINGS =			5;
PING_DURATION = 					10;

init()
{
	level.juggSettings = [];
	
	level.juggSettings[ "juggernaut_exosuit" ] = spawnStruct();
	level.juggSettings[ "juggernaut_exosuit" ].splashUsedName 		=	"used_juggernaut";
	level.juggSettings[ "juggernaut_exosuit" ].splashAttachmentName =	"callout_destroyed_heavyexoattachment";
	level.juggSettings[ "juggernaut_exosuit" ].splashWeakenedName 	=	"callout_weakened_heavyexoattachment";
	
	level._effect[ "green_light_mp" ] =			LoadFX( "vfx/lights/aircraft_light_wingtip_green" );
	level._effect[ "juggernaut_sparks" ] =		LoadFX( "vfx/explosion/bouncing_betty_explosion" );
	level._effect[ "jugg_droppod_open" ] =		LoadFX( "vfx/explosion/goliath_pod_opening");
	level._effect[ "jugg_droppod_marker" ] =	LoadFX( "vfx/unique/vfx_marker_killstreak_guide_goliath" );
	level._effect[ "exo_ping_inactive" ] =		LoadFX( "vfx/unique/exo_ping_inactive" );
	level._effect[ "exo_ping_active" ] =		LoadFX( "vfx/unique/exo_ping_active" );
	level._effect[ "goliath_death_fire" ] =			LoadFX( "vfx/fire/goliath_death_fire" );
	level._effect[ "goliath_self_destruct" ] =	LoadFX( "vfx/explosion/goliath_self_destruct" );
	level._effect[ "lethal_rocket_wv" ] =	LoadFX( "vfx/muzzleflash/playermech_lethal_flash_wv" );
	level._effect[ "swarm_rocket_wv" ] =	LoadFX( "vfx/muzzleflash/playermech_tactical_wv_run" );
	
	level.killstreakWieldWeapons["juggernaut_sentry_mg_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["iw5_juggernautrockets_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["iw5_exoxmgjugg_mp_akimbo"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["iw5_juggtitan45_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["iw5_exominigun_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["iw5_mechpunch_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["playermech_rocket_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["killstreak_goliathsd_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["orbital_carepackage_droppod_mp"] = "juggernaut_exosuit";
	level.killstreakWieldWeapons["heavy_exo_trophy_mp"] = "juggernaut_exosuit";
	level.killstreakFuncs["heavy_exosuit"] = ::tryUseHeavyExosuit;
	
/#
	SetDvarIfUninitialized( "scr_goliath_god", "0" );
#/
	
	// Coop Buddy Assistance VO
	game["dialog"][ "assist_mp_goliath" ] = "ks_goliath_joinreq";
	game["dialog"][ "copilot_mp_goliath" ] = "copilot_mp_goliath";
	
	// Offline VO
	game["dialog"][ "sntryoff_mp_exoai" ] = "sntryoff_mp_exoai";	
	game["dialog"][ "mancoff_mp_exoai" ] = "mancoff_mp_exoai";	
	game["dialog"][ "longoff_mp_exoai" ] = "longoff_mp_exoai";	
	game["dialog"][ "rcnoff_mp_exoai" ] = "rcnoff_mp_exoai";	
	game["dialog"][ "rcktoff_mp_exoai" ] = "rcktoff_mp_exoai";	
	game["dialog"][ "trphyoff_mp_exoai" ] = "trphyoff_mp_exoai";	
	game["dialog"][ "weakdmg_mp_exoai" ] = "weakdmg_mp_exoai";	
	
	level thread onPlayerConnect();
}

tryUseHeavyExosuit( lifeId, modules )
{	
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		if ( isdefined ( self.hordeGoliathPodInField ) || isdefined ( self.hordeGoliathController ) || isdefined ( self.hordeClassGoliathController ) )
		{
			self IPrintLnBold( &"KILLSTREAKS_HEAVY_EXO_IN_USE" );
			return false;
		}
	}
	
	result = self playerLaunchDropPod( modules );
	
	return result;		
}

resetWeapon()
{
	killstreakWeapon = getKillstreakWeapon( "heavy_exosuit" );
	self SwitchToWeapon( self getLastWeapon() );
	self maps\mp\killstreaks\_killstreaks::takeKillstreakWeaponIfNoDupe( killstreakWeapon );
}

canSetupStance()
{
	if ( self GetStance() == "prone" || self GetStance() == "crouch" )
		self SetStance( "stand" );
		
	self freezeControlsWrapper( true );
	waitTimeEnd = GetTime() + 1500;
	while ( GetTime() < waitTimeEnd && self GetStance() != "stand" )
		waitframe();
	self freezeControlsWrapper( false );
	return self GetStance() == "stand";
}

giveJuggernaut( juggType, modules ) // self == player or bot
{
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	// added this wait here because i think the disabling the weapons and re-enabling while getting the crate, 
	//	needs a little time or else we sometimes won't have a weapon in front of us after we get juggernaut
//	wait(0.05); 

	//	remove light armor if equipped
	if ( self maps\mp\perks\_perkfunctions::hasLightArmor() )
		maps\mp\perks\_perkfunctions::unsetLightArmor();

	//	remove explosive bullets if equipped
	if ( self _hasPerk( "specialty_explosivebullets" ) )
		self _unsetPerk( "specialty_explosivebullets" );

	self.maxHealth = CONST_JUGG_EXO_HEALTH;
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self.maxHealth = CONST_JUGG_EXO_HEALTH_HORDE + ( 25 * self.hordeArmor );
	self.health = self.maxHealth;
	self.attackerList = [];
	
	switch( juggType )
	{
		case "juggernaut_exosuit":
		default:
			speedScale = CONST_JUGG_EXO_SPEED_SCALE;
			juggClass = "juggernaut_exosuit";
			if ( !IsDefined( modules ) || array_contains( modules, "heavy_exosuit_maniac" ) )
			{
				speedScale = CONST_JUGG_EXO_SPEED_SCALE_MANIAC;
				juggClass = "juggernaut_exosuit_maniac";
			}
			
			self.juggMoveSpeedScaler = speedScale;
			self removeWeapons();
			hasHardline = IsDefined( self.perks["specialty_hardline"] );
			self maps\mp\gametypes\_class::giveAndApplyLoadout( self.pers["team"], juggClass, false, false );
			self maps\mp\gametypes\_playerlogic::streamClassWeapons( false, false, juggClass );
			self.isJuggernaut = true;
			self.moveSpeedScaler = speedScale;
			self givePerk( "specialty_radarjuggernaut", false );
			if ( hasHardline )
				self givePerk( "specialty_hardline", false );
			self thread playerSetupJuggernautExo( modules, juggType );
			self.saved_lastWeapon = self getWeaponsListPrimaries()[0];
			break;
	}

	self maps\mp\gametypes\_weapons::updateMoveSpeedScale();

	self disableWeaponPickup();

//	self thread juggernautSounds();
	if ( !IsDefined( modules ) || array_contains( modules, "heavy_exosuit_maniac" ) ) // is a maniac
		self PlaySound( "goliath_suit_up_mp" );
	else
		self PlaySound( "goliath_suit_up_mp" );
	
	self thread teamPlayerCardSplash( level.juggSettings[ juggType ].splashUsedName, self );	
	
	//	- giveLoadout() nukes action slot 4 (killstreak weapon)
	//	- it's usually restored after activating a killstreak but 
	//	  equipping juggernaut out of a box isn't part of killstreak activation flow
	//	- restore action slot 4 by re-updating killstreaks
//	self thread maps\mp\killstreaks\_killstreaks::updateKillstreaks( true );

	self thread juggRemover();

	level notify( "juggernaut_equipped", self );

	self maps\mp\_matchdata::logKillstreakEvent( "juggernaut", self.origin );
}

juggernautSounds()
{
	level endon ( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "jugg_removed" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	for ( ;; )
	{
		wait ( 3.0 );
		self playSound( "juggernaut_breathing_sound" );
	}
}

radarMover( portableRadar )
{
	level endon("game_ended");
	self endon( "disconnect" );
	self endon( "jugg_removed" );
	self endon( "jugdar_removed" );

	for( ;; )
	{
		portableRadar MoveTo( self.origin, .05 );
		wait (0.05);
	}
}


juggRemover()
{
	level endon("game_ended");
	self endon( "disconnect" );
	self endon( "jugg_removed" );

	self thread juggRemoveOnGameEnded();
	self waittill_any( "death", "joined_team", "joined_spectators", "lost_juggernaut" );

	self enableWeaponPickup();
	self.isJuggernaut = false;
	if ( isDefined( self.juggernautOverlay ) )
		self.juggernautOverlay destroy();

	self unsetPerk( "specialty_radarjuggernaut", true );

	if ( isDefined( self.personalRadar ) )
	{
		self notify( "jugdar_removed" );
		level maps\mp\gametypes\_portable_radar::deletePortableRadar( self.personalRadar );
		self.personalRadar = undefined;	
	}

	self notify( "jugg_removed" );
}

juggRemoveOnGameEnded()
{
	self endon( "disconnect" );
	self endon( "jugg_removed" );

	level waittill( "game_ended" );

	if( IsDefined( self.juggernautOverlay ) )
		self.juggernautOverlay destroy();
}

removeWeapons()
{
	self.primaryToRestore = self getLastWeapon();
	// since we're taking your weapons we need to keep track of the ammo for when we restore them
	foreach( weapon in self.weaponlist )
	{
		weaponTokens = getWeaponNameTokens( weapon );
		if( weaponTokens[0] == "alt" )
		{
			// we don't need to take the alt weapons but we do need to store the ammo for when we restore them
			self.restoreWeaponClipAmmo[ weapon ] = self GetWeaponAmmoClip( weapon );
			self.restoreWeaponStockAmmo[ weapon ] = self GetWeaponAmmoStock( weapon );
			continue;
		}

		self.restoreWeaponClipAmmo[ weapon ] = self GetWeaponAmmoClip( weapon );
		self.restoreWeaponStockAmmo[ weapon ] = self GetWeaponAmmoStock( weapon );
	}

	// now take the weapons
	self.weaponsToRestore = [];
	foreach( weapon in self.weaponlist )
	{
		weaponTokens = getWeaponNameTokens( weapon );
		if( weaponTokens[0] == "alt" )
			continue;
		
		if ( isKillstreakWeapon( weapon ) )
			continue;

		// keep a list of the weapons we take because self.weaponlist isn't reliable
		self.weaponsToRestore[ self.weaponsToRestore.size ] = weapon;
		
		self TakeWeapon( weapon );
	}
	
//	self DisableOffhandWeapons();
}

//restoreWeapons()
//{
//	if( !IsDefined( self.restoreWeaponClipAmmo ) ||
//		!IsDefined( self.restoreWeaponStockAmmo ) ||
//		!IsDefined( self.weaponsToRestore ) )
//		return;
//
//	altWeapons = [];
//	foreach( weapon in self.weaponsToRestore )
//	{
//		weaponTokens = getWeaponNameTokens( weapon );
//		if( weaponTokens[0] == "alt" )
//		{
//			// we don't need to give the alt weapons but we do need to restore the ammo
//			altWeapons[ altWeapons.size ] = weapon;
//			continue;
//		}
//
//		self _giveWeapon( weapon );
//		if( IsDefined( self.restoreWeaponClipAmmo[ weapon ] ) )
//			self SetWeaponAmmoClip( weapon, self.restoreWeaponClipAmmo[ weapon ] );
//		if( IsDefined( self.restoreWeaponStockAmmo[ weapon ] ) )
//			self SetWeaponAmmoStock( weapon, self.restoreWeaponStockAmmo[ weapon ] );
//	}
//
//	foreach( altWeapon in altWeapons )
//	{
//		if( IsDefined( self.restoreWeaponClipAmmo[ altWeapon ] ) )
//			self SetWeaponAmmoClip( altWeapon, self.restoreWeaponClipAmmo[ altWeapon ] );
//		if( IsDefined( self.restoreWeaponStockAmmo[ altWeapon ] ) )
//			self SetWeaponAmmoStock( altWeapon, self.restoreWeaponStockAmmo[ altWeapon ] );
//	}
//
//	self.restoreWeaponClipAmmo = undefined;
//	self.restoreWeaponStockAmmo = undefined;
//	self EnableOffhandWeapons();
//}

playerSetupJuggernautExo( modules, juggType ) // self == player
{
	data = SpawnStruct();
	self.heavyExoData = data;
	data.streakPlayer = self;
	data.hasCoopSentry = true;
	data.modules = modules;
	data.juggType = juggType;
	
/#
	data.hasCoopSentry = GetDvarInt( "scr_heavy_exo_turret", 1 ) == 1;
#/
	
	if ( IsDefined( modules ) ) 
	{
		data.hasRadar = array_contains( modules, "heavy_exosuit_radar" );
		data.hasManiac = array_contains( modules, "heavy_exosuit_maniac" );
		data.hasLongPunch = array_contains( modules, "heavy_exosuit_punch" );
		data.hasTrophy = array_contains( modules, "heavy_exosuit_trophy" );
		data.hasRockets = array_contains( modules, "heavy_exosuit_rockets" );
		data.hasExtraAmmo = array_contains( modules, "heavy_exosuit_ammo" );
	}
	else // from devgui
	{
		data.hasRadar = true;
		data.hasManiac = true;
		data.hasLongPunch = false;
		data.hasTrophy = true;
		data.hasRockets = true;
		data.hasExtraAmmo = true;
	}
	
	modulesOn = 0;
	// Must match powers of 2 defined in exoSuitAttachments.csv
	if ( data.hasRockets ) 		modulesOn += 1;
	if ( data.hasLongPunch ) 	modulesOn += 2;
	if ( data.hasRadar ) 		modulesOn += 4;
	if ( data.hasTrophy ) 		modulesOn += 8;
	if ( data.hasManiac ) 		modulesOn += 16;
	if ( data.hasCoopSentry ) 	modulesOn += 32;
	self SetClientOmnvar( "ui_exo_suit_modules_on", modulesOn );
	
	self playerAllowPowerSlide( false, "heavyexo" );
	
	if ( !data.hasManiac )
	{
		self playerAllowDodge( false, "heavyexo" );
		self playerAllowBoostJump( false, "heavyexo" );
		self playerAllowHighJump( false, "heavyexo" );
		self playerAllowHighJumpDrop( false, "heavyexo" );
	}
	self _disableUsability();
	self AllowJump( false );
	self AllowCrouch( false );
	self AllowLadder( false );
	self AllowMantle( false );
	self.inLivePlayerKillstreak = true;
	self.mechHealth = CONST_JUGG_EXO_HEALTH;
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self.mechHealth = self.maxhealth;	
	self SetDemiGod( true );
	self SetClientOmnvar( "ui_exo_suit_health", 1 );

	self playerSetJuggExoModel( data );
	self thread playerShowJuggernautHud( data );
	self thread playerCleanupOnDeath( data );
	self thread playerCleanupOnOther();
	self thread playerRocketsAndSwarmWatcher();
	self thread playermech_invalid_weapon_watcher();
	self thread playerHandleBootupSequence();
	self thread play_goliath_death_fx();
	self thread playermech_watch_emp_grenade();
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self thread playerMechTimeout();
	
	if ( data.hasCoopSentry )
	{
		// TODO: Disabled co-op turret for Gamescom, need to come up with something for ship.
//		turret = setupCoopTurret( data, self );
//		if ( level.teamBased )
//			level thread handleCoopJoining( data, self );
	}
	
	if ( data.hasRadar )
		level thread setupRadar( self, data );
	
	if ( data.hasManiac )
	{
		level thread setupManiac( self );
		set_mech_chaingun_state( "offline" );
	}
	else
	{
		self thread playerHandleBarrel();
		set_mech_chaingun_state( "ready" );
	}
	
	if ( data.hasLongPunch )
	{
		level thread setupLongPunch( self, data );
		set_mech_rocket_state( "ready" );
		self thread playermech_monitor_rocket_recharge();
	}
	else
	{
		set_mech_rocket_state( "offline" );
		if ( !data.hasManiac )
			self DisableOffhandWeapons();
	}
	
	if ( data.hasTrophy )
		level thread setupTrophy( self, data );
	
	if ( data.hasRockets )
	{
		level thread setupRocketSwarm( self, data );
		set_mech_swarm_state( "ready" );
		self thread playermech_monitor_swarm_recharge();
	}
	else
	{
		self DisableOffhandSecondaryWeapons();
		set_mech_swarm_state( "offline" );
	}
	
//	if ( data.hasExtraAmmo && !data.hasManiac )
//	{
//		weapon = "iw5_exominigun_mp";
//		maxAmmo = WeaponMaxAmmo( weapon );
//		self SetWeaponAmmoStock( weapon, maxAmmo );
//	}
	
	level thread delaySetWeapon( self );

	//Allow the cancelling of the goliath early (for instance, destroying it during the manticore event or jumping off of a cliff)
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "horde_cancel_goliath" );
	
	// Wait until the player is finished switching weapons to allow self destruct. Without this wait, players can self destruct without dying.
	wait( 5 );
	
	if ( IsDefined(self) )
		self thread self_destruct_goliath();
}

playerHandleBootupSequence()
{
	self.goliathBootupSequence = true;
	
	wait 4.16; // this is the length of the pullout animation
	
	self.goliathBootupSequence = undefined;
}

juggernautModifyDamage( victim, eAttacker, iDamage, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, eInflictor )
{
	if ( !victim isJuggernaut() )
		return iDamage;
	
	finalDamage = iDamage;
	
	if ( IsDefined( sMeansOfDeath ) && sMeansOfDeath == "MOD_FALLING" )
		finalDamage = 0;
	
	if ( IsDefined( sWeapon ) && sWeapon == "boost_slam_mp" )
		finalDamage = 20;
	
	if ( IsDefined( eAttacker ) && IsDefined( victim ) && eAttacker == victim && IsDefined( sWeapon ) && ( sWeapon == "iw5_juggernautrockets_mp" || sWeapon == "playermech_rocket_mp" ) )
		finalDamage = 0;
	
	if ( IsDefined( victim.goliathBootupSequence ) && victim.goliathBootupSequence )
	{
		if( IsDefined( level.isHorde ) && level.isHorde && sMeansOfDeath == "MOD_TRIGGER_HURT" && victim touchingBadTrigger() )
		{
			//If we've hit a death trigger (cliff jump), then make sure we deal enough damage to kill the player
			finalDamage = 10000;
		}
		else
		{
			finalDamage = 0;
		}
	}
	
	if ( IsDefined( eAttacker ) && !maps\mp\gametypes\_weapons::friendlyFireCheck( victim, eAttacker ) )
		finalDamage = 0;
	
/#
	if ( GetDvar( "scr_goliath_god", "0" ) != "0" )
		finalDamage = 0;
#/
	
	if ( finalDamage > 0 )
	{
		// HACK: have to handle friendly fire here because cac_modified_damage() is only called in Callback_PlayerDamage_internal() when it's not friendly fire.
		if ( attackerIsHittingTeam( victim, eAttacker ) )
		{
			if ( IsDefined( level.juggernautMod ) )
			{
				finalDamage *= level.juggernautMod;
			}
			else
			{
				finalDamage *= 0.08;
			}
		}
		
		if ( IsDefined( sHitLoc ) && sHitLoc == "head" )
		{
			finalDamage *= 4.0;
		}
		
		if ( IsDefined( sWeapon ) && sWeapon == "killstreak_goliathsd_mp" && IsDefined( eAttacker ) && IsDefined( victim ) && eAttacker == victim )
		{
			finalDamage = victim.mechHealth + 1;
		}
		
		if ( IsDefined( sWeapon ) && sWeapon == "nuke_mp" && IsDefined( eAttacker ) && IsDefined( victim ) && eAttacker != victim )
		{
			finalDamage = victim.mechHealth + 1;
		}
		
		victim.mechHealth -= finalDamage;
		if ( isdefined ( level.isHorde ) && level.isHorde )
			victim SetClientOmnvar( "ui_exo_suit_health", victim.mechHealth / victim.maxhealth );
		else
			victim SetClientOmnvar( "ui_exo_suit_health", victim.mechHealth / CONST_JUGG_EXO_HEALTH );
		
		if ( IsDefined( eAttacker ) && IsPlayer( eAttacker ) )
		{
			if ( IsDefined( sHitLoc ) && sHitLoc == "head" )
			{
				eAttacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "headshot" );		// TODO: need a new hitmarker for a juggernaut headshot.
			}
			else
			{
				eAttacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "hitjuggernaut" );
			}
			
			if( victim maps\mp\gametypes\_damage::isNewAttacker(eAttacker) )
			{
				victim.attackerList[victim.attackerList.size] = eAttacker;
			}
		}
		
		if ( victim.mechHealth < 0 )
		{
			if ( isdefined ( level.isHorde ) && level.isHorde )
			{
				self maps\mp\_snd_common_mp::snd_message( "goliath_self_destruct" );
				PlayFX( getfx( "goliath_self_destruct" ), self.origin, AnglesToUp(self.angles) );
				self thread [[level.hordeHandleJuggDeath]]();
			}
			else
				victim thread playerKillHeavyExo( vPoint, eAttacker, sMeansOfDeath, sWeapon, eInflictor );
		}
	}
	
	return int( finalDamage );
}

playerKillHeavyExo( point, attacker, meansOfDeath, weapon, eInflictor )
{
	self notify( "killHeavyExo" );
	self _enableUsability();
	self AllowJump( true );
	self AllowCrouch( true );
	self AllowLadder( true );
	self AllowMantle( true );
	self SetDemiGod( false );
	self.isJuggernaut = false;
	damage = 1001;
	
	if ( !IsDefined( point ) )
		point = self.origin;
	
	damaged = false;
	
	if ( IsDefined( weapon ) && IsDefined( attacker ) && IsDefined( meansOfDeath ) && IsDefined( eInflictor ) )
		damaged = self DoDamage( damage, point, attacker, eInflictor, meansOfDeath, weapon );
	else if ( IsDefined( weapon ) && IsDefined( attacker ) && IsDefined( meansOfDeath ) )
		damaged = self DoDamage( damage, point, attacker, undefined, meansOfDeath, weapon );
	else if ( IsDefined( attacker ) && IsDefined( meansOfDeath ) )
		damaged = self DoDamage( damage, point, attacker, undefined, meansOfDeath );
	else if ( IsDefined( attacker ) )
		damaged = self DoDamage( damage, point, attacker, undefined );
	else
		damaged = self DoDamage( damage, point );
	
	Assert( damaged == true );
}

delaySetWeapon( player )
{
	player endon( "death" );
	player endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	killstreakWeapon = getKillstreakWeapon( "heavy_exosuit" );
	player maps\mp\killstreaks\_killstreaks::takeKillstreakWeaponIfNoDupe( killstreakWeapon );
	player GiveWeapon( "iw5_exominigun_mp" );
	player SwitchToWeapon( "iw5_exominigun_mp" );
	player notify( "waitTakeKillstreakWeapon" );
	waitframe();
	player SetPlayerMech( 1 );
	player DisableWeaponSwitch();
}

playerCleanupOnDeath( data ) // self == player
{
	self endon( "disconnect" );
	
	self waittill( "death", attacker, meansOfDeath, weapon );
	
	if( IsDefined(attacker) && IsPlayer(attacker) && (attacker != self) )
	{
		attacker incPlayerStat( "goliath_destroyed", 1 );
		level thread maps\mp\gametypes\_rank::awardGameEvent( "goliath_destroyed", attacker, weapon, self, meansOfDeath );
	}
	
	if ( !isdefined ( level.isHorde ) )
		self maps\mp\_events::checkVandalismMedal( attacker );
		
	self.inLivePlayerKillstreak = undefined;
	self.mechHealth = undefined;
	
	self playerReset( data );	
}

playerCleanupOnOther()		// self = player
{
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "joined_team" );
	self endon( "faux_spawn" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
	
	level waittill_any( "game_ended" );
	
	self playerResetOmnvars();
}

playerReset( data )
{
	self notify( "lost_juggernaut" );
	self notify( "exit_mech" );
	
	self playerResetOmnvars();
	self playerAllowDodge( true, "heavyexo" );
	self playerAllowPowerSlide( true, "heavyexo" );
	self playerAllowBoostJump( true, "heavyexo" );
	self playerAllowHighJump( true, "heavyexo" );
	self EnableOffhandSecondaryWeapons();
	self EnableOffhandWeapons();
	self EnableWeaponSwitch();
	
	self SetPlayerMech( 0 );
	
	self.restoreWeaponClipAmmo = undefined;
	self.restoreWeaponStockAmmo = undefined;
	self.juggernautWeak = undefined;
	self.heavyExoData = undefined;
	if ( IsDefined( self.juggernautAttachments ) )
		self.juggernautAttachments = undefined;
	
	foreach ( element in data.hud )
	{
		if ( IsDefined( element ) )
		{
			element.textOffline = undefined;
			element.type = undefined;
			element Destroy();
		}
	}
}

playerResetOmnvars()
{
	self SetClientOmnvar( "ui_exo_suit_enabled", false );
	self SetClientOmnvar( "ui_exo_suit_modules_on", 0 );
	self SetClientOmnvar( "ui_exo_suit_health", 0 );
	self SetClientOmnvar( "ui_exo_suit_recon_cd", 0 );
	self SetClientOmnvar( "ui_exo_suit_punch_cd", 0 );
	self SetClientOmnvar( "ui_exo_suit_rockets_cd", 0 );
	
	self SetClientOmnvar( "ui_playermech_swarmrecharge", 0 );
	self SetClientOmnvar( "ui_playermech_rocketrecharge", 0 );
}

playerSetJuggExoModel( data ) // self == player
{
	self DetachAll();
	
	self SetModel( "npc_exo_armor_mp_base" );
	self Attach( "head_hero_cormack_sentinel_halo" );
	self SetViewModel( "vm_view_arms_mech_mp" );
//	self SetClothType("cloth");
	
	if ( ( isdefined ( data ) && !data.hasManiac ) || isdefined ( level.isHorde ) )
		self Attach( "npc_exo_armor_minigun_handle", "TAG_HANDLE" );
	
	// Because bots and agents don't handle death anims right now, we'll just hide their bodies on death and play an explosion effect.
	if ( IsAI( self ) )
		self.hideOnDeath = true;
	
	self notify( "goliath_equipped" );
}

playerHandleBarrel()
{
	self endon( "death" );
	self endon( "disconnect" );
	if ( IsDefined ( level.isHorde ) && level.isHorde )
		self endon ( "becameSpectator" );
	
	self thread playerCleanupBarrel();
	
	self NotifyOnPlayerCommand( "goliathAttack", "+attack" );
	self NotifyOnPlayerCommand( "goliathAttackDone", "-attack" );
	
	self.barrelLinker = Spawn( "script_model", self GetTagOrigin( "tag_barrel" ) );
	self.barrelLinker SetModel( "generic_prop_raven" );
	self.barrelLinker LinkToSynchronizedParent( self, "tag_barrel", ( 12.7, 0, -2.9 ), ( 90, 0, 0 ) );
	
	self.barrel = Spawn( "script_model", self.barrelLinker GetTagOrigin( "j_prop_1" ) );
	self.barrel SetModel( "npc_exo_armor_minigun_barrel" );
	self.barrel LinkToSynchronizedParent( self.barrelLinker, "j_prop_1", ( 0, 0, 0 ), ( -90, 0, 0 ) );
	
	if ( isdefined ( level.isHorde ) && level.isHorde && isPlayer ( self ) )
		self.barrel HudOutlineEnable ( 5, true );
		

	self.barrelLinker ScriptModelPlayAnimDeltaMotion( "mp_generic_prop_spin_02" );
	self.barrelLinker ScriptModelPauseAnim( true );
	
	while ( true )
	{
		self waittill( "goliathAttack" );
		self.barrelLinker ScriptModelPauseAnim( false );
		
		self waittill( "goliathAttackDone" );
		self.barrelLinker ScriptModelPauseAnim( true );
	}
}

playerCleanupBarrel()
{
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self waittill_any( "death", "disconnect", "becameSpectator" );
	else
		self waittill_any( "death", "disconnect" );
	
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self.barrel HudOutlineDisable();
	self.barrel Delete();
	self.barrelLinker Delete();
}

playerRocketsAndSwarmWatcher()
{
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
		
	while ( true )
	{
		self waittill( "grenade_pullback", weaponName );
		
		if( weaponName == CONST_JUGG_EXO_LETHAL )
		{
			// Rocket
			self notify( "mech_rocket_pullback" );
			self waittill( "grenade_fire", missileEnt, weaponName );
			self notify( "mech_rocket_fire", missileEnt );	
		}
		else if ( weaponName == CONST_JUGG_EXO_TACTICAL || weaponName == CONST_JUGG_EXO_TACTICAL_MANIAC )
		{	
			// Swarm Missiles
			self notify( "mech_swarm_pullback" );
			self waittill( "grenade_fire", missileEnt, weaponName );	
			self notify( "mech_swarm_fire", missileEnt.origin );
			missileEnt delete();
		}
		
		waitframe();
	}
}

// -------------------------------------------------------------------------------
// Attachments/Modules

setupCoopTurret( data, player )
{
	startOrigin = player GetTagOrigin( "tag_turret" );
	
	turret = spawnAttachment( "juggernaut_sentry_mg_mp", "npc_heavy_exo_armor_turret_base", startOrigin, 200, player, &"KILLSTREAKS_HEAVY_EXO_SENTRY_LOST" );
	turret SetMode( "sentry_offline" );
	turret SetSentryOwner( player );
	turret SetLeftArc( CONST_JUGG_EXO_TURRET_HORZ_ARC );
	turret SetRightArc( CONST_JUGG_EXO_TURRET_HORZ_ARC );
	turret SetTopArc( CONST_JUGG_EXO_TURRET_TOP_ARC );
	turret SetBottomArc( CONST_JUGG_EXO_TURRET_BOTTOM_ARC );
	turret SetDefaultDropPitch( 0.0 );
	turret SetTurretModeChangeWait( true );
	turret MakeUnusable();
	turret MakeTurretSolid();
	turret.rocketTurret = false;
	turret.energyTurret = false;
	turret.turretType = "mg_turret";
	turret.isSentry = false;
	turret.stunned = false;
	turret.nextTracer = 5;
	turret.heatLevel = 0;
	turret.baseOwner = player;
	if ( level.teamBased )
		turret setTurretTeam( player.team );
//	turret thread maps\mp\killstreaks\_remoteturret::sentry_heatMonitor( "juggernaut_sentry_mg_mp", 4, 0.07 );
//	turret thread maps\mp\killstreaks\_remoteturret::sentry_attackTargets( ::turretShoot );
	turret make_entity_sentient_mp( player.team );
	turret maps\mp\killstreaks\_autosentry::addToTurretList( turret GetEntityNumber() );
	turret thread maps\mp\killstreaks\_remoteturret::turret_watchDisabled();
	turret LinkTo( player, "tag_turret", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	
	turret.effect = spawnAttachmentEffect( startOrigin, player );
	turret.effect LinkTo( turret, "tag_player", ( 29, -7, -6 ), ( 0, 0, 0 ) );
	turret.effect Hide();
	
	data.coopTurret = turret;
	
	thread stopTurret( data, turret, player );
	thread handleCoopShooting( data, turret, player );
	thread handleTurretOnPlayerDone( data, turret, player );
	
	return turret;
}

stopTurret( data, turret, player )
{
	turret waittill( "death" );
	
	if ( IsDefined( turret ) )
	{
		turret.isSentry = false; // so autosentry does not start up again
		player notify( "turretDead" );
		removeCoopTurretBuddy( data );
		stopFXOnAttachment( turret, getfx( "green_light_mp" ), true );
		turret PlaySound( "sentry_explode" );
		turret thread maps\mp\killstreaks\_remoteturret::sentry_stopAttackingTargets();
		turret maps\mp\killstreaks\_autosentry::removeFromTurretList( turret GetEntityNumber() );
		turret SetMode( "sentry_offline" );
		turret.damagecallback = undefined;
		turret SetCanDamage( false );
		turret SetDamageCallbackOn( false );
		turret FreeEntitySentient();
		turret setDefaultDropPitch( 35 );
		turret SetSentryOwner( undefined );
		level thread doTurretDeathEffects( turret );
	}
}

handleCoopShooting( data, turret, player )
{
	turret endon( "death" );
	
	fireTime = weaponFireTime( "juggernaut_sentry_mg_mp" );
	
	while ( true )
	{
		if ( !IsDefined( turret.remoteControlled ) || !turret.remoteControlled )
		{
			waitframe();
			continue;
		}
		
		if ( turret.owner AttackButtonPressed() && !turret IsTurretOverheated() )
		{
			turret turretShootBlank( turret.baseOwner );
			wait fireTime;
			continue;
		}
		
		waitframe();
	}
}

turretShoot() // self == turret
{
	self ShootTurret();
	
	self turretShootBlank( self.baseOwner );
}

turretShootBlank( showTo ) // self == turret
{
	aimOrigin = self GetTagOrigin( "tag_flash" );
	aimDir = AnglesToForward( self GetTagAngles( "tag_flash" ) );
	aimEnd = aimOrigin + ( aimDir * 1000 );
	
	drawTracer = false;
	self.nextTracer--;
	if ( self.nextTracer <= 0 )
	{
		drawTracer = true;
		self.nextTracer = 5;
	}	
	
	ShootBlank( aimOrigin, aimEnd, "juggernaut_sentry_mg_mp", drawTracer, showTo );
}

doTurretDeathEffects( turret )
{
	turret playSound( "sentry_explode" );		

	PlayFXOnTag( getFx( "sentry_explode_mp" ), turret, "tag_aim" );
	wait ( 1.5 );
	
	if ( !IsDefined( turret ) )
		return;
	
	turret PlaySound( "sentry_explode_smoke" );
	for ( i = 0; i < 10; i++ )
	{
		PlayFXOnTag( getFx( "sentry_smoke_mp" ), turret, "tag_aim" );
		wait ( 0.4 );
		
		if ( !IsDefined( turret ) )
			return;
	}
}

handleTurretOnPlayerDone( data, turret, player )
{
	thread attachmentDeath( data, turret, player );

	waittillAttachmentDone( player );

	stopFXOnAttachment( turret, getfx( "green_light_mp" ) );
	turret maps\mp\killstreaks\_autosentry::removeFromTurretList( turret GetEntityNumber() );
	turret.isSentry = false; // so auto sentry does not start up again
	player notify( "turretDead" );
	removeCoopTurretBuddy( data );
	turret Delete();
}

setupRadar( player, data )
{
	radarOrigin = player GetTagOrigin( "tag_recon_back" );
		
	radar = spawnAttachment( "radar", "npc_heavy_exo_armor_recon_back_base", radarOrigin, undefined, player );
	radar LinkTo( player, "tag_recon_back", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	
	player thread playerHandleRadarPing( data, radar );
	
//	thread attachmentDeath( data, radar, player );
	
	waittillAttachmentDone( player );
	
//	attachmentExplode( radar, player, "radar" );
	waitframe();
	radar Delete();
}

playerHandleRadarPing( data, radar ) // self == player
{
	radar endon( "death" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "faux_spawn" );
	self endon( "joined_team" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon ( "becameSpectator" );
	
	if ( !IsBot( self ) )
		self NotifyOnPlayerCommand( "juggernautPing", "weapnext" );
	
	//turn on disabled state vfx
	PlayFXOnTag( getfx( "exo_ping_inactive" ), self, "J_SpineUpper" );
	while ( true )
	{
		self waittill( "juggernautPing" );
		
		activate_exo_ping();
		
		self SetClientOmnvar( "ui_exo_suit_recon_cd", 1 );
		wait PING_DURATION;
		
		deactivate_exo_ping();

		waitAttachmentCooldown( MIN_TIME_BETWEEN_PINGS, "ui_exo_suit_recon_cd" );
	}
}

activate_exo_ping()	// self = player
{
	self thread stop_exo_ping();
	
	self SetPerk( "specialty_exo_ping", true, false );
	
	// temp activation sound
	self PlayLocalSound( "mp_exo_cloak_activate" );
		
	self.highlight_effect = maps\mp\_threatdetection::detection_highlight_hud_effect_on( self, -1 );
	
	KillFXOnTag( getfx( "exo_ping_inactive" ), self, "J_SpineUpper" );
	PlayFXOnTag( getfx( "exo_ping_active" ), self, "J_SpineUpper" );
	//ping overlay played in code to account for killcam
}

deactivate_exo_ping()		// self = player
{
	self UnSetPerk( "specialty_exo_ping", true );
	
	// temp deactivation sound
	self PlayLocalSound( "mp_exo_cloak_deactivate" );
	
	if ( IsDefined( self.highlight_effect ) )
		maps\mp\_threatdetection::detection_highlight_hud_effect_off( self.highlight_effect );
	
	KillFXOnTag( getfx( "exo_ping_active" ), self, "J_SpineUpper" );
	PlayFXOnTag( getfx( "exo_ping_inactive" ), self, "J_SpineUpper" );
}

stop_exo_ping()
{
	self endon( "disconnect" );
	
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self waittill_any( "death", "faux_spawn", "joined_team", "becameSpectator" );
	else
		self waittill_any( "death", "faux_spawn", "joined_team" );
	
	self UnSetPerk( "specialty_exo_ping", true );
	
	if ( IsDefined( self.highlight_effect ) )
		maps\mp\_threatdetection::detection_highlight_hud_effect_off( self.highlight_effect );
	
	KillFXOnTag( getfx( "exo_ping_active" ), self, "J_SpineUpper" );
}

setupManiac( player )
{
	legsOrigin = player GetTagOrigin( "tag_maniac_l" );
	speedAttachment = spawnAttachment( "speedAttachment", "npc_heavy_exo_armor_maniac_l_base", legsOrigin, undefined, player );
	speedAttachment LinkTo( player, "tag_maniac_l", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	
	legsOrigin = player GetTagOrigin( "tag_maniac_r" );
	speedAttachmentR = spawnAttachment( "speedAttachment", "npc_heavy_exo_armor_maniac_r_base", legsOrigin, undefined, player );
	speedAttachmentR LinkTo( player, "tag_maniac_r", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	
	backOrigin = player GetTagOrigin( "tag_jetpack" );
	speedAttachmentB = spawnAttachment( "speedAttachment", "npc_heavy_exo_armor_jetpack_base", backOrigin, undefined, player );
	speedAttachmentB LinkTo( player, "tag_jetpack", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	
	waittillAttachmentDone( player );
	
	attachmentExplode( speedAttachment, player, "maniac", speedAttachmentR );
	attachmentExplode( speedAttachmentB, player, "maniac" );
	waitframe();
	speedAttachment Delete();
	speedAttachmentR Delete();
	speedAttachmentB Delete();
}

setupLongPunch( player, data )
{
	player SetLethalWeapon( CONST_JUGG_EXO_LETHAL );
	player GiveWeapon( CONST_JUGG_EXO_LETHAL );

	tag = "tag_origin"; // "tag_hand_armor_l";

	player thread playerWatchNoobTubeUse( data );
	
	waittillAttachmentDone( player );
}

playerWatchNoobTubeUse( data ) // self == player
{
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
		
	while ( true )
	{
		self waittill( "mech_rocket_fire", missileEnt );
		
		//play lethal rocket muzzleflash
		PlayFXOnTag( getfx("lethal_rocket_wv"), self, "TAG_WEAPON_RIGHT" );
		
		thread reloadRocket( self, data );
	}
}

reloadRocket( player, data )
{
	player endon( "death" );
	player endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
//	thread playRocketReloadSound( player );
	waitAttachmentCooldown( CONST_JUGG_EXO_ROCKET_RELOAD_TIME, "ui_exo_suit_punch_cd" );
}

playRocketReloadSound( player )
{
	self PlayLocalSound( "orbitalsupport_reload_40mm" );
}

waitAttachmentCooldown( duration, omnvar )
{
	waited = 0;
	while( true )
	{
		wait 0.05;
		waited += 0.05;
		coolDown = 1 - ( waited / duration );
		coolDown = clamp( coolDown, 0, 1 );
		self SetClientOmnvar( omnvar, coolDown );
		if ( coolDown <= 0 )
		{
			break;
		}
	}
}

setupTrophy( player, data )
{
	chestOrigin = player GetTagOrigin( "j_spine4" );
	
	trophy1 = spawnAttachment( "trophy", "npc_heavy_exo_armor_trophy_l_base", chestOrigin, undefined, player );
	trophy1.stunned = false;
	trophy1.ammo = 1;
	trophy1 LinkTo( player, "tag_trophy_l", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	trophy1.weaponName = "heavy_exo_trophy_mp";
	trophy1 thread maps\mp\gametypes\_equipment::trophyActive( player, undefined, true, trophy1.weaponName );
	trophy1 thread maps\mp\gametypes\_equipment::trophyAddlaser( 12, ( 90, 90, 270 ) ); // , ( 150, 90, 270 ), true ); // 90 -90 270
	trophy1 thread maps\mp\gametypes\_equipment::trophySetMinDot( -0.087, ( 90, 90, 270 ) ); // cos 95
	level.trophies[level.trophies.size] = trophy1;

	trophy2 = spawnAttachment( "trophy", "npc_heavy_exo_armor_trophy_r_base", chestOrigin, undefined, player );
	trophy2.stunned = false;
	trophy2.ammo = 1;
	trophy2 LinkTo( player, "tag_trophy_r", ( 0, 0, 0 ), ( 0, 0, 0 ) );
	trophy2.weaponName = "heavy_exo_trophy_mp";
	trophy2 thread maps\mp\gametypes\_equipment::trophyActive( player, undefined, true, trophy2.weaponName );
	trophy2 thread maps\mp\gametypes\_equipment::trophyAddlaser( 6, ( 260, 90, 270 ) ); // , ( 180, 90, 270 ), false ); // ( 260, -90, 270 )
	trophy2 thread maps\mp\gametypes\_equipment::trophySetMinDot( -0.087, ( 260, 90, 270 ) ); // cos 95
	level.trophies[level.trophies.size] = trophy2;
	
	trophy1.otherTrophy = trophy2;
	trophy2.otherTrophy = trophy1;

	waittillAttachmentDone( player );
	
	trophy1 notify( "trophyDisabled" );
	trophy2 notify( "trophyDisabled" );
//	attachmentExplode( trophy1, player, "trophy", trophy2 );
	waitframe();
	if ( IsDefined( trophy1.laserEnt ) )
	    trophy1.laserEnt Delete();
	if ( IsDefined( trophy2.laserEnt ) )
	    trophy2.laserEnt Delete();
	trophy1 Delete();
	trophy2 Delete();
}

trophyStunBegin()
{
	if ( self.stunned )
		return;
	
	self.stunned = true;
	self.otherTrophy.stunned = true;
	stunEnt = Spawn( "script_model", self.origin );
	stunEnt SetModel( "tag_origin" );
	PlayFXOnTag( getfx( "mine_stunned" ), stunEnt, "tag_origin" );
	self thread trophyMoveStunEnt( stunEnt );
	
	self waittill_notify_or_timeout( "death", 3 );
	self notify( "stunEnd" );
	
	StopFXOnTag( getfx( "mine_stunned" ), stunEnt, "tag_origin" );
	waitframe();
	stunEnt Delete();
	if ( IsDefined( self ) )
	{
		self.stunned = false;
		self.otherTrophy.stunned = false;
	}
}

trophyMoveStunEnt( stunEnt )
{
	self endon( "death" );
	self endon( "stunEnd" );
	
	while ( true )
	{
		stunEnt.origin = self.origin;
		waitframe();
	}
}

setupRocketSwarm( player, data )
{
	swarmWeapon = CONST_JUGG_EXO_TACTICAL;
	if ( data.hasManiac )
		swarmWeapon = CONST_JUGG_EXO_TACTICAL_MANIAC;
	player SetTacticalWeapon( swarmWeapon );
	player GiveWeapon( swarmWeapon );
	
	tag = "tag_origin"; // "tag_missile_pack";
	startOrigin = player GetTagOrigin( tag );
	
	rocketAttachment = spawnAttachment( "rocketAttachment", "npc_heavy_exo_armor_missile_pack_base", startOrigin, undefined, player );
	rocketAttachment.lockedTarget = false;
	rocketAttachment.reloading = false;
	rocketAttachment.rockets = [];
	rocketAttachment.icons = [];
	rocketAttachment LinkTo( player, tag, ( 0, 0, 0 ), ( 0, 0, 0 ) );
	rocketAttachment Hide(); // temp
	player.rocketAttachment = rocketAttachment;
	
	thread scanForRocketEnemies( rocketAttachment, player );
	player thread playerWatchRocketUse( rocketAttachment, data );
	
	waittillAttachmentDone( player, rocketAttachment );
	
//	attachmentExplode( rocketAttachment, player, "rockets" );
	waitframe();
	rocketAttachment Delete();
	player.rocketAttachment = undefined;
}

scanForRocketEnemies( rocketAttachment, streakPlayer )
{
	streakPlayer endon( "death" );
	streakPlayer endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		streakplayer endon( "becameSpectator" );
		
	while ( true )
	{
		waitframe();
		
		if ( rocketAttachment.reloading || rocketAttachment.rockets.size > 0 || rocketAttachment.lockedTarget )
			continue;
		
		bestEnemy = getBestEnemy( streakPlayer, 4 );
		
//		if ( IsDefined( enemy ) )
//			Sphere( enemy GetEye(), 10, ( 0, 1, 0 ) );
		
		if ( IsDefined( bestEnemy ) )
		{
			if ( !IsDefined( rocketAttachment.enemyTarget ) || rocketAttachment.enemyTarget != bestEnemy )
				thread markPlayerAsRocketTarget( rocketAttachment, streakPlayer, bestEnemy );
		}
		else if ( IsDefined( rocketAttachment.enemyTarget ) )
		{
			rocketAttachment notify( "unmark" );
			rocketAttachment.enemyTarget = undefined;
		}
	}
}

playerIsRocketSwarmReloading()
{
	return ( IsDefined( self.rocketAttachment ) && IsDefined( self.rocketAttachment.reloading ) && self.rocketAttachment.reloading );
}

playerIsRocketSwarmTargetLocked()
{
	return ( IsDefined( self.rocketAttachment ) && IsDefined( self.rocketAttachment.enemyTarget ) );
}

getBestEnemy( player, numSightTracesTotal )
{
	cos32 = 0.8433914458; // view fov
	playerDir = AnglesToForward( player GetPlayerAngles() );
	playerEye = player GetEye();
	bestEnemy = undefined;
	
	bestEnemies = [];
	foreach ( guy in level.participants )
	{
		if ( guy.team == player.team )
			continue;
		
		if ( !isReallyAlive( guy ) )
			continue;
		
		enemyEye = guy GetEye();
		dirToEnemy = VectorNormalize( enemyEye - playerEye );
		dot = VectorDot( playerDir, dirToEnemy );
		if ( dot > cos32 )
		{
			bestEnemies[bestEnemies.size] = guy;
			guy.dot = dot;
			guy.checked = false;
		}
	}
	
	if ( bestEnemies.size == 0 )
		return;
	
	numSightTraces = 0;
	while ( numSightTraces < numSightTracesTotal && numSightTraces < bestEnemies.size )
	{
		nextEnemy = getHighestDot( bestEnemies );
		nextEnemy.checked = true;
		
		start = playerEye;
		end = nextEnemy GetEye();
		passed = SightTracePassed( start, end, true, player, nextEnemy );
		if ( passed )
		{
			bestEnemy = nextEnemy;
//			Line( start + ( 0, 0, -5 ), end, ( 0, 1, 0 ) );
			break;
		}
//		else
//		{
//			Line( start + ( 0, 0, -5 ), end, ( 1, 0, 0 ) );
//		}
		numSightTraces++;
	}
	
	foreach ( guy in level.participants )
	{
		guy.dot = undefined;
		guy.checked = undefined;
	}
	
	return bestEnemy;
}

getHighestDot( enemies )
{
	if ( enemies.size == 0 )
		return;
	
	bestEnemy = undefined;
	bestDot = 0;
	
	foreach ( enemy in enemies )
	{
		if ( !enemy.checked && enemy.dot > bestDot )
		{
			bestEnemy = enemy;
			bestDot = enemy.dot;
		}
	}
	
	return bestEnemy;
}

playerWatchRocketUse( rocketAttachment, data ) // self == player
{
	rocketAttachment endon( "death" );
		
	while ( true )
	{
		self waittill( "mech_swarm_fire", origin );
		
		if ( rocketAttachment.reloading || rocketAttachment.lockedTarget )
		{
			waitframe();
			continue;
		}
		
//		if ( IsDefined( rocketAttachment.enemyTarget ) )
		{
			thread handleLockedTarget( rocketAttachment, data );
			thread reloadRocketSwarm( rocketAttachment, self, data );
			thread fireRocketSwarm( rocketAttachment, self, origin );
		}
	}
}

handleLockedTarget( rocketAttachment, data )
{
	rocketAttachment endon( "death" );
	
	rocketAttachment.lockedTarget = true;
	rocketAttachment notify( "lockedTarget" );

	waittillRocketsExploded( rocketAttachment );
		
	if ( IsDefined( rocketAttachment ) )
	{
		rocketAttachment.lockedTarget = false;
		rocketAttachment.enemyTarget = undefined;
	}
}

fireRocketSwarm( rocketAttachment, streakPlayer, origin )
{
	playerDir = AnglesToForward( streakPlayer GetPlayerAngles() );
	playerRight = AnglesToRight( streakPlayer GetPlayerAngles() );
	offsets = [ ( 0, 0, 50 ), ( 0, 0, 20 ), ( 10, 0, 0 ), ( 0, 10, 0 ) ];
	
	//3P muzzleflash fx
	PlayFXOnTag( getfx("swarm_rocket_wv"), streakPlayer, "TAG_ROCKET4" );

	
	for ( i = 0; i < 4; i++ )
	{
		//push it out from the player and to the left
		initOrigin = origin + ( playerDir * 20 ) + ( playerRight * -30 );
		fireDirection = playerDir + random_vector( 0.2 );
		
		rocket = MagicBullet( "iw5_juggernautrockets_mp", initOrigin, initOrigin + fireDirection, streakPlayer );
		rocketAttachment.rockets = array_add( rocketAttachment.rockets, rocket );
		rocket thread rocketTargetEnt( rocketAttachment, rocketAttachment.enemyTarget, offsets[i] );
		rocket thread rocketDestroyAfterTime( 7 );
	}
}

rocketTargetEnt( rocketAttachment, target, offset ) // self == rocket
{
	rocketAttachment endon( "death" );
	
	if ( IsDefined( target ) )
		self Missile_SetTargetEnt( target, offset );
	self waittill( "death" );
	
	rocketAttachment.rockets = array_remove( rocketAttachment.rockets, self );
}

rocketDestroyAfterTime( time )
{
	self endon( "death" );
	
	wait time;
	
	self Delete();
}

reloadRocketSwarm( rocketAttachment, player, data )
{
	rocketAttachment endon( "death" );
	
	rocketAttachment.reloading = true;
	
//	thread playRocketSwarmReloadSound( rocketAttachment, player, CONST_JUGG_EXO_ROCKET_SWARM_RELOAD_TIME );
	waitAttachmentCooldown( CONST_JUGG_EXO_ROCKET_SWARM_RELOAD_TIME, "ui_exo_suit_rockets_cd" );
	
	rocketAttachment.reloading = false;
}

playRocketSwarmReloadSound( rocketAttachment, player, reloadTime )
{
	rocketAttachment endon( "death" );

	missileCount = 3;
	
	self PlayLocalSound( "warbird_missile_reload_bed" );
	
	wait 0.5;

	for ( i = 0; i < missileCount; i++ )
	{
		self PlayLocalSound( "warbird_missile_reload" );
		wait( reloadTime / missileCount );
	}
}

markPlayerAsRocketTarget( rocketAttachment, streakPlayer, markedPlayer )
{
	markedPlayer endon( "disconnect" );
	rocketAttachment notify( "mark" );
	rocketAttachment endon( "mark" );
	rocketAttachment endon( "unmark" );
	
	offset = ( 0, 0, 60 );
	
	entNum = markedPlayer GetEntityNumber();
	
	rocketAttachment.enemyTarget = markedPlayer;
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		markedPlayer HudOutlineEnableForClient( streakPlayer, 1, 0 );
		streakPlayer.markedForMech [ streakPlayer.markedForMech.size ] = markedPlayer;
	}
	else
		markedPlayer HudOutlineEnableForClient( streakPlayer, 4, 0 );
		

	thread cleanupRocketTargetIcon( rocketAttachment, markedPlayer, streakPlayer );
	
	rocketAttachment waittill( "lockedTarget" );
	
	markedPlayer HudOutlineEnableForClient( streakPlayer, 0, 0 );
	
	waittillRocketsExploded( rocketAttachment );
	
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		if ( level.currentAliveEnemyCount < 3  )
		{
			if ( ( level.objDefend && distancesquared ( streakPlayer.origin, level.currentDefendLoc.origin ) > 640000 ) )
				markedPlayer HudOutlineEnableForClient ( streakPlayer, level.enemyOutlineColor, false ); 
			streakPlayer.markedForMech = array_remove ( streakPlayer.markedForMech, markedPlayer );
		}
		else
		{
			markedPlayer HudOutlineDisableForClient( streakPlayer );
			streakPlayer.markedForMech = array_remove ( streakPlayer.markedForMech, markedPlayer );
		}
	}
	else
		markedPlayer HudOutlineDisableForClient( streakPlayer );
}

cleanupRocketTargetIcon( rocketAttachment, markedPlayer, streakPlayer )
{
	markedPlayer endon( "disconnect" );
	
	waittillUnmarkPlayerAsRocketTarget( rocketAttachment );
	
	if ( isdefined ( level.isHorde ) && level.isHorde && isdefined ( streakPlayer ) )
	{
		if ( level.currentAliveEnemyCount < 3  )
		{
			if ( ( level.objDefend && distancesquared ( streakPlayer.origin, level.currentDefendLoc.origin ) > 640000 ) )
				markedPlayer HudOutlineEnableForClient ( streakPlayer, level.enemyOutlineColor, false ); 
			streakPlayer.markedForMech = array_remove ( streakPlayer.markedForMech, markedPlayer );
		}
		else
		{
			markedPlayer HudOutlineDisableForClient( streakPlayer );
			streakPlayer.markedForMech = array_remove ( streakPlayer.markedForMech, markedPlayer );
		}
	}
	else if ( IsDefined( streakPlayer ) )
		markedPlayer HudOutlineDisableForClient( streakPlayer );
}

waittillUnmarkPlayerAsRocketTarget( rocketAttachment )
{
	rocketAttachment.enemyTarget endon( "death" );
	
	rocketAttachment waittill_any( "death", "mark", "unmark" );
}

waittillRocketsExploded( rocketAttachment )
{
	wait 0.1;
	
	while ( IsDefined( rocketAttachment ) && rocketAttachment.rockets.size > 0 )
		waitframe();
}

// -----------------------------------------------------------------------------------------------------
// Utilities

waittillAttachmentDone( player, attachment1, attachment2 )
{
	player endon( "disconnect" );
	player endon( "death" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	if ( IsDefined( attachment1 ) )
		attachment1 endon( "death" );

	if ( IsDefined( attachment2 ) )
		attachment2 endon( "death" );
	
	player waittill( "forever" );
}

delayPlayFX( ent, fx )
{
	ent endon( "death" );
	waitframe();
	waitframe();
	PlayFXOnTag( fx, ent, "tag_origin" );
}

stopFXOnAttachment( attachment, fx, doSparksFx )
{
	if ( IsDefined( attachment.effect ) )
	{
		StopFXOnTag( fx, attachment.effect, "tag_origin" );
		if ( IsDefined( doSparksFx ) && doSparksFx )
			PlayFX( getfx( "juggernaut_sparks" ), attachment.effect.origin );
		attachment.effect Delete();
	}
}

attachmentDeath( data, attachment, player, attachment2 )
{
	player endon( "death" );
	player endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	if ( IsDefined( attachment2 ) )
		attachment2 endon( "death" );
	
	attachment waittill( "death", attacker, meansOfDeath, weapon );
	
	if ( IsDefined( attacker ) && IsPlayer( attacker ) )
	{
		splash = level.juggSettings[ data.juggType ].splashAttachmentName;
		if ( IsSubStr( attachment.attachmentType, "weakSpot" ) )
			splash = level.juggSettings[ data.juggType ].splashWeakenedName;
		teamPlayerCardSplash( splash, attacker );
	}
}

attachmentExplode( attachment1, player, type, attachment2 )
{
	if ( IsDefined( player ) )
	{
		if ( IsAlive( player ) )
		{
			player thread playerPlayAttachmentDialog( attachment1.attachmentType );
		}
		if ( IsDefined( attachment1 ) )
			PlayFX( getfx( "juggernaut_sparks" ), attachment1.origin );
		if ( IsDefined( attachment2 ) )
			PlayFX( getfx( "juggernaut_sparks" ), attachment2.origin );
		player PlaySound( "sentry_explode" );
	}
}

HideFromPlayer( playerToHideFrom ) // self == entity
{
	self Hide();
	foreach ( player in level.players )
	{
		if ( player != playerToHideFrom )
			self ShowToPlayer( player );
	}
}

HideFromPlayers( playersToHideFrom ) // self == entity
{
	self Hide();
	foreach ( player in level.players )
	{
		if ( !array_contains( playersToHideFrom, player ) )
			self ShowToPlayer( player );
	}
}

spawnAttachment( type, modelName, startOrigin, health, player, eventString )
{
	attachment = undefined;
	if ( IsSubStr( type, "sentry" ) )
		attachment = SpawnTurret( "misc_turret", startOrigin, type );
	else
		attachment = Spawn( "script_model", startOrigin );
	attachment SetModel( modelName );
	attachment.attachmentType = type;
	if ( IsDefined( health ) )
	{
		attachment.health = health;
		attachment.maxhealth = attachment.health;
		attachment.damagecallback = ::handleAttachmentDamage;
		if ( IsDefined( eventString ) )
			attachment thread handleAttachmentDeath( type, player, eventString );
		attachment SetDamageCallbackOn( true );
		//attachment SetCanDamage( true );
	}
	attachment HideFromPlayer( player );
	
	attachment.owner = player;
	if ( level.teamBased )
		attachment.team = player.team;
	
	// for debug
/#
	if ( !IsDefined( player.juggernautAttachments ) )
		player.juggernautAttachments = [];
	else
		player.juggernautAttachments = array_removeUndefined( player.juggernautAttachments );
	player.juggernautAttachments[ player.juggernautAttachments.size ] = attachment;
#/
	return attachment;
}

spawnAttachmentEffect( startOrigin, player, isWeakSpot )
{
	if ( !IsDefined( isWeakSpot ) )
		isWeakSpot = false;
	
	effect = Spawn( "script_model", startOrigin );
	effect SetModel( "tag_origin" );
	effect HideFromPlayer( player );
	thread delayPlayFX( effect, getfx( "green_light_mp" ) );
	return effect;
}

handleAttachmentDeath( type, player, eventString )
{
	if ( type == "weakSpotHead" )
		return;
	
	level endon( "game_ended" );
	
	self waittill( "death", attacker, meansOfDeath, weapon );
	
	if ( !IsDefined( attacker ) || !IsPlayer( attacker ) || ( IsDefined( player ) && attacker == player ) )
		return;
	
	level thread maps\mp\gametypes\_rank::awardGameEvent( "heavy_exo_attachment", attacker, undefined, undefined, undefined, eventString );
}

handleAttachmentDamage( inflictor, attacker, damage, iDFlags, meansOfDeath, weapon, point, direction_vec, hitLoc, timeOffset, modelIndex, partName )
{
	if ( !IsDefined( self.lastTimeDamaged ) )
		self.lastTimeDamaged = 0;
	
	finalDamage = damage;
	
	// don't allow people to destroy equipment on their team if FF is off
	if ( IsDefined( attacker ) && !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) || attacker == self.owner || self.lastTimeDamaged == GetTime() )
	{
		finalDamage = 0;
	}
	else
	{
		if ( IsDefined( weapon ) && weapon == "boost_slam_mp" && damage > 10 )
			finalDamage = 10;
		
		if ( isMeleeMOD(meansOfDeath) )
			finalDamage += self.maxHealth;
	
		if ( IsDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
			self.wasDamagedFromBulletPenetration = true;

		self.wasDamaged = true;
		
		self.damageFade = 0.0;
	
		if( IsPlayer( attacker ) )
		{
			if ( attacker _hasPerk( "specialty_armorpiercing" ) )
			{
				finalDamage = finalDamage * level.armorPiercingMod;			
			}
			
			attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "juggernautAttachment" );
			attacker notify( "hitHeavyExoAttachment" );
			self.lastAttacker = attacker;
		}
		
		if( IsDefined( weapon ) )
		{
			shortWeapon = maps\mp\_utility::strip_suffix( weapon, "_lefthand" );

			switch( shortWeapon )
			{
			case "ac130_105mm_mp":
			case "ac130_40mm_mp":
			case "stinger_mp":
			case "remotemissile_projectile_mp":
				self.largeProjectileDamage = true;
				finalDamage = self.maxHealth + 1;
				break;

			case "artillery_mp":
			case "stealth_bomb_mp":
				self.largeProjectileDamage = false;
				finalDamage += ( damage * 4 );
				break;

			case "bomb_site_mp":
			case "emp_grenade_mp":
			case "emp_grenade_var_mp":
			case "emp_grenade_killstreak_mp":
				self.largeProjectileDamage = false;
				finalDamage = self.maxHealth + 1;
				break;
		}
			
			maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
		}
	}

	self.lastTimeDamaged = GetTime();
	self FinishEntityDamage( inflictor, attacker, finalDamage, iDFlags, meansOfDeath, weapon, point, direction_vec, hitLoc, timeOffset, modelIndex, partName );
}

random_vector( num )
{
	return( RandomFloat( num ) - num * 0.5, RandomFloat( num ) - num *0.5, RandomFloat( num ) - num * 0.5 );
}

// -----------------------------------------------------------------------------------------------------
// Coop Logic

handleCoopJoining( data, player )
{
	while ( true )
	{
		id = maps\mp\killstreaks\_coop_util::promptForStreakSupport( player.team, &"MP_JOIN_HEAVY_EXO", "heavy_exosuit_coop_offensive", "assist_mp_goliath", "copilot_mp_goliath", player );
		
		level thread watchForJoin( id, player, data );
		
		result = waittillPromptComplete( player, "buddyJoinedStreak" ); // , "turretBeingHacked", "turretStunned" );
		
		maps\mp\killstreaks\_coop_util::stopPromptForStreakSupport( id );
		
		if ( !IsDefined( result ) )
			return;
		
		result = waittillPromptComplete( player, "buddyLeftCoopTurret" ); // , "turretBeingHacked", "turretStunned" );
		
		if ( !IsDefined( result ) )
			return;
	}
}

waittillPromptComplete( player, text, text2, text3 )
{
	player endon( "death" );
	player endon( "disconnect" );
	player endon( "turretDead" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	return player waittill_any_return_no_endon_death( text, text2, text3 );
}

waittillTurretStunComplete( data, player )
{
	player endon( "death" );
	player endon( "disconnect" );
	player endon( "turretDead" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	while ( true )
	{
		waitframe();
		
		if ( data.coopTurret.stunned || data.coopTurret.directHacked )
			continue;
		
		return true;
	}
}

//playerNotifyOnTurretStun( data )
//{
//	self endon( "death" );
//	self endon( "disconnect" );
//	self endon( "turretDead" );
//	data.coopTurret endon( "death" );
//	
//	while ( true )
//	{
//		result = data.coopTurret waittill_any_return_no_endon_death( "stunned", "beingHacked" );
//		
//		if ( result == "stunned" )
//			self notify( "turretStunned" );
//		else // if ( result == "beingHacked" )
//			self notify( "turretBeingHacked" );
//		
//	}
//}

watchForJoin( id, player, data )
{
	player endon( "disconnect" );
	player endon( "death" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	buddy = maps\mp\killstreaks\_coop_util::waittillBuddyJoinedStreak( id );
	
	player notify( "buddyJoinedStreak" );
	buddy thread playerRemoteCoopTurret( data );
}

playerRemoteCoopTurret( data )
{
	self endon( "disconnect" );
	data.coopTurret endon( "death" );
	data.streakPlayer endon( "death" );
	data.streakPlayer endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		self endon( "becameSpectator" );
		data.streakPlayer endon( "becameSpectator" );
	}
	
	data.coopTurret SetSentryOwner( undefined );
	data.coopTurret SetSentryOwner( self );
	data.coopTurret.owner = self;
	data.coopTurret.effect HideFromPlayers( [ self, data.streakPlayer ] );
	self.using_remote_turret = true;
	data.coopTurret maps\mp\killstreaks\_remoteturret::startUsingRemoteTurret( CONST_JUGG_EXO_TURRET_HORZ_ARC, CONST_JUGG_EXO_TURRET_HORZ_ARC, CONST_JUGG_EXO_TURRET_TOP_ARC, CONST_JUGG_EXO_TURRET_BOTTOM_ARC, true );
	self thread removeCoopTurretBuddyOnDisconnect( data );
	
	data.coopTurret maps\mp\killstreaks\_remoteturret::waittillRemoteTurretLeaveReturn();
	
	removeCoopTurretBuddy( data );
}

removeCoopTurretBuddyOnDisconnect( data )
{
	data.coopTurret endon( "removeCoopTurretBuddy" );
	
	self waittill( "disconnect" );
	
	thread removeCoopTurretBuddy( data );
}

removeCoopTurretBuddy( data )
{
	Assert( IsDefined( data.coopTurret ) );
	
	if ( !IsDefined( data.coopTurret.remoteControlled ) )
	    return;
	
	data.coopTurret notify( "removeCoopTurretBuddy" );
	
	data.coopTurret.remoteControlled = undefined;
		
	buddy = data.coopTurret.owner;
	if ( IsDefined( buddy ) )
	{
		buddy.using_remote_turret = undefined;
		data.coopTurret maps\mp\killstreaks\_remoteTurret::stopUsingRemoteTurret( false );
	}
	else if ( IsAlive( data.coopTurret ) )
	{
//		data.coopTurret thread maps\mp\killstreaks\_remoteturret::sentry_attackTargets( ::turretShoot );
	}
	
	buddy EnableWeaponSwitch();
	
	if ( IsDefined( data.streakPlayer ) && isReallyAlive( data.streakPlayer ) )
	{
		if ( IsDefined( data.coopTurret.effect ) )
			data.coopTurret.effect Hide();
		data.coopTurret SetSentryOwner( undefined );
		data.coopTurret SetSentryOwner( data.streakPlayer );
		data.coopTurret.owner = data.streakPlayer;
		data.streakPlayer notify( "buddyLeftCoopTurret" );
	}
}

playerShowJuggernautHud( data ) // self == player
{
	data.hud = [];
		
	self thread playerWatchEMP( data );
	
	createJuggernautOverlay( data );
}

createJuggernautOverlay( data ) // self == player
{
//	if ( getDvarInt( "camera_thirdPerson" ) )
//		return;
	self SetClientOmnvar( "ui_exo_suit_enabled", true );
	self thread playermech_state_manager();
}

playerWatchEMP( data )
{
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	while ( true )
	{
		self waittill_any( "emp_grenaded", "applyEMPkillstreak", "directHackStarted" );
		
		foreach ( element in data.hud )
			element.alpha = 0;
		
		while ( true )
		{
			self waittill_any( "empGrenadeTimedOut", "removeEMPkillstreak", "directHackTimedOut" );
			waitframe();
			
			if ( playerShouldShowHUD() )
				break;
		}
		
		foreach ( element in data.hud )
		{
			if ( element.type != "rocketReload" )
			{
				element FadeOverTime( 0.5 );
				element.alpha = 1;
			}
		}
	}
}

playerShouldShowHUD()
{
	return ( ( !IsDefined( self.empGrenaded ) || !self.empGrenaded ) || ( !IsDefined( self.empOn ) || !self.empOn ) );
}

playerPlayAttachmentDialog( attachmentType )
{
	dialogRef = undefined;
	switch ( attachmentType )
	{
		case "juggernaut_sentry_mp_mp":
			dialogRef = "sntryoff_mp_exoai";
			break;
		case "speedAttachment":
			dialogRef = "mancoff_mp_exoai";
			break;
		case "punchAttachment":
			dialogRef = "longoff_mp_exoai";
			break;
		case "radar":
			dialogRef = "rcnoff_mp_exoai";
			break;
		case "rocketAttachment":
			dialogRef = "rcktoff_mp_exoai";
			break;
		case "trophy":
			dialogRef = "trphyoff_mp_exoai";
			break;
		default:
			dialogRef = "weakdmg_mp_exoai";
			break;
	}
	
	self leaderDialogOnPlayer( dialogRef );
}

playerLaunchDropPod( modules )			// self = player or bot
{
	outsideNode = self playerGetOutsideNode();
	if ( !IsDefined( outsideNode ) )
	{
		self thread playerPlayInvalidPositionEffect( getfx( "ocp_ground_marker_bad" ) );
		self SetClientOmnvar( "ui_invalid_goliath", 1 );
		return false;
	}
	
	self thread fireDropPod( outsideNode, modules );
	
	return true;
}

dropPodMoveNearbyAllies( player ) // self = drop pod
{
	if ( !IsDefined( self ) || !IsDefined( player ) )
		return;

	// Don't let unresolved_collision_nearest_node() below calculate the nodes
	// A min radius is necessary because the pod uses patches and an invalid node could be selected
	self.unresolved_collision_nodes = GetNodesInRadius( self.origin, 300, 80, 200 );

	foreach ( character in level.characters )
	{
		if ( !IsAlive( character ) )
			continue;

		if ( IsAlliedSentient( character, player ) )
		{
			// Any allies that are touching the pod should be teleported out of its way
			// IsTouching() doesn't work all the time because the pod uses patches
			// if ( character IsTouching( self ) )
			if ( DistanceSquared( self.origin, character.origin ) < CONST_JUGG_EXO_USABILITY_RADIUS_SQ )
			{
				self maps\mp\_movers::unresolved_collision_nearest_node( character, true );
			}
		}
	}
}

fireDropPod( node, modules )		// self = player or bot
{
	startPos = self playerGetOrbitalStartPos( node );
	targetPos = node.origin;
	
	podrocket = MagicBullet( "orbital_carepackage_droppod_mp", startPos, targetPos, self );
	podrocket.team = self.team;
	
	podrocket.killCamEnt = Spawn( "script_model", (0,0,0) );
	podrocket.killCamEnt LinkToSynchronizedParent( podrocket, "tag_origin", (0,0,200), (0,10,10) );
	podrocket.killCamEnt.targetname = "killCamEnt_goliath_droppod";
	podrocket.killCamEnt SetScriptMoverKillCam( "missile" );
	podrocket thread maps\mp\_load::deleteDestructibleKillCamEnt();
	
	curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
	Objective_Add( curObjID, "invisible", ( 0, 0, 0 ) );
	Objective_Position( curObjID, targetPos );
	Objective_State( curObjID, "active" );
	shaderName = "compass_waypoint_farp";
	Objective_Icon( curObjID, shaderName );
	
	marker = Spawn( "script_model", targetPos + ( 0, 0, 5 ) );
	marker.angles = ( -90, 0, 0 );
	marker SetModel( "tag_origin" );
	marker Hide();
	marker ShowToPlayer( self );
	PlayFXOnTag( getfx( "jugg_droppod_marker" ), marker, "tag_origin" );
	
	maps\mp\killstreaks\_orbital_util::addDropMarker( marker );
	
	//Track if this is a goliath called as a built-in heavy scoretreak in co-op so we can start the cool-down on pod-timeout
	//and also start cooldown on the owner when another player picks up the built-in scorestreak goliath
	hordeClassGoliath = false;
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		if ( self.killstreakIndexWeapon == 1 )
		{
			self notify ( "used_horde_goliath" );
			hordeClassGoliath = true;
			self.hordeClassGoliathPodInField = true;
		}
		self.hordeGoliathPodInField = true;		
	}
	
	podrocket waittill( "death" );

	// pod may not have landed on a valid spawn location, so adjust target
	targetPos = GetGroundPosition( podrocket.origin + ( 0, 0, 8 ), 20 );
	
	thread destroy_nearby_turrets( targetPos );

	marker Hide();
	
	Earthquake( 0.4, 1, targetPos, 800 );
	PlayRumbleOnPosition( "artillery_rumble", targetPos );
	
	StopFXOnTag( getfx( "jugg_droppod_marker" ), marker, "tag_origin" );
	
	useEnt = Spawn( "script_model", targetPos );
	useEnt.angles = ( 0, 0, 0 );
	useEnt createCollision( targetPos );
	useEnt.targetname = "care_package";
	useEnt.droppingToGround = false;
	useEnt.curObjID = curObjID;
	
	goliathPodModel = Spawn( "script_model", targetPos );
	goliathPodModel.angles = ( 90, 0, 0 );
	goliathPodModel.targetname = "goliath_pod_model";
	goliathPodModel SetModel( "vehicle_drop_pod" );
	goliathPodModel thread handle_goliath_drop_pod_removal( useEnt );
	
	if ( IsDefined( self ) )
		useEnt.owner = self;
	useEnt.crateType = "juggernaut";
	useEnt.dropType = "juggernaut";
	useEnt thread control_goliath_usability();
	useEnt SetHintString( &"KILLSTREAKS_HEAVY_EXO_PICKUP" );
	useEnt thread maps\mp\killstreaks\_airdrop::crateOtherCaptureThink();		// these threads might never end.
	useEnt thread maps\mp\killstreaks\_airdrop::crateOwnerCaptureThink();
	useEnt thread useGoliathUpdater();
	
	// Handle kill triggers?
	
	// Moving platforms. 
	data 			= SpawnStruct();
	//data.emptyMech 	= emptyMech;
	data.useEnt 	= useEnt;
	data.playDeathFx = true;
	data.deathOverrideCallback = ::movingPlatformDeathFunc;
	data.touchingPlatformValid = ::movingPlatformTouchValid;
	
	useEnt thread maps\mp\_movers::handle_moving_platforms( data );
	
	useEnt thread handle_goliath_drop_pod_timeout( hordeClassGoliath );
	
	useEnt dropPodMoveNearbyAllies( self );
	
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		if ( level.zombiesStarted || level.teamEMPed["allies"] )
			useEnt deleteGoliathPod();
		else
			useent thread delete_goliath_drop_pod_for_event();
	}
		
	activator = useEnt playerWaittillGoliathActivated();
	
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		//this is for tracking death of a goliath called as a built-in class goliath for the heavy in 
		//co-op to start the cool-down regardless of who picked up the goliath.  No matter who takes 
		//control, we wait for this goliath to die to start the cool-down
		if ( isdefined ( activator ) && activator != self )
		{
			if ( hordeClassGoliath )
			{
			activator.hordeClassGoliathOwner = self;
			self.hordeClassGoliathController = activator;
			}
			else
			{
				activator.hordeGoliathOwner = self;
				self.hordeGoliathController = activator;
			}
			activator [[level.lastStandSaveLoadoutInfo]]( true, true, true );
		}			
		else
		{
			//save our loadout info so we get the right stuff back when we come back from spectator
			self [[level.lastStandSaveLoadoutInfo]]( true, true, true ); 
		}
		self.hordeClassGoliathPodInField = undefined;
		self.hordeGoliathPodInField = undefined;
	}
		
	if ( IsDefined( activator ) && IsAlive( activator ) )
	{
		activator.enteringGoliath = true;
		activator TakeAllWeapons();
		activator GiveWeapon( "iw5_combatknifegoliath_mp", 0, false, 0, true );
		activator SwitchToWeapon( "iw5_combatknifegoliath_mp" );
		// unlink from pod before freezing controls
		activator Unlink();
		activator freezeControlsWrapper( true );

		goliath_to_player_vector = targetPos - activator.origin;
		goliath_to_player_angles = VectorToAngles( goliath_to_player_vector );
		goliath_angles = ( 0, goliath_to_player_angles[1], 0 );
		drop_pod_vfx_forward_vector = RotateVector( goliath_to_player_vector, ( 45, 0, 0 ) );
		
		emptyMech = Spawn( "script_model", targetPos );
		emptyMech.angles = goliath_angles;
		emptyMech SetModel( "npc_exo_armor_ingress" );
		emptyMech ScriptModelPlayAnimDeltaMotion( "mp_goliath_spawn" );
		
		activator maps\mp\_snd_common_mp::snd_message( "goliath_pod_burst" );
		
		// remove collision so player can go inside goliath
		if ( IsDefined( useEnt ) )
			useEnt deleteGoliathPod( false );
		
		PlayFX( level._effect[ "jugg_droppod_open" ], targetPos, drop_pod_vfx_forward_vector );
		
		// Wait for the "mp_goliath_spawn" animation on the "npc_exo_armor_ingress" to finish.
		wait( 0.1 ); //was 0.6
		
		// Lock the player's controls and wait until they animate into the Goliath.
		activator is_entering_goliath( emptyMech, targetPos );
		
		if ( IsDefined( activator ) && IsAlive( activator ) )
		{
			activator SetOrigin( targetPos, true );
			activator SetPlayerAngles( emptyMech.angles );
			activator EnableWeapons();
			activator giveJuggernaut( "juggernaut_exosuit", modules );
			
			// we now have player goliath model, remove empty ones
			emptyMech Delete();
	
			// ideally, this anim should be as long as vm "weapon raise" anim
			activator PlayGoliathToIdleAnim();
			
			// wait for vm "weapon raise" anim and 3rd-person "to idle" to finish
			wait( 1 );
			activator.enteringGoliath = undefined;
			activator freezeControlsWrapper( false );
			if ( isdefined ( level.isHorde ) && level.isHorde )
				activator HudOutlineEnable ( 5, true );
		}
		else
		{
			emptyMech Delete();
		}
	}
	
	marker Delete();
}

destroy_nearby_turrets( org )
{
	dist_sq = 64 * 64;
	
	foreach( player in level.players )
	{
		if( IsDefined( player.turret ) && DistanceSquared( player.turret.origin, org ) <= dist_sq )
		{
			player.turret notify( "death" );
		}
	}
}

is_goliath_drop_pod( crate )
{
	return IsDefined(crate.crateType) && (crate.crateType == "juggernaut") && IsDefined(crate.dropType) && crate.dropType == "juggernaut";
}

movingPlatformDeathFunc( data )
{
	if ( IsDefined( data.emptyMech ) )
		data.emptyMech Delete();
	
	if ( IsDefined( data.useEnt ) )
		data.useEnt Delete();
}

movingPlatformTouchValid( platform )
{
	return ( self goliathAndCarepackageValid( platform ) && self goliathAndGoliathValid( platform ) && goliathAndPlatformValid( platform ) );
}

goliathAndCarepackageValid( platform )
{
	return ( !IsDefined( self.crateType )|| !IsDefined( platform.targetname ) || self.crateType != "juggernaut" || platform.targetname != "care_package" );
}

goliathAndGoliathValid( platform )
{
	return ( !IsDefined( self.crateType )|| !IsDefined( platform.crateType ) || self.crateType != "juggernaut" || platform.crateType != "juggernaut" );
}

goliathAndPlatformValid( platform )
{
	return ( !IsDefined( self.crateType ) || !IsDefined( platform.carepackageTouchValid ) || self.crateType != "juggernaut" || !platform.carepackageTouchValid );
}

control_goliath_usability()		// self = useEnt
{
	self endon( "captured" );
	self endon( "death" );
	level endon( "game_ended" );
	
	self MakeUsable();
	
	foreach ( player in level.players )
	{
		// Disable usability for all players.
		self DisablePlayerUse( player );
	}
	
	while ( true )
	{
		foreach ( player in level.players )
		{
			enable_goliath_use = false;
			
			if ( player IsOnGround() && !player IsOnLadder() && !player IsJumping() && !player IsMantling() && isReallyAlive( player ) && player GetStance() == "stand" )
			{
				if ( DistanceSquared( self.origin, player.origin ) < CONST_JUGG_EXO_USABILITY_RADIUS_SQ )
				{
					if ( player WorldPointInReticle_Rect( self.origin + (0,0,50), 65, 400, 600 ) )
					{
						//if ( VectorDot( AnglesToForward( self.angles ), AnglesToForward( player.angles ) ) > 0.7 )
						//
							enable_goliath_use = true;
						//}
					}
				}
			}
			
			if ( enable_goliath_use == true )
			{
				self EnablePlayerUse( player );
			}
			else
			{
				self DisablePlayerUse( player );
			}
		}
		
		wait( 0.2 );
	}
}

is_entering_goliath( goliathModel, targetPos )		// self = player
{
	goliathForward = AnglesToForward( goliathModel.angles );
	targetPos = targetPos - ( goliathForward * 37 );
	self SetOrigin( targetPos, false );
	self SetPlayerAngles( goliathModel.angles );

	// allow a few frames to lerp to position
	wait( 0.05 );
	
	goliathModel ScriptModelPlayAnimDeltaMotion( "mp_goliath_enter" );
	self PlayGoliathEntryAnim();

	//self waittill( "goliath_entry_complete" );
	wait( 2.3 );
}

createCollision( targetPos )		// self = useEnt
{
	collision = GetEnt( "goliath_collision", "targetname" );
	if ( IsDefined( collision ) )
	{
		self CloneBrushmodelToScriptmodel( collision );
	}
}

playerWaittillGoliathActivated()
{
	self endon( "death" );
	
	self waittill( "captured", player );
	
	player SetStance( "stand" );
	
	// Setting demigod here because it would be shitty for the player to die while getting into the Goliath.
	player SetDemiGod( true );
	
	if ( isDefined( self.owner ) && player != self.owner )
	{
		if ( !level.teamBased || player.team != self.owner.team )
		{
			player thread maps\mp\_events::hijackerEvent( self.owner );
		}
		else
		{
			if ( !isdefined ( level.isHorde ) )
				self.owner thread maps\mp\_events::sharedEvent();
		}
	}
	
	return player;
}

useGoliathUpdater() // self == useEnt
{
	self endon( "death" );
	level endon( "game_ended" );
	
	foreach ( player in level.players )
	{
		if ( player isJuggernaut() )
		{
			self DisablePlayerUse( player );
			self thread usePostJuggernautUpdater( player );
		}
	}
	
	while ( true )
	{
		level waittill ( "juggernaut_equipped", player );
		
		self disablePlayerUse( player );
		self thread usePostJuggernautUpdater( player );
	}	
}

usePostJuggernautUpdater( player ) // self == useEnt
{
	self endon( "death" );
	level endon( "game_ended" );
	player endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	player waittill( "death" );
	self enablePlayerUse( player );
}

// ------------------------------------------------------------------
// Debug Functions

adjustLink( item, tagName, player, startOrigin, startAngles )
{
	item endon( "death" );
	
	if ( !IsDefined( startOrigin ) )
		startOrigin = ( 0, 0, 0 );
	if ( !IsDefined( startAngles ) )
		startAngles = ( 0, 0, 0 );
	thread drawSpine( player, item );
	SetDvar( "scr_adjust_angles", ""+startAngles );
	SetDvar( "scr_adjust_origin", ""+startOrigin );
	currentAngles = (0, 0, 0);
	currentOrigin = (0, 0, 0);
	while ( true )
	{
		waitframe();
		
		nextAngles = GetDvarVector( "scr_adjust_angles" );
		nextOrigin = GetDvarVector( "scr_adjust_origin" );
		
		if ( nextAngles == currentAngles && nextOrigin == currentOrigin )
			continue;
		
		currentAngles = nextAngles;
		currentOrigin = nextOrigin;
		
		item Unlink();
		item LinkTo( player, tagName, currentOrigin, currentAngles );
	}
}


drawSpine( player, item )
{
	player endon( "disconnect" );
	player endon( "death" );
	item endon( "death" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		player endon( "becameSpectator" );
	
	while ( true )
	{
//		origin = player GetTagOrigin( "j_spine4" );
//		angles = player GetTagAngles( "j_spine4" );
//		Box(origin, 0, ( 0, 1, 0 ) );
		origin = item.origin;
		angles = item.angles;
		debug_axis( origin, angles );
		waitframe();
	}
}

debug_axis( origin, angles )
{
	size = 20;

	forward = AnglesToForward( angles ) * size;
	right	= AnglesToRight( angles ) * size;
	up		= AnglesToUp( angles ) * size;
	Line( origin, origin + forward, ( 1, 0, 0 ), 1.0, 0, 1 );
	Line( origin, origin + up	, ( 0, 1, 0 ), 1.0, 0, 1 );
	Line( origin, origin + right, ( 0, 0, 1 ), 1.0, 0, 1 );
}

// ---------------------------------------------------------------------------------------
// Copied from _playermech_code.gsc


MECH_CHAINGUN_STATE_NONE		= 0;
MECH_CHAINGUN_STATE_READY		= 1;
MECH_CHAINGUN_STATE_FIRING		= 2;
MECH_CHAINGUN_STATE_OVERHEAT	= 3;
MECH_CHAINGUN_STATE_OFFLINE		= 4;

MECH_SWARM_STATE_NONE			= 0;
MECH_SWARM_STATE_READY			= 1;
MECH_SWARM_STATE_TARGETING		= 2;
MECH_SWARM_STATE_RELOADING		= 3;
MECH_SWARM_STATE_OFFLINE		= 4;

MECH_ROCKET_STATE_NONE			= 0;
MECH_ROCKET_STATE_READY			= 1;
MECH_ROCKET_STATE_RELOADING		= 2;
MECH_ROCKET_STATE_OFFLINE		= 3;


//*******************************************************************
//                                                                  *
// UI / FX                                                			*
//                                                                  *
//*******************************************************************

playermech_ui_state_reset()
{
	if ( !IsDefined( self.mechUIState ) )
	{
		self.mechUIState = SpawnStruct();
		self.mechUIState.chaingun = SpawnStruct();
		self.mechUIState.swarm = SpawnStruct();
		self.mechUIState.rocket = SpawnStruct();
		self.mechUIState.threat_list = SpawnStruct();
		
		self.mechUIState.state = "none";
		self.mechUIState.chaingun.state = "none";
		self.mechUIState.chaingun.last_state = "none";
		self.mechUIState.swarm.state = "none";
		self.mechUIState.swarm.last_state = "none";
		self.mechUIState.rocket.state = "none";	
		self.mechUIState.rocket.last_state = "none";
	}
	
	self set_mech_state();
	
	// Threats
	self.mechUIState.threat_list.threats = [];
	self.mechUIState.threat_list.compass_offsets = [];
	
	// Chaingun
	self.mechUIState.chaingun.heatlevel = 0;
	self.mechUIState.chaingun.overheated = false;
	
	// Swarm
//	self.mechUIState.swarm.target_list = [];
	self.mechUIState.swarm.threat_scan = false;	
	self.mechUIState.swarm.recharge = 100;
	
	// Rocket
	self.mechUIState.rocket.fire = false;
	self.mechUIState.rocket.recharge = 100;
}

//playermech_ui_state_enter( state )
//{
//	switch( state )
//	{		
//		case "base_noweap_bootup":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGame( "playermech_bootup" );
//			break;
//		}		
//		case "base_swarmonly_nolegs":	
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGameLoop( "playermech_base_swarmonly_nolegs" );
//			break;
//		}	
//		case "base_swarmonly_exit":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGame( "playermech_base_swarmonly_exit" );
//			break;
//		}	
//		case "base_swarmonly":	
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGameLoop( "playermech_base_swarmonly" );
//			break;
//		}		
//		case "base_transition":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGame( "playermech_base_transition" );
//			break;
//		}
//		case "base":			
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			//CinematicInGameLoop( "playermech_base" );
//			break;
//		}
//		case "dmg1_transition":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGame( "playermech_dmg1_transition" );
//			break;
//		}
//		case "dmg1":	
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGameLoop( "playermech_dmg1" );
//			break;
//		}		
//		case "dmg2_transition":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGame( "playermech_dmg2_transition" );
//			break;
//		}
//		case "dmg2":
//		{
//			// Player enters mech		
//			setsaveddvar( "cg_cinematicfullscreen", "0" );
//			CinematicInGameLoop( "playermech_dmg2" );
//			break;
//		}
//	}
//}
//
//playermech_ui_state_leave( state )
//{
//	switch( state )
//	{
//		case "base_noweap_bootup":
//			break;			
//		default:		
//		{
//			// Player enters mech
//			setsaveddvar( "cg_cinematicfullscreen", "1" );
//			stopcinematicingame();
//			break;
//		}
//	}
//}

playermech_state_manager()
{
	self endon( "disconnect" );
	self endon( "exit_mech" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
	
	// To be kept up to date for UI and FX.
	playermech_ui_state_reset();

	// Initialize states	
	self set_mech_state();
	self set_mech_chaingun_state();
	self set_mech_rocket_state();
	self set_mech_swarm_state();
	
	waitframe();
	
	while ( 1 )
	{			
		// Main mech state pump (Binks, main mech stuff)
//		state_main_pump();		
		
		// Weapon state pumps
		state_chaingun_pump();
		state_rocket_pump();
		state_swarm_pump();		
		
//		// Update current threat list
//		self.mechUIState.threat_list.threats = remove_dead_from_array( self.mechData.threat_list );
//		self playermech_ui_update_threat_compass_values( self.mechUIState.threat_list );
		
		// Send data to HUD.
		self playermech_ui_update_lui( self.mechUIState );

		wait 0.05;
	}
}

set_mech_state( state )
{
	Assert( IsPlayer( self ) );
	
	if( !IsDefined( state ) )
		state = "none";
	
	if( !IsDefined( self.mechUIState ) )
		return;
	
	if( self.mechUIState.state == state )
		return;
	
//	playermech_ui_state_leave( self.mechUIState.state );
	self.mechUIState.state = state;
//	playermech_ui_state_enter( state );	
//	iprintln( "Main state: " + state + ", Weap:" + self GetCurrentPrimaryWeapon() );
}

get_mech_state()
{
	Assert( IsPlayer( self ) );
	if( !IsDefined( self.mechUIState ) )
		return;
	return self.mechUIState.state;
}

get_is_in_mech()
{
	modelName = self getattachmodelname( 0 );
	
	if( IsDefined( modelName ) && modelName == "head_hero_cormack_sentinel_halo" )
		return true;
	
	return false;
}

get_front_sorted_threat_list( threats, forward )
{
	sorted = [];

	// Sort threats. 
	foreach( threat in threats )
	{
		// Check if in front of player. 
		if ( VectorDot( threat.origin - self.origin, forward ) < 0 )
			continue;

		sorted[sorted.size]	= threat;
	}
	sorted = SortByDistance( sorted, self.origin );
	return sorted;
}

playermech_ui_weapon_feedback( buttonPressed, omnvar )
{
	self endon( "disconnect" );
	self endon( "exit_mech" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	self SetClientOmnvar( omnvar, false );
	while ( 1 )
	{
		while ( !self call [[buttonPressed]]() )
		{
			wait( 0.05 );
		}	
		self SetClientOmnvar( omnvar, true );			
		while ( self call [[buttonPressed]]() )
		{
			wait( 0.05 );
		}
		self SetClientOmnvar( omnvar, false );
		wait( 0.05 );
	}
}

//playermech_ui_update_threat_compass_values( threatList )
//{
//	threatList.compass_offsets = [];
//
//	// Test
//	//threatList.threats = [];
//	//threatList.threats = SortByDistance( GetAIArray( "axis" ), self.origin );
//
//	forward = AnglesToForward( self GetPlayerAngles() );
//	if ( forward[2] > 0.99 )
//		return;
//	forward = VectorNormalize( forward * (1,1,0) );
//
//	sorted = get_front_sorted_threat_list( threatList.threats, forward );
//
//	// Fill threat data. 
//	compassFudge = GetDvarFloat( "mechCompassScaleFudge" );
//	foreach( threat in sorted )
//	{
//		threatDir = threat.origin - self.origin;
//		threatDir = VectorNormalize( threatDir * (1,1,0) );
//		if ( threatDir[2] > 0.99 )
//		{
//			sorted.compass_offsets[threatList.compass_offsets.size] = 0;
//			continue;
//		}
//
//		frac = acos( clamp( VectorDot( threatDir, forward ), -1, 1 ) ) / 180.0;
//		cross = VectorCross( threatDir, forward );
//		if ( cross[2] <= 0 )
//			frac *= -1.0;
//		frac *= compassFudge;
//		threatList.compass_offsets[threatList.compass_offsets.size] = frac;
//		//iprintlnbold( frac );
//	}
//}
//
playermech_ui_update_lui( uiState )
{
	isThreat = self playerIsRocketSwarmTargetLocked();
	numSwarmTargets = 0;
	if ( isThreat )
		numSwarmTargets = 1;
	
	if ( self.heavyExoData.hasRockets )
		self SetClientOmnvar( "ui_playermech_swarmrecharge", uiState.swarm.recharge );
	if ( self.heavyExoData.hasLongPunch )
		self SetClientOmnvar( "ui_playermech_rocketrecharge", uiState.rocket.recharge );
}


//*******************************************************************
//                                                                  *
// Invalid weapon events                                           	*
//                                                                  *
//*******************************************************************

playermech_invalid_gun_callback()
{
	if ( self.mechUIState.chaingun.overheated )
	{
		// Play mech gun invalid ui and sound. 
		return true;
	}

	return false;
}

playermech_invalid_rocket_callback()
{
	if ( self.mechUIState.rocket.recharge < 100 )
	{
		// Play mech rocket invalid ui and sound. 
		return true;
	}
	return false;
}

playermech_invalid_swarm_callback()
{
	if ( self.mechUIState.swarm.recharge < 100 )
	{
		// Play mech swarm invalid ui and sound. 
		return true;
	}
	return false;
}

playermech_invalid_weapon_instance( buttonFunc, callback )
{
	self endon( "disconnect" );
	self endon( "exit_mech" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	buttonDown = false;
	while ( 1 )
	{
		wait 0.05;

		if ( self call [[buttonFunc]]() ) 
		{
			if ( !buttonDown )
			{
				if ( [[callback]]() )
				{
					buttonDown = true;
					self PlayLocalSound( "wpn_mech_offline" );
					wait 1.5;
				}
			}
		}
		else 
		{
			buttonDown = false;
		}
	}
}

playermech_invalid_weapon_watcher()
{
	self thread playermech_invalid_weapon_instance( ::AttackButtonPressed, ::playermech_invalid_gun_callback );
	self thread playermech_invalid_weapon_instance( ::FragButtonPressed, ::playermech_invalid_rocket_callback );
	self thread playermech_invalid_weapon_instance( ::SecondaryOffhandButtonPressed, ::playermech_invalid_swarm_callback );
}


//*******************************************************************
//                                                                  *
// Bink States                                                		*
//                                                                  *
//*******************************************************************

// Main mech state
state_main_pump()
{
	switch( self get_mech_state() )
	{
		case "base_noweap_bootup":		
		case "base_swarmonly_nolegs":		
		case "base_swarmonly_exit":	
		case "base_noweap":			
		case "base_swarmonly":			
		case "base_transition":
		case "base":			
		case "dmg1_transition":
		case "dmg1":			
		case "dmg2_transition":
		case "dmg2":					
			break;

		case "none":
		{
			playermech_ui_state_reset();
			break;
		}			

		default:
		{
			assertmsg( "Mech state invalid: " + self get_mech_state() );
		}
	}
}


//*******************************************************************
//                                                                  *
// Weapon States                                                	*
//                                                                  *
//*******************************************************************

state_chaingun_pump()
{	
//	// Let's avoid running this function if we are in the same state
//	if( same_mech_chaingun_last_state() )
//		return;
	
	current_state = self get_mech_chaingun_state();	
	
	// Overheating chaingun checks
	weapon = self GetCurrentWeapon();	
	self.mechUIState.chaingun.heatlevel = self GetWeaponHeatLevel( weapon );
	self.mechUIState.chaingun.overheated = self IsWeaponOverheated( weapon );		
	
	// Overheat
	if( current_state == "ready" )
	{
		if( self.mechUIState.chaingun.overheated )
			set_mech_chaingun_state( "overheat" );
		else if( self AttackButtonPressed() )
			set_mech_chaingun_state( "firing" );
	}
	else if( current_state == "firing" )
	{
		if( self.mechUIState.chaingun.overheated )
			set_mech_chaingun_state( "overheat" );
		else if( !self AttackButtonPressed() )
			set_mech_chaingun_state( "ready" );
	}
	else if( current_state == "overheat" && !self.mechUIState.chaingun.overheated )
	{
		set_mech_chaingun_state( "ready" );
	}
}

state_rocket_pump()
{
//	// Let's avoid running this function if we are in the same state
//	if( same_mech_rocket_last_state() )
//		return;
	
	current_state = self get_mech_rocket_state();
	
	// Reloading	
	if( current_state != "offline" && self playermech_invalid_rocket_callback() )
		set_mech_rocket_state( "reload" );
	else if( current_state == "reload" && !self playermech_invalid_rocket_callback() )
		set_mech_rocket_state( "ready" );
}

state_swarm_pump()
{
//	// Let's avoid running this function if we are in the same state
//	if( same_mech_swarm_last_state() )
//		return;
	
	current_state = self get_mech_swarm_state();
	
//	// Swarm target list update
//	self.mechUIState.swarm.target_list = remove_dead_from_array( self.mechData.swarm_target_list );
	
	// Targetting / Reloading
	if( !self playerIsRocketSwarmTargetLocked() && !self playerIsRocketSwarmReloading() )
		set_mech_swarm_state( "target" );
	else if( current_state == "target" && self playermech_invalid_swarm_callback() )
		set_mech_swarm_state( "reload" );
	else if( current_state == "reload" && !self playermech_invalid_swarm_callback() )
		set_mech_swarm_state( "ready" );
}

set_mech_chaingun_state( state )
{
	Assert( IsPlayer( self ) );
	
	if( !IsDefined( state ) )
		state = "none";
	
	if( !IsDefined( self.mechUIState.chaingun.state ) )
		self.mechUIState.chaingun.state = "none";
	
	if( self.mechUIState.chaingun.state == state )
		return;
	
	self.mechUIState.chaingun.state = state;
	self notify( "chaingun_state_" + state );
	//iprintln( "Chaingun state: " + state );
}

get_mech_chaingun_state()
{
	Assert( IsPlayer( self ) );
	if( !IsDefined( self.mechUIState ) )
		return;	
	return self.mechUIState.chaingun.state;
}

same_mech_chaingun_last_state()
{	
	if( IsDefined( self.mechUIState.chaingun.last_state ) && self.mechUIState.chaingun.state == self.mechUIState.chaingun.last_state )
		return true;
	self.mechUIState.chaingun.last_state = self.mechUIState.chaingun.state;	
	return false;
}

set_mech_rocket_state( state )
{
	Assert( IsPlayer( self ) );
	
	if( !IsDefined( state ) )
		state = "none";
	
	if( !IsDefined( self.mechUIState.rocket.state ) )
		self.mechUIState.rocket.state = "none";
	
	if( self.mechUIState.rocket.state == state )
		return;
	
	self.mechUIState.rocket.state = state;
	//iprintln( "Rocket state: " + state );
}

get_mech_rocket_state()
{
	Assert( IsPlayer( self ) );
	if( !IsDefined( self.mechUIState ) )
		return;
	return self.mechUIState.rocket.state;
}

same_mech_rocket_last_state()
{	
	if( IsDefined( self.mechUIState.rocket.last_state ) && self.mechUIState.rocket.state == self.mechUIState.rocket.last_state )
		return true;
	self.mechUIState.rocket.last_state = self.mechUIState.rocket.state;	
	return false;
}

set_mech_swarm_state( state )
{
	Assert( IsPlayer( self ) );
	
	if( !IsDefined( state ) )
		state = "none";
	
	if( !IsDefined( self.mechUIState.swarm.state ) )
		self.mechUIState.swarm.state = "none";
	
	if( self.mechUIState.swarm.state == state )
		return;
	
	self.mechUIState.swarm.state = state;	
	//iprintln( "Swarm state: " + state );
}

get_mech_swarm_state()
{
	Assert( IsPlayer( self ) );
	if( !IsDefined( self.mechUIState ) )
		return;
	return self.mechUIState.swarm.state;
}

same_mech_swarm_last_state()
{	
	if( IsDefined( self.mechUIState.swarm.last_state ) && self.mechUIState.swarm.state == self.mechUIState.swarm.last_state )
		return true;
	self.mechUIState.swarm.last_state = self.mechUIState.swarm.state;	
	return false;
}

playermech_monitor_update_recharge( weapon, time )
{
	weapon.recharge = 0;
	iteration = 100.0 / (time / 0.05);
	while ( weapon.recharge < 100 )
	{
		weapon.recharge += iteration;
		wait 0.05;
	}
	weapon.recharge = 100;
	
	while ( IsDefined( self.underwaterMotionType ) )		// delay re-enabling weapons until the Goliath is out of water.
	{
		wait( 0.05 );
	}
}

playermech_monitor_rocket_recharge()
{
	self endon( "disconnect" );
	self endon( "exit_mech" );
	self notify( "stop_rocket_recharge" );	
	self endon( "stop_rocket_recharge" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	while ( 1 )
	{
		self waittill( "mech_rocket_fire" );		
		self DisableOffhandWeapons();
		self playermech_monitor_update_recharge( self.mechuistate.rocket, CONST_JUGG_EXO_ROCKET_RELOAD_TIME );
		self EnableOffhandWeapons();			
		wait 0.05;
	}
}

playermech_monitor_swarm_recharge()
{
	self endon( "disconnect" );
	self endon( "exit_mech" );
	self notify( "stop_swarm_recharge" );
	self endon( "stop_swarm_recharge" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );

	while ( 1 )
	{
		self waittill( "mech_swarm_fire" );				
		self DisableOffhandSecondaryWeapons();
		self playermech_monitor_update_recharge( self.mechuistate.swarm, CONST_JUGG_EXO_ROCKET_SWARM_RELOAD_TIME );
		self EnableOffhandSecondaryWeapons();
		wait 0.05;
	}
}

play_goliath_death_fx()			// self = player
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
	
	self waittill_any( "death", "joined_team", "faux_spawn" );
	
	if ( IsAI( self ) )
	{
		self maps\mp\_snd_common_mp::snd_message( "goliath_self_destruct" );
		PlayFX( getfx( "goliath_self_destruct" ), self.origin, AnglesToUp(self.angles) );
		
		if ( IsAgent( self ) && IsDefined( self.hideOnDeath ) && self.hideOnDeath == true )
		{
			// Need to hide the Agent's body here. For Bots and Players, their bodies are hidden in _damage::PlayerKilled_internal().
			agent_corpse = self GetCorpseEntity();
			if ( IsDefined( agent_corpse ) )
				agent_corpse Hide();
		}
	}
	else if ( !isDefined( self.juggernautSuicide ) && !IsDefined( level.isHorde ) )
	{
	    playfxontag( getfx( "goliath_death_fire" ), self.body, "J_NECK" );
	    self maps\mp\_snd_common_mp::snd_message( "goliath_death_explosion" );
	}
	self.juggernautSuicide = undefined;
}

self_destruct_goliath()			// self = player
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "joined_team" );
	self endon( "faux_spawn" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "horde_cancel_goliath" );
	
	button_hold_time = 0;
	
	while ( true )
	{
		if ( self UseButtonPressed() )
		{
			button_hold_time += 0.05;
			if ( button_hold_time > 1.0 )
			{
				self maps\mp\_snd_common_mp::snd_message( "goliath_self_destruct" );
				PlayFX( getfx( "goliath_self_destruct" ), self.origin, AnglesToUp(self.angles) );
				wait 0.05;
				self.hideOnDeath = true;
				self.juggernautSuicide = true;
				
				RadiusDamage( self.origin + ( 0, 0, 50 ), 400, 200, 20, self, "MOD_EXPLOSIVE", "killstreak_goliathsd_mp" );
				
				if ( isdefined ( level.isHorde ) && level.isHorde )
				{
					self thread [[level.hordeHandleJuggDeath]]();
				}
			}
		}
		else
		{
			button_hold_time = 0;
		}
		wait( 0.05 );
	}
}

playerMechTimeout()
{
	level endon ( "game_ended" );
	self endon ( "disconnect" );
	self endon ( "lost_juggernaut" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon ( "horde_cancel_goliath" );
	
	while ( true )
	{
		wait 1;
		self.mechHealth -= int ( self.maxhealth / 100 );
		
		if ( self.mechHealth < 0 )
		{
			self maps\mp\_snd_common_mp::snd_message( "goliath_self_destruct" );
			PlayFX( getfx( "goliath_self_destruct" ), self.origin, AnglesToUp(self.angles) );
			self thread [[level.hordeHandleJuggDeath]]();
		}
		
			self SetClientOmnvar( "ui_exo_suit_health", self.mechHealth / self.maxhealth );
		
	}
}

onPlayerConnect()
{
	for(;;)
	{
		level waittill("connected", player);
		player thread onPlayerSpawned();
	}
}

onPlayerSpawned()
{
	self endon("disconnect");

	for(;;)
	{
		self waittill( "spawned_player" );
		self.hideOnDeath = false;
	}
}


deleteGoliathPod( playDestroyVFX, playDestroySound )		// self = goliath pod useEnt. This function is similar to _airdrop::deleteCrate().
{
	if ( !IsDefined( playDestroyVFX ) )
		playDestroyVFX = true;
	
	if ( !IsDefined( playDestroySound ) )
		playDestroySound = false;
	
	if ( isDefined( self.curObjID ) )
		_objective_delete( self.curObjID );
	
	if ( isDefined( self.dropType ) )
	{
		if ( playDestroyVFX )
		{
			PlayFX( getfx( "ocp_death" ), self.origin );
		}
		
		if ( playDestroySound )
		{
			playSoundAtPos( self.origin, "orbital_pkg_self_destruct" );
		}
	}
	
	self delete();
}

handle_goliath_drop_pod_timeout( hordeClassGoliath )				// self = useEnt
{
	level endon( "game_ended" );
	self endon( "death" );
	
	wait( CONST_JUGG_EXO_TIMEOUT_SEC );
	
	if ( isdefined ( level.isHorde ) && level.isHorde && hordeClassGoliath )
	{
		self.owner.hordeClassGoliathPodInField = undefined;
		self.owner.hordeGoliathPodInField = undefined;
		self.owner notify ( "startJuggCooldown" );
	}
		
	self deleteGoliathPod();
}

delete_goliath_drop_pod_for_event()
{
	level endon( "game_ended" );
	self endon( "death" );
	
	level waittill_any ( "zombies_start", "EMP_JamTeamallies" );
	
	self deleteGoliathPod();
}

handle_goliath_drop_pod_removal( useEnt )		// self = goliath drop pod model
{
	level endon( "game_ended" );
	self endon( "death" );
	
	useEnt waittill( "death" );
	
	self Delete();
}

playermech_watch_emp_grenade()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
	
	while( true )
	{
		self waittill( "emp_grenaded", attacker );
		if( IsDefined( attacker ) && IsPlayer( attacker ) )
		{
			attacker thread ch_emp_goliath_think();
		}
	}
}

ch_emp_goliath_think()
{
	//self is the guy that emp'd the goliath
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self endon( "becameSpectator" );
	
	time_to_wait = 5.0;
	
	wait( time_to_wait );
	
	if( isReallyAlive( self ) )
	{
		self maps\mp\gametypes\_missions::processChallenge( "ch_precision_closecall" );
	}
}
