#using scripts\codescripts\struct;

#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\dev_shared;
#using scripts\shared\damagefeedback_shared;
#using scripts\shared\entityheadicons_shared;
#using scripts\shared\flagsys_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\player_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\vehicle_shared;
#using scripts\shared\weapons_shared;
#using scripts\shared\weapons\_hive_gun;
#using scripts\shared\weapons\_satchel_charge;
#using scripts\shared\weapons\_trophy_system;
#using scripts\shared\weapons\_weaponobjects;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   
       


	
#precache( "lui_menu_data", "spikeLauncherCounter.spikesReady" );
#precache( "lui_menu_data", "spikeLauncherCounter.blasting" );

#precache( "string", "MP_DEFUSING_EXPLOSIVE" );
#precache( "string", "PLATFORM_SATCHEL_CHARGE_DOUBLE_TAP" );

#precache( "fx", "_t6/weapon/claymore/fx_claymore_laser" );
#precache( "fx", "explosions/fx_exp_equipment_lg" );
#precache( "fx", "killstreaks/fx_emp_explosion_equip" );
#precache( "fx", "_t6/explosions/fx_exp_equipment" );
#precache( "fx", "explosions/fx_exp_equipment_lg" );
#precache( "fx", "weapon/fx_equip_light_os" );

#precache( "triggerstring", "MP_BOUNCINGBETTY_HACKING" );
#precache( "triggerstring", "MP_BOUNCINGBETTY_PICKUP" );
#precache( "triggerstring", "MP_HATCHET_PICKUP");
#precache( "triggerstring", "MP_CLAYMORE_PICKUP");
#precache( "triggerstring", "MP_BOUNCINGBETTY_PICKUP");
#precache( "triggerstring", "MP_TROPHY_SYSTEM_PICKUP");
#precache( "triggerstring", "MP_ACOUSTIC_SENSOR_PICKUP");
#precache( "triggerstring", "MP_CAMERA_SPIKE_PICKUP");
#precache( "triggerstring", "MP_SATCHEL_CHARGE_PICKUP");
#precache( "triggerstring", "MP_SCRAMBLER_PICKUP");
#precache( "triggerstring", "MP_SHOCK_CHARGE_PICKUP");
#precache( "triggerstring", "MP_TROPHY_SYSTEM_DESTROY");
#precache( "triggerstring", "MP_SENSOR_GRENADE_DESTROY");
#precache( "triggerstring", "MP_CLAYMORE_HACKING");
#precache( "triggerstring", "MP_BOUNCINGBETTY_HACKING");
#precache( "triggerstring", "MP_TROPHY_SYSTEM_HACKING");
#precache( "triggerstring", "MP_ACOUSTIC_SENSOR_HACKING");
#precache( "triggerstring", "MP_CAMERA_SPIKE_HACKING");
#precache( "triggerstring", "MP_SATCHEL_CHARGE_HACKING");
#precache( "triggerstring", "MP_SCRAMBLER_HACKING");

#namespace weaponobjects;

function init_shared()
{
	callback::on_start_gametype( &start_gametype );
	
	clientfield::register( "toplayer", "proximity_alarm", 1, 2, "int" );	
	clientfield::register( "clientuimodel", "hudItems.proximityAlarm", 1, 2, "int" ); // registered client-side in raw/ui/uieditor/clientfieldmodels.lua
	clientfield::register( "missile", "retrievable", 1, 1, "int" );
	clientfield::register( "scriptmover", "retrievable", 1, 1, "int" );
	clientfield::register( "missile", "enemyequip", 1, 2, "int" );
	clientfield::register( "scriptmover", "enemyequip", 1, 2, "int" );
	clientfield::register( "missile", "teamequip", 1, 1, "int" );
	
	level.weaponObjectDebug = GetDvarInt( "scr_weaponobject_debug", 0 );

	level.supplementalWatcherObjects = [];
	
	/#
	level thread updateDvars();
	#/
}

function updateDvars()
{
	while(1)
	{
		level.weaponObjectDebug = GetDvarInt( "scr_weaponobject_debug", 0 );

		wait(1.0);
	}
}

function start_gametype()
{
	coneangle = GetDvarInt( "scr_weaponobject_coneangle", 70 );
	mindist = GetDvarInt( "scr_weaponobject_mindist", 20 );
	graceperiod = GetDvarFloat( "scr_weaponobject_graceperiod", 0.6 );
	radius = GetDvarInt( "scr_weaponobject_radius", 192 );

	callback::on_connect( &on_player_connect );
	callback::on_spawned( &on_player_spawned );

	level.watcherWeapons = [];
	level.watcherWeapons = getWatcherWeapons();

	level.retrievableWeapons = [];
	level.retrievableWeapons = getRetrievableWeapons();

	level.weaponobjectexplodethisframe = false;

	if ( GetDvarString( "scr_deleteexplosivesonspawn") == "" )
	{
		SetDvar("scr_deleteexplosivesonspawn", 1);
	}
	
	level.deleteExplosivesOnSpawn = GetDvarInt( "scr_deleteexplosivesonspawn");

	level.claymoreFXid = "_t6/weapon/claymore/fx_claymore_laser";
	level._equipment_spark_fx = "explosions/fx_exp_equipment_lg";
	level._equipment_fizzleout_fx = "explosions/fx_exp_equipment_lg";
	level._equipment_emp_destroy_fx = "killstreaks/fx_emp_explosion_equip";
	level._equipment_explode_fx = "_t6/explosions/fx_exp_equipment";
	level._equipment_explode_fx_lg = "explosions/fx_exp_equipment_lg";
	level._effect[ "powerLight" ] = "weapon/fx_equip_light_os";

	setUpRetrievableHintStrings();
	
	level.weaponobjects_hacker_trigger_width = 32; 
	level.weaponobjects_hacker_trigger_height = 32;		
}

function setUpRetrievableHintStrings()
{
	createRetrievableHint("hatchet", &"MP_HATCHET_PICKUP");
	createRetrievableHint("claymore", &"MP_CLAYMORE_PICKUP");
	createRetrievableHint("bouncingbetty", &"MP_BOUNCINGBETTY_PICKUP");
	createRetrievableHint("trophy_system", &"MP_TROPHY_SYSTEM_PICKUP");
	createRetrievableHint("acoustic_sensor", &"MP_ACOUSTIC_SENSOR_PICKUP");
	//createRetrievableHint("sensor_grenade", &"MP_SENSOR_GRENADE_PICKUP");
	createRetrievableHint("camera_spike", &"MP_CAMERA_SPIKE_PICKUP");
	createRetrievableHint("satchel_charge", &"MP_SATCHEL_CHARGE_PICKUP");
	createRetrievableHint("scrambler", &"MP_SCRAMBLER_PICKUP");
	createRetrievableHint("proximity_grenade", &"MP_SHOCK_CHARGE_PICKUP");

	createDestroyHint( "trophy_system", &"MP_TROPHY_SYSTEM_DESTROY");
	createDestroyHint( "sensor_grenade", &"MP_SENSOR_GRENADE_DESTROY");

	createHackerHint("claymore", &"MP_CLAYMORE_HACKING");
	createHackerHint("bouncingbetty", &"MP_BOUNCINGBETTY_HACKING");
	createHackerHint("trophy_system", &"MP_TROPHY_SYSTEM_HACKING");
	createHackerHint("acoustic_sensor", &"MP_ACOUSTIC_SENSOR_HACKING");
	//createHackerHint("sensor_grenade", &"MP_SENSOR_GRENADE_HACKING");
	createHackerHint("camera_spike", &"MP_CAMERA_SPIKE_HACKING");
	createHackerHint("satchel_charge", &"MP_SATCHEL_CHARGE_HACKING");
	createHackerHint("scrambler", &"MP_SCRAMBLER_HACKING");
}

function on_player_connect()
{
	if ( isdefined( level._weaponobjects_on_player_connect_override ) )
	{
		level thread [[level._weaponobjects_on_player_connect_override]]();
		return;
	}

	self.usedWeapons = false;
	self.hits = 0;
}

function on_player_spawned() // self == player
{
	self endon("disconnect");

	pixbeginevent("onPlayerSpawned");		
	
	if ( !isdefined( self.watchersInitialized ) )
	{
		self createBaseWatchers();
		
		self callback::callback_weapon_watcher();

		//Ensure that the watcher name is the weapon name minus _mp if you want to add weapon specific functionality.
		self createClaymoreWatcher();
		self createRCBombWatcher();
		self createQRDroneWatcher();
		self createPlayerHelicopterWatcher();
		//self createKniferangWatcher();
		self createHatchetWatcher();
		self createSpecialCrossbowWatcher();
		self createTactInsertWatcher();
		self hive_gun::createFireflyPodWatcher();

		//set up retrievable specific fields
		self setupRetrievableWatcher();
	
		self thread watchWeaponObjectUsage();
		
		self.watchersInitialized = true;
	}
	
	self resetWatchers();
	
	self trophy_system::ammo_reset();	
	
	pixendevent();
}

function resetWatchers()
{
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		return undefined;
	}
	
	team = self.team;
	
	foreach( watcher in self.weaponObjectWatcherArray )
	{
		resetWeaponObjectWatcher( watcher, team );
	}	
}

function createBaseWatchers()
{
	//Check for die on respawn weapons
	foreach( index, weapon in level.watcherWeapons )
	{
		self createWeaponObjectWatcher( weapon.name, self.team );
	}

	//Check for retrievable weapons
	foreach( index, weapon in level.retrievableWeapons )
	{
		self createWeaponObjectWatcher( weapon.name, self.team );
	}
}

function setupRetrievableWatcher()
{
	//Check for retrievable weapons
	for( i = 0; i < level.retrievableWeapons.size; i++ )
	{
		watcher = getWeaponObjectWatcherByWeapon( level.retrievableWeapons[i] );
		if( isdefined( watcher ) )
		{
			if( !isdefined( watcher.onSpawnRetrieveTriggers ) )
				watcher.onSpawnRetrieveTriggers =&onSpawnRetrievableWeaponObject;
	
			if ( !isdefined( watcher.onDestroyed ))
				watcher.onDestroyed =&onDestroyed;
	
			if( !isdefined( watcher.pickUp ) )
				watcher.pickUp =&pickUp;
		}
	}
}

function createSpecialCrossbowWatcherTypes( weaponName )
{
	watcher = self createUseWeaponObjectWatcher( weaponName, self.team );
	watcher.onDetonateCallback = &deleteEnt;
	watcher.onDamage =&voidOnDamage;
	
	if ( ( isdefined( level.b_crossbow_bolt_destroy_on_impact ) && level.b_crossbow_bolt_destroy_on_impact ) ) // PORTIZ 7/5/16: override crossbow bolt lingering/retrieval, since behavior is set up here in script instead of through GDT
	{
		watcher.onSpawn = &onSpawnCrossbowBoltImpact;
		watcher.onSpawnRetrieveTriggers = &voidOnSpawnRetrieveTriggers;
		watcher.pickUp = &voidPickUp;
	}
	else
	{
		watcher.onSpawn = &onSpawnCrossbowBolt;
		watcher.onSpawnRetrieveTriggers =&onSpawnSpecialCrossbowTrigger;
		watcher.pickUp = &pickUpCrossbowBolt;
	}
}

function createSpecialCrossbowWatcher() // self == player
{
	createSpecialCrossbowWatcherTypes( "special_crossbow" );
	createSpecialCrossbowWatcherTypes( "special_crossbowlh" );
	createSpecialCrossbowWatcherTypes( "special_crossbow_dw" );
	
	if ( ( isdefined( level.b_create_upgraded_crossbow_watchers ) && level.b_create_upgraded_crossbow_watchers ) ) // PORTIZ 7/5/16: ZM maps can set to true to include upgraded crossbows
	{
		createSpecialCrossbowWatcherTypes( "special_crossbowlh_upgraded" );
		createSpecialCrossbowWatcherTypes( "special_crossbow_dw_upgraded" );
	}
}

function createHatchetWatcher() // self == player
{
	watcher = self createUseWeaponObjectWatcher( "hatchet", self.team );
	watcher.onDetonateCallback = &deleteEnt;
	watcher.onSpawn =&onSpawnHatchet;
	watcher.onDamage =&voidOnDamage;
	watcher.onSpawnRetrieveTriggers =&onSpawnHatchetTrigger;
}

function createTactInsertWatcher() // self == player
{
	watcher = self createUseWeaponObjectWatcher( "tactical_insertion", self.team );
	watcher.playDestroyedDialog = false;
}

function createRCBombWatcher() // self == player
{
	watcher = self createUseWeaponObjectWatcher( "rcbomb", self.team );

	watcher.altDetonate = false;
	watcher.headIcon = false;
	watcher.isMovable = true;
	watcher.ownerGetsAssist = true;
	watcher.playDestroyedDialog = false;
	watcher.deleteOnKillbrush = false;

	watcher.onDetonateCallback = level.rcbombOnBlowUp;
	watcher.stunTime = 1;
	watcher.notEquipment = true;
}

function createQRDroneWatcher() // self == player
{
	watcher = self createUseWeaponObjectWatcher( "qrdrone", self.team );

	watcher.altDetonate = false;
	watcher.headIcon = false;
	watcher.isMovable = true;
	watcher.ownerGetsAssist = true;
	watcher.playDestroyedDialog = false;
	watcher.deleteOnKillbrush = false;

	watcher.onDetonateCallback = level.qrdroneOnBlowUp;
	watcher.onDamage = level.qrdroneOnDamage;
	watcher.stunTime = 5;
	watcher.notEquipment = true;
}

function getSpikeLauncherActiveSpikeCount( watcher )
{
	// the spike launcher generates a new weapon when fired to handle detonation so make sure we're only counting the bolts
	currentItemCount = 0;
	foreach ( obj in watcher.objectArray )
	{
		if ( IsDefined(obj) && obj.item !== watcher.weapon )
		{
			currentItemCount++;
		}
	}
	return currentItemCount;
}

function watchSpikeLauncherItemCountChanged( watcher )	// self == player
{
	self endon( "death" );
	lastItemCount = undefined;
	while ( 1 )
	{
		self waittill( "weapon_change", weapon );
		
		while ( weapon.name == "spike_launcher" )
		{
			// the spike launcher generates a new weapon when fired to handle detonation so make sure we're only counting the bolts
			currentItemCount = getSpikeLauncherActiveSpikeCount( watcher );
				
			if ( currentItemCount !== lastItemCount )
			{
				self SetControllerUIModelValue( "spikeLauncherCounter.spikesReady", currentItemCount );
				lastItemCount = currentItemCount;
			}
			
			wait 0.1;
			weapon = self GetCurrentWeapon();
		}
	}
}

function spikesDetonating( watcher ) // self == player
{
	spikeCount = getSpikeLauncherActiveSpikeCount( watcher );
	
	if ( spikeCount > 0 )
	{
		self SetControllerUIModelValue( "spikeLauncherCounter.blasting", 1 );
		wait 2;
		self SetControllerUIModelValue( "spikeLauncherCounter.blasting", 0 );
	}
}

function createSpikeLauncherWatcher(weapon) // self == player
{
	watcher = self weaponobjects::createUseWeaponObjectWatcher( weapon, self.team );
	watcher.altName = "spike_charge";
	watcher.altWeapon = GetWeapon( "spike_charge" );
	watcher.altDetonate = false;
	watcher.watchForFire = true;
	watcher.hackable = true;
	watcher.hackerToolRadius = level.equipmentHackerToolRadius;
	watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs;
	watcher.headIcon = false;
	watcher.onDetonateCallback = &spikeDetonate;
	watcher.onStun = &weaponobjects::weaponStun;
	watcher.stunTime = 1;
	watcher.ownerGetsAssist = true;
	watcher.detonateStationary = false;
	watcher.detonationDelay = 0.0;
	watcher.detonationSound = "wpn_claymore_alert";
	watcher.onDetonationHandle = &spikesDetonating;
	self thread watchSpikeLauncherItemCountChanged( watcher );
}

function createPlayerHelicopterWatcher() // self == player
{
	watcher = self createUseWeaponObjectWatcher( "helicopter_player", self.team );

	watcher.altDetonate = true;
	watcher.headIcon = false;
	
	watcher.notEquipment = true;
}

function createClaymoreWatcher() // self == player
{
	watcher = self createProximityWeaponObjectWatcher( "claymore", self.team );
	watcher.watchForFire = true;
	watcher.onDetonateCallback = &claymoreDetonate;
	watcher.activateSound = "wpn_claymore_alert";
	watcher.hackable = true;
	watcher.hackerToolRadius = level.equipmentHackerToolRadius;
	watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs;
	watcher.ownerGetsAssist = true;

	detectionConeAngle = GetDvarInt( "scr_weaponobject_coneangle" );
	watcher.detectionDot = cos( detectionConeAngle );
	watcher.detectionMinDist = GetDvarInt( "scr_weaponobject_mindist" );
	watcher.detectionGracePeriod = GetDvarFloat( "scr_weaponobject_graceperiod" );
	watcher.detonateRadius = GetDvarInt( "scr_weaponobject_radius" );
	watcher.onStun =&weaponStun;
	watcher.stunTime = 1;
}

function voidOnSpawn( unused0, unused1 )
{
}

function voidOnDamage( unused0 )
{
}

function voidOnSpawnRetrieveTriggers( unused0, unused1 )
{	
}

function voidPickUp( unused0, unused1 )
{
}

function deleteEnt( attacker, emp, target )
{
	self delete();
}

function clearFXOnDeath( fx )
{
	fx endon("death");
	self util::waittill_any( "death", "hacked" );
	fx delete();
}

//
// generic watcher code
//

function deleteWeaponObjectInstance()
{
	if ( !isdefined( self ) )
		return;

	if ( isdefined( self.mineMover ) )
	{
		if ( isdefined( self.mineMover.killCamEnt ) )
		{
			self.mineMover.killCamEnt delete();
		}

		self.mineMover delete();
	}

	self delete();
}

function deleteWeaponObjectArray() 
{
	if ( isdefined( self.objectArray ) ) 
	{
		foreach( weaponObject in self.objectArray )
		{
			weaponObject deleteWeaponObjectInstance();
		}
	}
	
	self.objectArray = [];
}

function delayedSpikeDetonation( attacker, weapon )
{
//	wait 0.05;//flip the execute order back - previous wait will have flipped the thread order coming into this function - we want the charges to go off in the order they were placed
	if (!IsDefined(self.owner.spikeDelay))
	{
		self.owner.spikeDelay = 0;
	}
	const delayTimeIncrement = 0.3;
	delayTime = self.owner.spikeDelay;
	owner = self.owner;
	self.owner.spikeDelay += delayTimeIncrement;//delay between successive blasts
	waittillframeend;//make sure any other thread that wants to fire this frame uses an incrementing time value, if it's zero it won't wait and everyone will get zero time delay
	wait delayTime;
	owner.spikeDelay -= delayTimeIncrement;
	if (IsDefined(self))
	{
		self weaponobjects::weaponDetonate( attacker, weapon );
	}
}

function spikeDetonate( attacker, weapon, target )
{
	if ( IsDefined( weapon ) && weapon.isValid )
	{
		if ( isdefined( attacker ) )
		{
			if ( self.owner util::IsEnemyPlayer( attacker ) )
			{
//				attacker challenges::destroyedExplosive( weapon );
//				scoreevents::processScoreEvent( "destroyed_c4", attacker, self.owner, weapon );
			}
		
		}
	}
	
	thread delayedSpikeDetonation(attacker, weapon);
}

function claymoreDetonate( attacker, weapon, target )
{
	if ( !isdefined( weapon ) || !weapon.isEmp )
	{
		//PlayFX( level._equipment_explode_fx_lg, self.origin );
	}

	if ( isdefined(attacker) && self.owner util::IsEnemyPlayer( attacker ) )
	{
		attacker challenges::destroyedExplosive( weapon );
		scoreevents::processScoreEvent( "destroyed_claymore", attacker, self.owner, weapon );
	}
	
	weaponobjects::weaponDetonate( attacker, weapon );
}


function weaponDetonate( attacker, weapon )
{
	if ( IsDefined( weapon ) && weapon.isEmp )
	{
		self delete();
		return;
	}
	
	if ( isdefined( attacker ) )
	{
		if ( isdefined( self.owner ) && ( attacker != self.owner ) )
		{
			self.playDialog = true;
		}
		
		if ( IsPlayer( attacker ) )
		{
			self Detonate( attacker );
		}
		else
		{
			self Detonate();
		}
	}
	else if ( isdefined( self.owner ) && isplayer( self.owner ) )
	{
		self.playDialog = false;
		self Detonate( self.owner );
	}
	else
	{
		self Detonate();
	}
}

function detonateWhenStationary( object, delay, attacker, weapon )
{
	level endon( "game_ended" );
	
	object endon( "death" );
	object endon( "hacked" );
	object endon( "detonating" );


	if ( object isOnGround() == false )
	{
		object waittill( "stationary" );
	}
	
	self thread waitAndDetonate( object, delay, attacker, weapon );
}

function waitAndDetonate( object, delay, attacker, weapon )
{
	object endon("death");
	object endon("hacked");

	if ( !isdefined( attacker ) && !isdefined( weapon ) && object.weapon.proximityalarmactivationdelay > 0 )
	{
		// no double armed_detonation_wait detonations
		if ( ( isdefined( object.armed_detonation_wait ) && object.armed_detonation_wait ) )
		{
			return;
		}	
		
		object.armed_detonation_wait = true;
		
		while ( !( isdefined( object.proximity_deployed ) && object.proximity_deployed ) )
		{
			{wait(.05);};
		}
	}
	
	// no double detonations
	if ( ( isdefined( object.detonated ) && object.detonated ) )
	{
		return;
	}
	
	object.detonated = true;
	
	object notify( "detonating" );
	
	isEmpDetonated = IsDefined( weapon ) && weapon.isemp;
	
	if ( isEmpDetonated && object.weapon.doEmpDestroyFx )
	{
		object.stun_fx = true;

		PlayFX( level._equipment_emp_destroy_fx, object.origin + ( 0, 0, 5 ) , ( 0, RandomFloat( 360 ), 0 ) );
		empFxDelay = 1.1;
	}

	if ( !isdefined( self.onDetonateCallback ) )
	{
		return;
	}	
	
	if ( !isEmpDetonated && !isdefined( weapon ) )
	{		
		if ( isdefined( self.detonationDelay ) && self.detonationDelay > 0.0 )
		{
			if ( isdefined( self.detonationSound ) )
			{
				object playsound ( self.detonationSound );		
			}
			
			delay = self.detonationDelay;
		}
	}
	else if ( isdefined( empFxDelay ) )
	{
		delay = empFxDelay;
	}
	
	if ( delay > 0 )
	{
		wait ( delay );
	}
	
	// "destroyed_explosive" notify, for challenges
	if ( isdefined( attacker ) && IsPlayer( attacker ) && isdefined( attacker.pers["team"] ) && isdefined( object.owner ) && isdefined( object.owner.pers["team"] ) )
	{
		if ( level.teambased )
		{
			if ( attacker.pers["team"] != object.owner.pers["team"] )
			{
				attacker notify("destroyed_explosive");
				//attacker _properks::destroyedEquipment();
			}
		}
		else
		{
			if ( attacker != object.owner )
			{
				attacker notify("destroyed_explosive");
				//attacker _properks::destroyedEquipment();
			}
		}
	}
	
	object [[self.onDetonateCallback]]( attacker, weapon, undefined );	
}

function waitAndFizzleOut( object, delay )
{
	object endon("death");
	object endon("hacked");

	// no double detonations
	if ( isdefined( object.detonated ) && object.detonated == true )
	{
		return;
	}
	
	object.detonated = true;
	
	object notify( "fizzleout" );
	
	if ( delay > 0 )
	{
		wait ( delay );
	}

	if ( !isdefined( self.onFizzleOut ) )
	{
		self deleteEnt();
		return;
	}	
	
	object [[self.onFizzleOut]]();	
}

function detonateWeaponObjectArray( forceDetonation, weapon ) 
{
	undetonated = [];
	if ( isdefined(self.objectArray) ) 
	{
		for ( i = 0; i < self.objectArray.size; i++ )
		{
			if ( isdefined( self.objectArray[i] ) )
			{
				// weapon is stunned, but can be detonated later
				if ( self.objectArray[i] isstunned() && forceDetonation == false )
				{
					undetonated[undetonated.size] = self.objectArray[i];
					continue;
				}				
		
				if ( isdefined( weapon ) )
				{
					// hacked weapon, don't destroy other weapons not of the same
					if ( weapon util::isHacked() && weapon.name != self.objectArray[i].weapon.name )
					{
						undetonated[undetonated.size] = self.objectArray[i];
						continue;
					}
					// planted weapon, don't destroy hacked weapons not of the same
					else if ( self.objectArray[i] util::isHacked() && weapon.name != self.objectArray[i].weapon.name )
					{
						undetonated[undetonated.size] = self.objectArray[i];
						continue;
					}					
				}
				
				// detonateStationary watcher, don't destroy until stationary
				if ( isdefined( self.detonateStationary ) && self.detonateStationary && forceDetonation == false )
				{
					self thread detonateWhenStationary( self.objectArray[i], 0.0, undefined, weapon );
				}
				else
				{
					self thread waitAndDetonate( self.objectArray[i], 0.0, undefined, weapon );
				}
			}
		}
	}
	
	self.objectArray = undetonated;
}

function addWeaponObjectToWatcher( watcherName, weapon_instance )
{
	watcher = getWeaponObjectWatcher( watcherName );
	assert( isdefined( watcher ), "Weapon object watcher " + watcherName + " does not exist" );

	self addWeaponObject( watcher, weapon_instance );
}

function addWeaponObject( watcher, weapon_instance, weapon )
{
	if( !isdefined( watcher.storeDifferentObject ) )
		watcher.objectArray[watcher.objectArray.size] = weapon_instance;
		
	if ( !IsDefined( weapon ) )
	{
		weapon = watcher.weapon;
	}

	weapon_instance.owner = self;
	weapon_instance.detonated = false;
	weapon_instance.weapon = weapon;
	
	if( isdefined( watcher.onDamage ) )
	{
		weapon_instance thread [[watcher.onDamage]]( watcher );
	}
	else
	{
		weapon_instance thread weaponObjectDamage( watcher );
	}
	
	weapon_instance.ownerGetsAssist = watcher.ownerGetsAssist;
	weapon_instance.destroyedByEmp = watcher.destroyedByEmp;
	
	if ( isdefined( watcher.onSpawn ) )
		weapon_instance thread [[watcher.onSpawn]]( watcher, self );

	if ( isdefined( watcher.onSpawnFX ) )
		weapon_instance thread [[watcher.onSpawnFX]]();
		
	weapon_instance thread setupReconEffect();

	if( isdefined( watcher.onSpawnRetrieveTriggers ) )
		weapon_instance thread [[watcher.onSpawnRetrieveTriggers]](watcher, self);

	if ( watcher.hackable )
		weapon_instance thread hackerInit( watcher );

	if( watcher.playDestroyedDialog )
	{
		weapon_instance thread playDialogOnDeath( self );
		weapon_instance thread watchObjectDamage( self );
	}

	if( watcher.deleteOnKillbrush )
	{
		if ( isdefined ( level.deleteOnKillbrushOverride ) ) 
		{
			weapon_instance thread [[level.deleteOnKillbrushOverride]]( self, watcher );
		}
		else
		{
			weapon_instance thread deleteOnKillbrush( self );
		}
	}	
	
	if ( weapon_instance useTeamEquipmentClientField( watcher ) )
	{
		weapon_instance clientfield::set( "teamequip", 1 );
	}
	
	if ( watcher.timeOut )
	{
		weapon_instance thread weapon_object_timeout( watcher );
	}
	
	weapon_instance thread delete_on_notify( self );
		
	weapon_instance thread cleanupWatcherOnDeath( watcher );
}

function cleanupWatcherOnDeath( watcher ) // call on weapon entity
{
	self waittill( "death" );
	
	if ( isdefined( watcher ) && isdefined( watcher.objectarray ) )
	{
		removeWeaponObject( watcher, self );
	}
	
	if ( isdefined( self ) && ( self.delete_on_death === true ) )
	{
		self deleteWeaponObjectInstance();
	}
}

function weapon_object_timeout( watcher )
{
	self endon( "death" );
	
	wait ( watcher.timeOut );
	
	self deleteEnt();
}

function delete_on_notify( e_player )
{
	e_player endon( "disconnect" );
	self endon( "death" );
	
	e_player waittill( "delete_weapon_objects" );
	
	self Delete();
}

// call on player
function deleteWeaponObjectHelper( weapon_ent )
{
	watcher = self getWeaponObjectWatcherByWeapon( weapon_ent.weapon );
	if ( !isdefined( watcher ) )
	{
		return;
	}

	removeWeaponObject( watcher, weapon_ent );
}

function removeWeaponObject( watcher, weapon_ent )
{
	watcher.objectArray = array::remove_undefined( watcher.objectArray );
	ArrayRemoveValue( watcher.objectArray, weapon_ent );
}

function cleanWeaponObjectArray( watcher )
{
	watcher.objectArray = array::remove_undefined( watcher.objectArray );
}

function weapon_object_do_DamageFeedBack( weapon, attacker )
{
	if ( isdefined( weapon ) && isdefined( attacker ) )
	{
		if ( weapon.doDamageFeedback )
		{
			// if we're not on the same team then show damage feedback
			if ( level.teambased && self.owner.team != attacker.team )
			{
				if ( damagefeedback::doDamageFeedback( weapon, attacker ) )
					attacker damagefeedback::update();
			}
			// for ffa just make sure the owner isn't the same
			else if ( !level.teambased && self.owner != attacker )
			{
				if ( damagefeedback::doDamageFeedback( weapon, attacker ) )
					attacker damagefeedback::update();
			}
		}
	}	
}

function weaponObjectDamage( watcher ) // self == weapon object
{
	self endon( "death" );
	self endon( "hacked" );
	self endon( "detonating" );

	self setcandamage(true);
	self.maxhealth = 100000;
	self.health = self.maxhealth;
	self.damageTaken = 0;

	attacker = undefined;
	
	while ( 1 )
	{
		self waittill ( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, weapon, iDFlags );		
		
		self.damageTaken += damage;
		
		if ( !isPlayer( attacker ) && isdefined( attacker.owner ) )
		{
			attacker = attacker.owner;	
		}
		
		if ( isdefined( weapon ) )
		{
			// do damage feedback
			self weapon_object_do_DamageFeedBack( weapon, attacker );
			
			// most equipment should be flash/concussion-able, so it'll disable for a short period of time
			// check to see if the equipment has been flashed/concussed and disable it
			if ( watcher.stunTime > 0 && weapon.doStun )
			{
				self thread stunStart( watcher, watcher.stunTime );				
				continue;
			}			
		}		
		
		// we're currently allowing the owner/teammate to flash their own		
		if ( level.teambased && IsPlayer( attacker ) && isdefined( self.owner ) )
 		{
			// if we're not hardcore and the team is the same, do not destroy
			if( !level.hardcoreMode && self.owner.team == attacker.pers["team"] && self.owner != attacker )
			{
				continue;
			}
		}

		if ( IsDefined( watcher.shouldDamage ) && !(self [[watcher.shouldDamage]]( watcher, attacker, weapon, damage )) )
		{
			continue;
		}
			
		// TODO: figure out if this is needed anymore
		// don't allow people to destroy satchel on their team if FF is off
		// don't bother with the FF check on vehicles as it will already have been done in 
		// the vehicle damage callback
		if ( !isvehicle( self ) && !friendlyFireCheck( self.owner, attacker ) )
			continue;

		break;
	}
	
	if ( level.weaponobjectexplodethisframe )
		wait .1 + randomfloat(.4);
	else
		wait .05;
	
	if (!isdefined(self))
		return;
	
	level.weaponobjectexplodethisframe = true;
	
	thread resetWeaponObjectExplodeThisFrame();
	
	self entityheadicons::setEntityHeadIcon("none");
	
	if ( isdefined( type ) && (isSubStr( type, "MOD_GRENADE_SPLASH" ) || isSubStr( type, "MOD_GRENADE" ) || isSubStr( type, "MOD_EXPLOSIVE" )) )
		self.wasChained = true;
	
	if ( isdefined( iDFlags ) && (iDFlags & 8) )
	{
		self.wasDamagedFromBulletPenetration = true;
		//attacker _properks::shotEquipment( self.owner, iDFlags );
	}
	
	self.wasDamaged = true;
	
	watcher thread waitAndDetonate( self, 0.0, attacker, weapon );

	// won't get here; got death notify.
}

//Notify player that their equipment was destroyed
function playDialogOnDeath( owner )
{
	owner endon("death");
	owner endon("disconnect");
	self endon( "hacked" );

	self waittill( "death" );

	if( isdefined(self.playDialog) && self.playDialog )
	{
		if( isdefined( level.playEquipmentDestroyedOnPlayer ) )
		{
			owner [[level.playEquipmentDestroyedOnPlayer]]( );
		}		
	}		
}

function watchObjectDamage( owner )
{
	owner endon("death");
	owner endon("disconnect");
	self endon( "hacked" );
	self endon( "death" );

	while(1)
	{
		self waittill( "damage", damage, attacker );

		if( isdefined(attacker) && isPlayer(attacker) && attacker != owner )
		{
			self.playDialog = true;
		}
		else
		{
			self.playDialog = false;
		}
	}
}

function stunStart( watcher, time )
{
	self endon ( "death" );

	if ( self isStunned() )
	{
		return;
	}

	if ( isdefined( self.cameraHead ) )
	{
		//self.cameraHead clientfield::set( "cf_m_stun", 1 );
	}
	//self clientfield::set( "cf_m_stun", 1 );
	
	// allow specific effects
	if ( isdefined( watcher.onStun ) )
		self thread [[watcher.onStun]]();
	
	if ( watcher.name == "rcbomb" )
	{
		self.owner util::freeze_player_controls( true );
	}
	
	if ( isdefined( time ) )
	{
		wait ( time );
	}
	else
	{
		return;
	}

	if ( watcher.name == "rcbomb" )
	{
		self.owner util::freeze_player_controls( false );
	}
	
	self stunStop();
}

function stunStop()
{
	self notify ( "not_stunned" );
	if ( isdefined( self.cameraHead ) )
	{
		//self.cameraHead clientfield::set( "cf_m_stun", 0 );
	}
	//self clientfield::set( "cf_m_stun", 0 );
}

function weaponStun() // self == weapon object
{
	self endon( "death" );
	self endon( "not_stunned" );
	
	origin = self GetTagOrigin( "tag_fx" );

	if ( !isdefined( origin ) )
	{
		origin = self.origin + ( 0, 0, 10 );
	}

	self.stun_fx = spawn( "script_model", origin );
	self.stun_fx SetModel( "tag_origin" );
	self thread stunFxThink( self.stun_fx );
	wait ( 0.1 );

	PlayFXOnTag( level._equipment_spark_fx, self.stun_fx, "tag_origin" );
	
}

function stunFxThink( fx )
{
	fx endon("death");
	self util::waittill_any( "death", "not_stunned" );
	fx delete();
}

function isStunned()
{
	return ( isdefined( self.stun_fx ) );
}

function weaponObjectFizzleOut()
{
	self endon( "death" );
	
	PlayFX( level._equipment_fizzleout_fx, self.origin );
		
	deleteEnt();
}

function resetWeaponObjectExplodeThisFrame()
{
	wait .05;
	level.weaponobjectexplodethisframe = false;
}

function getWeaponObjectWatcher( name )
{
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		return undefined;
	}
	
	for ( watcher = 0; watcher < self.weaponObjectWatcherArray.size; watcher++ )
	{
		if ( self.weaponObjectWatcherArray[watcher].name == name || (isdefined(self.weaponObjectWatcherArray[watcher].altName) && self.weaponObjectWatcherArray[watcher].altName == name) )
		{
			return self.weaponObjectWatcherArray[watcher];
		}
	}
	
	return undefined;
}

function getWeaponObjectWatcherByWeapon( weapon ) // self == player
{
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		return undefined;
	}
	
	if ( !isdefined( weapon ) )
	{
		return undefined;
	}
	
	for ( watcher = 0; watcher < self.weaponObjectWatcherArray.size; watcher++ )
	{
		if ( isdefined(self.weaponObjectWatcherArray[watcher].weapon) && ( self.weaponObjectWatcherArray[watcher].weapon == weapon || self.weaponObjectWatcherArray[watcher].weapon == weapon.rootWeapon ) )
		{
			return self.weaponObjectWatcherArray[watcher];
		}
		if ( isdefined(self.weaponObjectWatcherArray[watcher].weapon) && isdefined(self.weaponObjectWatcherArray[watcher].altWeapon) && self.weaponObjectWatcherArray[watcher].altWeapon == weapon )
		{
			return self.weaponObjectWatcherArray[watcher];
		}
	}
	
	return undefined;
}

function resetWeaponObjectWatcher( watcher, ownerTeam )
{
	if ( watcher.deleteOnPlayerSpawn == 1 || ( isdefined( watcher.ownerTeam ) && watcher.ownerTeam != ownerTeam ) )
	{
		self notify( "weapon_object_destroyed" );
		watcher deleteWeaponObjectArray();
	}
	
	// player may have switched teams
	watcher.ownerTeam = ownerTeam;
}

function createWeaponObjectWatcher( weaponname, ownerTeam )
{
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		self.weaponObjectWatcherArray = [];
	}
	
	weaponObjectWatcher = getWeaponObjectWatcher( weaponname );

	if ( !isdefined( weaponObjectWatcher ) )
	{ 
		weaponObjectWatcher = SpawnStruct();
		self.weaponObjectWatcherArray[self.weaponObjectWatcherArray.size] = weaponObjectWatcher;
	
		weaponObjectWatcher.name = weaponname;
		weaponObjectWatcher.type = "use";
		weaponObjectWatcher.weapon = GetWeapon( weaponname );
		weaponObjectWatcher.watchForFire = false;
		weaponObjectWatcher.hackable = false;
		weaponObjectWatcher.altDetonate = false;
		weaponObjectWatcher.detectable = true;
		weaponObjectWatcher.headIcon = false;
		weaponObjectWatcher.stunTime = 0;
		weaponObjectWatcher.timeOut = 0;
		weaponObjectWatcher.destroyedByEmp = true;
		weaponObjectWatcher.activateSound = undefined;
		weaponObjectWatcher.ignoreDirection = undefined;
		weaponObjectWatcher.immediateDetonation = undefined; 
		weaponObjectWatcher.deploySound = weaponObjectWatcher.weapon.firesound;
		weaponObjectWatcher.deploySoundPlayer = weaponObjectWatcher.weapon.firesoundplayer;
		weaponObjectWatcher.pickUpSound = weaponObjectWatcher.weapon.pickupsound;
		weaponObjectWatcher.pickUpSoundPlayer = weaponObjectWatcher.weapon.pickupsoundplayer;
		weaponObjectWatcher.altWeapon = weaponObjectWatcher.weapon.altweapon;
		weaponObjectWatcher.ownerGetsAssist = false;
		weaponObjectWatcher.playDestroyedDialog = true;
		weaponObjectWatcher.deleteOnKillbrush = true;
		weaponObjectWatcher.deleteOnDifferentObjectSpawn = true;
		weaponObjectWatcher.enemyDestroy = false;
		weaponObjectWatcher.deleteOnPlayerSpawn = level.deleteExplosivesOnSpawn;
		weaponObjectWatcher.ignoreVehicles = false;
		weaponObjectWatcher.ignoreAI = false;
		weaponObjectWatcher.activationDelay = 0;
		
		// callbacks
		weaponObjectWatcher.onSpawn = undefined;
		weaponObjectWatcher.onSpawnFX = undefined;
		weaponObjectWatcher.onSpawnRetrieveTriggers = undefined;
		weaponObjectWatcher.onDetonateCallback = undefined;
		weaponObjectWatcher.onStun = undefined;
		weaponObjectWatcher.onStunFinished = undefined;
		weaponObjectWatcher.onDestroyed = undefined;
		weaponObjectWatcher.onFizzleOut = &weaponobjects::weaponObjectFizzleOut;
		weaponObjectWatcher.shouldDamage = undefined;
		weaponObjectWatcher.onSupplementalDetonateCallback = undefined;

		if ( !isdefined( weaponObjectWatcher.objectArray ) )
			weaponObjectWatcher.objectArray = [];
	}

	resetWeaponObjectWatcher( weaponObjectWatcher, ownerTeam );
	
	return weaponObjectWatcher;
}

function createUseWeaponObjectWatcher( weaponname, ownerTeam )
{
	weaponObjectWatcher = createWeaponObjectWatcher( weaponname, ownerTeam );

	weaponObjectWatcher.type = "use";
	weaponObjectWatcher.onSpawn =&onSpawnUseWeaponObject;

	return weaponObjectWatcher;
}

function createProximityWeaponObjectWatcher( weaponname, ownerTeam )
{
	weaponObjectWatcher = createWeaponObjectWatcher( weaponname, ownerTeam );

	weaponObjectWatcher.type = "proximity";
	weaponObjectWatcher.onSpawn =&onSpawnProximityWeaponObject;

	detectionConeAngle = GetDvarInt( "scr_weaponobject_coneangle" );
	weaponObjectWatcher.detectionDot = cos( detectionConeAngle );
	weaponObjectWatcher.detectionMinDist = GetDvarInt( "scr_weaponobject_mindist" );
	weaponObjectWatcher.detectionGracePeriod = GetDvarFloat( "scr_weaponobject_graceperiod" );
	weaponObjectWatcher.detonateRadius = GetDvarInt( "scr_weaponobject_radius" );
	
	return weaponObjectWatcher;
}

function commonOnSpawnUseWeaponObject( watcher, owner ) // self == weapon (for example: the claymore)
{
	level endon ( "game_ended" );
	self endon ( "death" );
	self endon ( "hacked" );

	if ( watcher.detectable )
	{
//		if ( isdefined(watcher.isMovable) && watcher.isMovable )
//			self thread weaponObjectDetectionMovable( owner.pers["team"] );
//		else
//			self thread weaponObjectDetectionTrigger_wait( owner.pers["team"] );
		
		if ( watcher.headIcon && level.teamBased )
		{
			self util::waitTillNotMoving();
			
			if ( isdefined( self ) )
			{
				offset = self.weapon.weaponHeadObjectiveHeight;
				
				v_up = AnglesToUp( self.angles );
				x_offset = abs( v_up[0] );
				y_offset = abs( v_up[1] );
				z_offset = abs( v_up[2] );
				
				if( x_offset > y_offset && x_offset > z_offset )
				{
					//v_up = v_up * ( 1, 0, 0 );
				}
				else if( y_offset > x_offset && y_offset > z_offset )
				{
					//v_up = v_up * ( 0, 1, 0 );
				}
				else if( z_offset > x_offset && z_offset > y_offset )
				{
					v_up = v_up * ( 0, 0, 1 );
				}
				
				up_offset_modified = v_up * offset;
				up_offset = AnglesToUp( self.angles ) * offset;
				
				objective = GetEquipmentHeadObjective( self.weapon );
				self entityheadicons::setEntityHeadIcon( owner.pers["team"], owner, up_offset, objective );
			}
		}
	}
}

function WasProximityAlarmActivatedBySelf()
{
	return isdefined( self.owner.ProximityAmlarmEnt ) && self.owner.ProximityAmlarmEnt == self;
}

function ProximityAlarmActivate( active, watcher )
{
	if ( !IsDefined( self.owner ) || !IsPlayer( self.owner ) )
	{
		return;
	}
	
	if ( active && !isdefined( self.owner.ProximityAmlarmEnt ) )
	{
		self.owner.ProximityAmlarmEnt = self;
		self.owner clientfield::set_to_player( "proximity_alarm", 2 );
		self.owner clientfield::set_player_uimodel( "hudItems.proximityAlarm", 2 );
	}
	else
	{
		if ( !isdefined( self ) || self WasProximityAlarmActivatedBySelf() || self.owner clientfield::get_to_player( "proximity_alarm" ) == 1 )
		{
			self.owner.ProximityAmlarmEnt = undefined;
			self.owner clientfield::set_to_player( "proximity_alarm", 0 );
			self.owner clientfield::set_player_uimodel( "hudItems.proximityAlarm", 0 );
		}
	}
	
}

function ProximityAlarmLoop( watcher, owner ) // self == weapon entity (for example: the claymore)
{
	level endon ( "game_ended" );
	self endon ( "death" );
	self endon ( "hacked" );
	self endon( "detonating" );
	
	if ( self.weapon.proximityalarminnerradius <= 0 )
	{
		return;
	}
	
	self util::waitTillNotMoving();
	
	delayTimeSec = self.weapon.proximityalarmactivationdelay / 1000;
	
	if ( delayTimeSec > 0 )
	{
		wait delayTimeSec;
		
		if ( !IsDefined( self ) )
		{
			return;
		}
	}	
	
	if( !( isdefined( self.owner._disable_proximity_alarms ) && self.owner._disable_proximity_alarms ) )
	{
		self.owner clientfield::set_to_player( "proximity_alarm", 1 );
		self.owner clientfield::set_player_uimodel( "hudItems.proximityAlarm", 1 );
	}
	
	self.proximity_deployed = true;
	
	alarmStatusOld =  "notify";
	alarmStatus = "off";
	
	// this is executing for each equipment with proximity alarm
	// the loop will turn on/off the alarm
	// the loop will play proximityAlarmActivateSound when going from 'off' to 'on'
	while ( 1 )
	{
		wait( .05 );
		
		if ( !IsDefined( self.owner ) || !IsPlayer( self.owner ) )
		{
			return;
		}
		
		if ( IsAlive( self.owner ) == false && self.owner util::isUsingRemote() == false )
		{
			self ProximityAlarmActivate( false, watcher );
			return;
		}
		
		if( ( isdefined( self.owner._disable_proximity_alarms ) && self.owner._disable_proximity_alarms ) )
		{
			self ProximityAlarmActivate( false, watcher );
		}
		else if ( (alarmStatus != alarmStatusOld) || (alarmStatus == "on" && !isdefined( self.owner.ProximityAmlarmEnt )) ) // check if there is a change in the alarm status
		{
			if ( alarmStatus == "on" )
			{
				// the alarm turns on if it was not already on
				if ( alarmStatusOld == "off" && isdefined( watcher ) && isdefined( watcher.proximityAlarmActivateSound ) )
				{
					// play alarm activated sound, plays only once even if more players come within range
					//self playsound( watcher.proximityAlarmActivateSound );
					//changed this playsound to the line below because the sound origin was slightly below ground and occluding the sound.
					playsoundatposition( watcher.proximityAlarmActivateSound , (self.origin + (0,0,32)) );
				}
				
				// turn on the alarm
				self ProximityAlarmActivate( true, watcher );
			}
			else
			{				
				// turn off the alarm
				self ProximityAlarmActivate( false, watcher );
			}
			
			alarmStatusOld = alarmStatus;
		}
		
		alarmStatus =  "off";
		
		actors = GetActorArray();
		players = Getplayers();
		detectEntities = arraycombine( players, actors, false, false );

		foreach ( entity in detectEntities )
		{
			wait( .05 ); // only handle 1 player per frame

			if ( !isdefined( entity ) )
			{
				continue;
			}
			
			owner = entity;
			if( IsActor( entity ) && ( !isDefined( entity.isAiClone ) || !entity.isAiClone ) )
			{
				continue;
			}
			else if( isActor( entity ) )
			{
				owner = entity.owner;
			}
			
			if ( entity.team == "spectator" )
			{
				continue;
			}			
			
			if ( level.weaponObjectDebug != 1 )
			{
				if( owner HasPerk( "specialty_detectexplosive" ) )
				{
					continue;
				}
				if ( isdefined( self.owner ) && owner == self.owner )
				{
					continue;
				}
				if ( !friendlyFireCheck( self.owner, owner, 0 ) )
				{	
					continue;
				}
			}
	
			if ( self isstunned() ) 
				continue;
	
			if( !IsAlive( entity ) )
				continue;
			
			if ( isdefined( watcher.immunespecialty ) && owner hasPerk( watcher.immunespecialty ) )
				continue;
				
			radius = self.weapon.proximityalarmouterradius;
			distanceSqr = DistanceSquared( self.origin, entity.origin );
			
			if ( radius * radius < distanceSqr )
			{								
				// this player is outside outer radius
				continue;
			}
			
			if ( entity damageConeTrace( self.origin, self ) == 0 )			
			{
				// this player cannot be damaged
				continue;
			}
			
			if ( alarmStatusOld == "on" )
			{
				// this player is inside outer radius and the alarm was already on
				alarmStatus = "on";
				break;
			}
			
			radius = self.weapon.proximityalarminnerradius;
			
			if ( radius * radius < distanceSqr )
			{
				// this player is not triggering the alarm
				continue;
			}			
			
			// this player is inside the inner radius 
			alarmStatus = "on";
			break;
		}
	}	
}

// self is the entity with proximity alarm
function commonOnSpawnUseWeaponObjectProximityAlarm( watcher, owner ) // self == weapon entity (for example: the claymore)
{	
	/#
	if ( level.weaponObjectDebug == 1 )
	{
		self thread proximityAlarmWeaponObjectDebug( watcher );
	}
	#/
		
	// waits until self is dead
	self ProximityAlarmLoop( watcher, owner );
	
	// cleans up after self death	
	self ProximityAlarmActivate( false, watcher );

	
}

function onSpawnUseWeaponObject( watcher, owner ) // self == weapon (for example: the claymore)
{
	self thread commonOnSpawnUseWeaponObject(watcher, owner);
	self thread commonOnSpawnUseWeaponObjectProximityAlarm(watcher, owner);
}

function onSpawnProximityWeaponObject( watcher, owner ) // self == weapon (for example: the claymore)
{
	self.protected_entities = [];
	
	self thread commonOnSpawnUseWeaponObject(watcher, owner);

	if ( isdefined( level._proximityWeaponObjectDetonation_override ) )
	{
		self thread [[level._proximityWeaponObjectDetonation_override]]( watcher );
	}
	else
	{
		self thread proximityWeaponObjectDetonation( watcher );
	}

	/#
	if ( level.weaponObjectDebug == 1 )
	{
		self thread proximityWeaponObjectDebug( watcher );
	}
	#/
}

function watchWeaponObjectUsage() // self == player
{
	self endon( "disconnect" );
	
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		self.weaponObjectWatcherArray = [];
	}
	
	self thread watchWeaponObjectSpawn( "grenade_fire" );
	self thread watchWeaponObjectSpawn( "grenade_launcher_fire" );
	self thread watchWeaponObjectSpawn( "missile_fire" );
	self thread watchWeaponObjectDetonation();
	self thread watchWeaponObjectAltDetonation();
	self thread watchWeaponObjectAltDetonate();
	self thread deleteWeaponObjectsOn();	
}

// check for projectile type weapon objects spawning
function watchWeaponObjectSpawn( notify_type ) // self == player
{
	self notify( "watchWeaponObjectSpawn_" + notify_type );
	self endon( "watchWeaponObjectSpawn_" + notify_type );
	self endon( "disconnect" );

	while(1)
	{
		self waittill( notify_type, weapon_instance, weapon );
		
		if( ( ( SessionModeIsCampaignZombiesGame() ) || ( isdefined( level.projectiles_should_ignore_world_pause ) && level.projectiles_should_ignore_world_pause ) ) && IsDefined( weapon_instance ) )
		{
			weapon_instance SetIgnorePauseWorld( true );
		}

		// need to increment the used stat for claymore and c4
		if ( weapon.setUsedStat && !self util::isHacked() )
		{
			self AddWeaponStat( weapon, "used", 1 );
		}

		watcher = getWeaponObjectWatcherByWeapon( weapon );
		if ( isdefined( watcher ) )
		{	
			// remove any empty objects
			cleanWeaponObjectArray( watcher );

			if ( weapon.maxinstancesallowed )
			{
				// This allows you to have only a certain number of this weapon object type in the world 
				// Once you reach the max the first will "fizzle out".
				if(  watcher.objectarray.size > (weapon.maxinstancesallowed - 1))
				{
					watcher thread waitAndFizzleOut( watcher.objectarray[0], 0.1 );
					watcher.objectarray[0] = undefined;

					cleanWeaponObjectArray( watcher );
				}
			}
	
			self addWeaponObject( watcher, weapon_instance );
		}
	}
}

function anyObjectsInWorld( weapon )
{
	objectsInWorld = false;
	for( i = 0; i < self.weaponObjectWatcherArray.size; i++)
	{
		if( self.weaponObjectWatcherArray[i].weapon != weapon )
			continue;

		if( isdefined( self.weaponObjectWatcherArray[i].onDetonateCallback ) && self.weaponObjectWatcherArray[i].objectarray.size > 0 )
		{
			objectsInWorld = true;
			break;
		}
	}
	return objectsInWorld;
}

/#
function proximitySphere( origin, innerRadius, inColor, outerRadius, outColor )
{
	self endon("death");	
	
	while(1)
	{
		if ( isdefined( innerRadius ) )
		{
			dev::debug_sphere( origin, innerRadius, inColor, 0.25, 1 ); // inner
		}		
		
		if ( isdefined( outerRadius ) )
		{
			dev::debug_sphere( origin, outerRadius, outColor, 0.25, 1 ); // outer
		}		
		
		wait .05;
	}
}

function proximityAlarmWeaponObjectDebug( watcher )
{
	self endon("death");
	
	self util::waitTillNotMoving();
	
	if ( !isdefined( self ) )
	{
		return;
	}
	
	self thread proximitySphere( self.origin, self.weapon.proximityalarminnerradius, (0,0.75,0), self.weapon.proximityalarmouterradius, (0,0.75,0) );	
}

function proximityWeaponObjectDebug( watcher )
{
	self endon("death");
	
	self util::waitTillNotMoving();
	
	if ( !isdefined( self ) )
	{
		return;
	}
	
	if( isdefined( watcher.ignoreDirection ) )
	{
		self thread proximitySphere( self.origin, watcher.detonateRadius, (1,.85,0), self.weapon.explosionRadius, (1,0,0) );	
	}
	else
	{
		self thread showCone( acos( watcher.detectionDot ), watcher.detonateRadius, (1,.85,0) );
		self thread showCone( 60, 256, (1,0,0) );
	}	
}

function showCone( angle, range, color )
{
	self endon("death");
	
	start = self.origin;
	forward = anglestoforward(self.angles);
	right = vectorcross( forward, (0,0,1) );
	up = vectorcross( forward, right );
	
	fullforward = forward * range * cos( angle );
	sideamnt = range * sin( angle );
	
	while(1)
	{
		prevpoint = (0,0,0);
		for ( i = 0; i <= 20; i++ )
		{
			coneangle = i/20.0 * 360;
			point = start + fullforward + sideamnt * (right * cos(coneangle) + up * sin(coneangle));
			if ( i > 0 )
			{
				line( start, point, color );
				line( prevpoint, point, color );
			}
			prevpoint = point;
		}
		wait .05;
	}
}
#/

function weaponObjectDetectionMovable( ownerTeam ) // self == weapon (for example: the claymore)
{
	self endon ( "end_detection" );
	level endon ( "game_ended" );
	self endon ( "death" );
	self endon ( "hacked" );

	if ( !level.teambased )
		return;

	self.detectId = "rcBomb" + getTime() + randomInt( 1000000 );
}


function setIconPos( item, icon, heightIncrease )
{
	icon.x = item.origin[0];
	icon.y = item.origin[1];
	icon.z = item.origin[2]+heightIncrease;
}

function weaponObjectDetectionTrigger_wait( ownerTeam ) // self == weapon (for example: the claymore)
{
	self endon ( "death" );
	self endon ( "hacked" );
	self endon( "detonating" );

	util::waitTillNotMoving();

	self thread weaponObjectDetectionTrigger( ownerTeam );
}

function weaponObjectDetectionTrigger( ownerTeam ) // self == weapon (for example: the claymore)
{
	trigger = spawn( "trigger_radius", self.origin-(0,0,128), 0, 512, 256 );
	trigger.detectId = "trigger" + getTime() + randomInt( 1000000 );
	
	trigger SetHintLowPriority( true );

	self util::waittill_any( "death", "hacked", "detonating" );
	trigger notify ( "end_detection" );

	if ( isdefined( trigger.bombSquadIcon ) )
		trigger.bombSquadIcon destroy();
	
	trigger delete();	
}

function hackerTriggerSetVisibility( owner )
{
	self endon( "death" );
	
	assert( IsPlayer( owner ) );

	ownerTeam = owner.pers["team"];

	for ( ;; )
	{		
		if ( level.teamBased && isdefined( ownerTeam ) )
		{
			self SetVisibleToAllExceptTeam( ownerTeam );
			self SetExcludeTeamForTrigger( ownerTeam );
		}
		else
		{
			self SetVisibleToAll();
			self SetTeamForTrigger( "none" );
		}

		if ( isdefined( owner ) ) 
		{
			self SetInvisibleToPlayer( owner );
		}
	
		level util::waittill_any( "player_spawned", "joined_team" );
	}
}

function hackerNotMoving()
{
	self endon( "death" );
	self util::waitTillNotMoving();
	self notify( "landed" );
}

function hackerInit( watcher )
{
	self thread hackerNotMoving();

	event = self util::waittill_any_return( "death", "landed" );

	if ( event == "death" )
	{
		return;
	}

	triggerOrigin = self.origin;
	if ( "" != self.weapon.hackerTriggerOriginTag )
	{
		triggerOrigin = self GetTagOrigin( self.weapon.hackerTriggerOriginTag );
	}

	// set up a trigger for a hacker
	self.hackerTrigger = spawn( "trigger_radius_use", triggerOrigin, level.weaponobjects_hacker_trigger_width, level.weaponobjects_hacker_trigger_height );

	/#
		//_teargrenades::drawcylinder( self.hackerTrigger.origin, level.weaponobjects_hacker_trigger_width, level.weaponobjects_hacker_trigger_height, 0, "hacker_debug" );	
	#/

	self.hackerTrigger SetHintLowPriority( true );
	self.hackerTrigger SetCursorHint( "HINT_NOICON", self );
	self.hackerTrigger SetIgnoreEntForTrigger( self );

	self.hackerTrigger EnableLinkTo();
	self.hackerTrigger LinkTo( self );

	if( isdefined( level.hackerHints[self.weapon.name] ) )
	{
		self.hackerTrigger SetHintString( level.hackerHints[self.weapon.name].hint );
	}
	else
	{
		self.hackerTrigger SetHintString( &"MP_GENERIC_HACKING" );
	}

	
	self.hackerTrigger SetPerkForTrigger( "specialty_disarmexplosive" );
	self.hackerTrigger thread hackerTriggerSetVisibility( self.owner );
	
	self thread hackerThink( self.hackerTrigger, watcher );
}

function hackerThink( trigger, watcher ) // self == weapon_instance
{
	self endon( "death" );

	for ( ;; )
	{
		trigger waittill( "trigger", player, instant );

		if ( !isdefined( instant ) && !trigger hackerResult( player, self.owner ) )
		{
			continue;
		}

		self ItemHacked( watcher, player );

		// the hacker thread will be respawned with the new owner, this thread can be killed
		return;
	}
}

function ItemHacked( watcher, player )
{
	self ProximityAlarmActivate( false, watcher );
	
	self.owner hackerRemoveWeapon( self );

	if( isdefined( level.playEquipmentHackedOnPlayer ) )
	{
		self.owner [[level.playEquipmentHackedOnPlayer]]( );
	}		

	if ( self.weapon.ammoCountEquipment > 0 && isdefined( self.ammo ) )
	{
		ammoLeftEquipment = self.ammo;
		
		if ( self.weapon.rootWeapon == GetWeapon( "trophy_system" ) )
		{
			player trophy_system::ammo_weapon_hacked( ammoLeftEquipment );
		}
	}	
	
	self.hacked = true;
	self SetMissileOwner( player );
	self SetTeam( player.pers["team"] );	
	self.owner = player;	
	self clientfield::set( "retrievable", 0 );

	if ( self.weapon.doHackedStats )
	{
		scoreevents::processScoreEvent( "hacked", player );
		player AddWeaponStat( GetWeapon( "pda_hack" ), "CombatRecordStat", 1 );
		
		player challenges::hackedOrDestroyedEquipment();
	}

	// detonation info for the C4
	if ( self.weapon.rootWeapon == level.weaponSatchelCharge && isdefined( player.lowerMessage ) )
	{
		player.lowerMessage SetText( &"PLATFORM_SATCHEL_CHARGE_DOUBLE_TAP" );
		player.lowerMessage.alpha = 1;
		player.lowerMessage FadeOverTime( 2.0 );
		player.lowerMessage.alpha = 0;
	}

	// kill the previous owner's threads
	self notify( "hacked", player );
	level notify( "hacked", self, player );

	if ( isdefined( self.cameraHead ) )
	{
		self.cameraHead notify( "hacked", player );
	}

	/#
		//level notify ( "hacker_debug" );
	#/

	{wait(.05);}; // let current threads clean up from the 'hacked' notify
	
	if ( isdefined( player ) && player.sessionstate == "playing" )
	{
		// this will re-initialize the watcher system for this equipment
		player notify( "grenade_fire", self, self.weapon, true );
	}
	else
	{
		watcher thread waitAndDetonate( self, 0.0, undefined, self.weapon );
	}
}

function hackerUnfreezePlayer( player )
{
	self endon( "hack_done" );
	self waittill( "death" );

	if ( isdefined( player ) )
	{
		player util::freeze_player_controls( false );
		player EnableWeapons();
	}
}

function hackerResult( player, owner ) // self == trigger_radius
{
	success = true;
	time = GetTime();
	hackTime = GetDvarfloat( "perk_disarmExplosiveTime" );

	if ( !canHack( player, owner, true ) )
	{
		return false;
	}

	self thread hackerUnfreezePlayer( player );

	while ( time + ( hackTime * 1000 ) > GetTime() )
	{
		if ( !canHack( player, owner, false ) )
		{
			success = false;
			break;
		}

		if ( !player UseButtonPressed() )
		{
			success = false;
			break;
		}

		if ( !isdefined( self ) )
		{
			success = false;
			break;
		}

	/*
		if ( !player IsTouching( self ) )
		{
			success = false;
			break;
		}
	*/

		player util::freeze_player_controls( true );
		player DisableWeapons();

		if ( !isdefined( self.progressBar ) )
		{
			self.progressBar = player hud::createPrimaryProgressBar();
			self.progressBar.lastUseRate = -1;
			self.progressBar hud::showElem();
			self.progressBar hud::updateBar( 0.01, 1 / hackTime );

			self.progressText = player hud::createPrimaryProgressBarText();
			self.progressText setText( &"MP_HACKING" );
			self.progressText hud::showElem();
			player PlayLocalSound ( "evt_hacker_hacking" );
		}
		
		{wait(.05);};
	}

	if ( isdefined( player ) )
	{
		player util::freeze_player_controls( false );
		player EnableWeapons();
	}

	if ( isdefined( self.progressBar ) )
	{
		self.progressBar hud::destroyElem();
		self.progressText hud::destroyElem();
	}

	if ( isdefined( self ) )
	{
		self notify( "hack_done" );
	}

	return success;
}

function canHack( player, owner, weapon_check )
{
	if ( !isdefined( player ) )
		return false;
	
	if ( !IsPlayer( player ) )
		return false;

	if ( !IsAlive( player ) )
		return false;

	if ( !isdefined( owner ) )
		return false;

	if ( owner == player )
		return false;
		
	if ( level.teambased && player.team == owner.team )
		return false;

	if ( ( isdefined( player.isDefusing ) && player.isDefusing ) )
		return false;

	if ( ( isdefined( player.isPlanting ) && player.isPlanting ) )
		return false;

	if ( isdefined( player.proxBar ) && !player.proxBar.hidden )
		return false;

	if ( isdefined( player.revivingTeammate ) && player.revivingTeammate == true )
		return false;

	if ( !player IsOnGround() )
		return false;
		
	if ( player IsInVehicle() )
		return false;

	if ( player IsWeaponViewOnlyLinked() )
		return false;

	if ( !player HasPerk( "specialty_disarmexplosive" ) )
		return false;

	if ( player IsEMPJammed() )
		return false;

	if ( isdefined( player.laststand ) && player.laststand )
		return false;

	if ( weapon_check )
	{
		if ( player IsThrowingGrenade() )
			return false;

		if ( player IsSwitchingWeapons() )
			return false;

		if ( player IsMeleeing() )
			return false;

		weapon = player GetCurrentWeapon(); 

		if ( !isdefined( weapon ) )
			return false;

		if ( weapon == level.weaponNone )
			return false;

		if ( weapon.isEquipment && player IsFiring() )
			return false;

		if ( weapon.isSpecificUse )
			return false;
	}

	return true;
}

function hackerRemoveWeapon( weapon_instance )
{
	for( i = 0; i < self.weaponObjectWatcherArray.size; i++)
	{
		if( self.weaponObjectWatcherArray[i].weapon != weapon_instance.weapon.rootWeapon )
		{
			continue;
		}

		// remove any empty objects and the hacked object
		removeWeaponObject( self.weaponObjectWatcherArray[i], weapon_instance );

		return;
	}
}

function proximityWeaponObject_CreateDamageArea( watcher )
{
	damagearea = spawn("trigger_radius", self.origin + (0,0,0-watcher.detonateRadius), level.aiTriggerSpawnFlags | level.vehicleTriggerSpawnFlags, watcher.detonateRadius, watcher.detonateRadius*2);
	damagearea EnableLinkTo();
	damagearea LinkTo( self );

	self thread deleteOnDeath( damagearea );
	
	return damagearea;
}

function proximityWeaponObject_ValidTriggerEntity( watcher, ent )
{
	if ( level.weaponObjectDebug != 1 )
	{
		if ( isdefined( self.owner ) && ent == self.owner )
			return false;
		if ( IsVehicle( ent ) )
		{
			if ( watcher.ignoreVehicles )
				return false;
			
			if ( self.owner === ent.owner ) // owner's own vehicle
				return false;
		}
	
		if ( !friendlyFireCheck( self.owner, ent, 0 ) )
			return false;
	
		if ( watcher.ignoreVehicles && IsAI( ent ) && !( isdefined( ent.isAiClone ) && ent.isAiClone ) )
			return false;
	}
	if ( ( lengthsquared( ent getVelocity() ) < 10 ) && !isdefined( watcher.immediateDetonation ) )
		return false;
	
	if ( !ent shouldAffectWeaponObject( self, watcher ) )
		return false;
	
	if ( self isstunned() ) 
		return false;
	
	if( IsPlayer( ent ) )
	{
		if ( !IsAlive(ent) ) //Make sure player watching their killcam won't set it off
			return false;
	
		if ( isdefined( watcher.immunespecialty ) && ent hasPerk( watcher.immunespecialty ) )
			return false;
	}
	
	return true;
}

function	proximityWeaponObject_RemoveSpawnProtectOnDeath( ent )
{
	self endon("death");
	
	ent util::waittill_any("death", "disconnected");
	
	ArrayRemoveValue( self.protected_entities, ent );
}

function	proximityWeaponObject_SpawnProtect( watcher, ent )
{
	self endon("death");
	ent endon("death");
	ent endon("disconnect");
	
	self.protected_entities[self.protected_entities.size] = ent;
	
	// this function will not get cleaned up until either ent dies which should not cause problems
	self thread proximityWeaponObject_RemoveSpawnProtectOnDeath( ent );
	
	radius_sqr = watcher.detonateRadius * watcher.detonateRadius;
	
	while ( 1 )
	{
		if ( DistanceSquared( ent.origin, self.origin ) > radius_sqr )
		{
			ArrayRemoveValue( self.protected_entities, ent );
			return;
		}
		
		wait( 0.5 );
	}
}

function proximityWeaponObject_IsSpawnProtected( watcher, ent )
{
		if( !IsPlayer( ent ) )
			return false;
			
		foreach( protected_ent in self.protected_entities )
		{
			if ( protected_ent == ent )
				return true;
		}
		
		linked_to = self GetLinkedEnt();
		if (linked_to === ent )
			return false;

		if ( ent player::is_spawn_protected() )
		{
			self thread proximityWeaponObject_SpawnProtect( watcher, ent );
			return true;
		}
		
		return false;
}

function proximityWeaponObject_DoDetonation( watcher, ent, traceOrigin )
{	
	self endon( "death" );
	self endon( "hacked" );

	self notify( "kill_target_detection" );

	if ( isdefined(watcher.activateSound) )
	{
		self playsound (watcher.activateSound);
	}
	
	wait watcher.detectionGracePeriod;

	if ( IsPlayer( ent ) && ent HasPerk( "specialty_delayexplosive" ) )
	{
		wait( GetDvarfloat( "perk_delayExplosiveTime" ) );
	}
	
	self entityheadicons::setEntityHeadIcon("none");
	
	// move up one unit	
	self.origin = traceOrigin;
	
	if ( isdefined( self.owner ) && isplayer( self.owner ) )
	{	
		self [[watcher.onDetonateCallback]]( self.owner, undefined, ent );
	}
	else
	{
		self [[watcher.onDetonateCallback]]( undefined, undefined, ent );
	}
}

function proximityWeaponObject_ActivationDelay( watcher )
{
	self util::waitTillNotMoving();

	if ( watcher.activationDelay )
	{
		wait( watcher.activationDelay );
	}
}

function proximityWeaponObject_WaitTillFrameEndAndDoDetonation( watcher, ent, traceOrigin )
{
	self endon( "death" );

	dist = Distance( ent.origin, self.origin );
	
	if ( isdefined( self.activated_entity_distance ) )
	{
		if ( dist < self.activated_entity_distance )
		{
			// this is a closer target
			self notify( "better_target" );
		}
		else
		{
			// the other target was better
			return;
		}
	}
	
	self endon( "better_target" );
	self.activated_entity_distance = dist;
	
	// this allows anything that is in the trigger to have a chance to be the target
	// so we can pick the closest instead of just picking the first numerically
	wait(0.05);
	
	proximityWeaponObject_DoDetonation( watcher, ent, traceOrigin );
}

function proximityWeaponObjectDetonation( watcher )
{
	self endon( "death" );
	self endon( "hacked" );
	self endon( "kill_target_detection" );
	
	proximityWeaponObject_ActivationDelay( watcher );
	
	damagearea = proximityWeaponObject_CreateDamageArea( watcher );
	
	up = AnglesToUp( self.angles );
	traceOrigin = self.origin + up;

	while(1)
	{
		damagearea waittill("trigger", ent);
	
		if ( !proximityWeaponObject_ValidTriggerEntity( watcher, ent ) )
			continue;

		if ( proximityWeaponObject_IsSpawnProtected( watcher, ent ) )
			continue;
			
		if ( ent damageConeTrace( traceOrigin, self ) > 0 )
		{
			thread proximityWeaponObject_WaitTillFrameEndAndDoDetonation( watcher, ent, traceOrigin );
		}
	}
}

function shouldAffectWeaponObject( object, watcher )
{
	radius = object.weapon.explosionRadius;
	distanceSqr = DistanceSquared( self.origin, object.origin );
	
	// this fixes an issue where if the object is above head height
	// the players head can set it off but the radius damage check 
	// looks at the origin of the player
	if ( radius * radius < distanceSqr )
		return false;
	
	pos = self.origin + (0,0,32);
	
	if( isdefined( watcher.ignoreDirection ) )
	{
		return true;
	}

	dirToPos = pos - object.origin;
	objectForward = anglesToForward( object.angles );
	
	dist = vectorDot( dirToPos, objectForward );
	if ( dist < watcher.detectionMinDist )
		return false;
	
	dirToPos = vectornormalize( dirToPos );
	
	dot = vectorDot( dirToPos, objectForward );
	return ( dot > watcher.detectionDot );
}

function deleteOnDeath(ent)
{
	self util::waittill_any("death", "hacked");
		wait .05;
	if ( isdefined(ent) )
		ent delete();
}

function testKillbrushOnStationary( a_killbrushes, player ) // self == weaponobject
{
	player endon( "disconnect" );
	self endon( "death" );

	self waittill( "stationary" );
	
	foreach ( trig in a_killbrushes )
	{
		if ( isdefined( trig ) && self IsTouching( trig ) )
		{
			if ( !trig IsTriggerEnabled() )
			{
				continue;
			}
			
			if ( (isdefined(self.spawnflags)&&((self.spawnflags & 2) == 2)) )
			{
				continue;
			}
			
			if ( self.origin[ 2 ]  > player.origin[ 2 ] )
			{
				break;
			}

			if ( isdefined( self ) )
			{
				self delete();
			}

			return;
		}
	}
}

function deleteOnKillbrush(player)
{
	player endon( "disconnect" );
	self endon( "death" );
	self endon( "stationary" );
			
	a_killbrushes = GetEntArray( "trigger_hurt","classname" );

	self thread testKillbrushOnStationary( a_killbrushes, player );

	while( 1 )
	{
		a_killbrushes = GetEntArray( "trigger_hurt","classname" );

		for ( i = 0; i < a_killbrushes.size; i++)
		{
			if ( self IsTouching( a_killbrushes[ i ] ) )
			{		
				if ( !a_killbrushes[ i ] IsTriggerEnabled() )
				{
					continue;
				}
				
				if ( (isdefined(self.spawnflags)&&((self.spawnflags & 2) == 2)) )
				{
					continue;
				}
				
				if( self.origin[ 2 ] > player.origin[ 2 ] )
				{
					break;
				}

				if ( isdefined( self ) )
				{
					self delete();
				}

				return;
			}
		}
	
		wait 0.1;
	}	
}


function watchWeaponObjectAltDetonation() // self == player
{
	self endon("disconnect");

	while( 1 )
	{
		self waittill( "alt_detonate" );

		if ( !IsAlive( self ) || ( self util::isUsingRemote() ) )
		{
			continue;
		}
		
		for ( watcher = 0; watcher < self.weaponObjectWatcherArray.size; watcher++ )
		{
			if ( self.weaponObjectWatcherArray[watcher].altDetonate )
			{
				self.weaponObjectWatcherArray[watcher] detonateWeaponObjectArray( false );
			}
		}
	}
}


function watchWeaponObjectAltDetonate() // self == player
{
	self endon( "disconnect" );	
	level endon( "game_ended" );
	
	buttonTime = 0;
	for( ;; )
	{
		self waittill( "doubletap_detonate" );
		
		if ( !IsAlive( self ) && !( self util::isUsingRemote() ) )
		{
			continue;
		}
		
		self notify ( "alt_detonate" );
		
		{wait(.05);};
	}
}

function watchWeaponObjectDetonation() // self == player
{
	self endon( "disconnect" );
	
	while( 1 )
	{
		self waittill( "detonate" );

		if ( self isUsingOffhand() )
		{
			weap = self getCurrentOffhand();
		}
		else
		{
			weap = self getCurrentWeapon();
		}

		watcher = getWeaponObjectWatcherByWeapon( weap );
		
		if ( isdefined( watcher ) )
		{
			if ( isdefined( watcher.onDetonationHandle ) )
			{
				self thread [[watcher.onDetonationHandle]]( watcher );
			}
			
			watcher detonateWeaponObjectArray( false );
		}
	}
}

function cleanUpWatchers()
{
	if ( !isdefined(self.weaponObjectWatcherArray) )
	{
		assert( "Can't clean Up watechers" );
		return;
	}
			
	watchers = [];
	
	// make a psudo copy of the watchers out of the player 
	// so that when the player ent gets cleaned we still have
	// the object arrays to clean up
	for ( watcher = 0; watcher < self.weaponObjectWatcherArray.size; watcher++ )
	{
		weaponObjectWatcher = SpawnStruct();
		watchers[watchers.size] = weaponObjectWatcher;
		weaponObjectWatcher.objectArray = [];
		
		if ( isdefined(  self.weaponObjectWatcherArray[watcher].objectArray ) )
		{
			weaponObjectWatcher.objectArray =  self.weaponObjectWatcherArray[watcher].objectArray;
		}
	}
	
	wait .05;
	
	for ( watcher = 0; watcher < watchers.size; watcher++ )
	{
		watchers[watcher] deleteWeaponObjectArray();
	}
}


function watchForDisconnectCleanUp()
{
	self waittill( "disconnect" );
	
	cleanUpWatchers();
}

function deleteWeaponObjectsOn() // self == player
{
	self thread watchForDisconnectCleanUp();

	self endon( "disconnect" );
	
	if( !isPlayer( self ))
	{
		return;
	}
	
	while(1)
	{
		msg = self util::waittill_any_return( "joined_team", "joined_spectators", "death", "disconnect" );	
		
		// only need this because util::waittill_any_return will endon death if we dont pass it in
		if ( msg == "death" )
			continue;

		cleanUpWatchers();
	}
}

function saydamaged(orig, amount)
{
	/#
	for (i = 0; i < 60; i++)
	{
		print3d(orig, "damaged! " + amount);
		wait .05;
	}
	#/
}

function showHeadIcon( trigger )
{
	triggerDetectId = trigger.detectId;
	useId = -1;
	for ( index = 0; index < 4; index++ )
	{
		detectId = self.bombSquadIcons[index].detectId;

		if ( detectId == triggerDetectId )
			return;
			
		if ( detectId == "" )
			useId = index;
	}
	
	if ( useId < 0 )
		return;

	self.bombSquadIds[triggerDetectId] = true;
	
	self.bombSquadIcons[useId].x = trigger.origin[0];
	self.bombSquadIcons[useId].y = trigger.origin[1];
	self.bombSquadIcons[useId].z = trigger.origin[2]+24+128;

	self.bombSquadIcons[useId] fadeOverTime( 0.25 );
	self.bombSquadIcons[useId].alpha = 1;
	self.bombSquadIcons[useId].detectId = trigger.detectId;
	
	while ( isAlive( self ) && isdefined( trigger ) && self isTouching( trigger ) )
		{wait(.05);};
		
	if ( !isdefined( self ) )
		return;
		
	self.bombSquadIcons[useId].detectId = "";
	self.bombSquadIcons[useId] fadeOverTime( 0.25 );
	self.bombSquadIcons[useId].alpha = 0;
	self.bombSquadIds[triggerDetectId] = undefined;
}

// returns true if damage should be done to the item given its owner and the attacker
function friendlyFireCheck( owner, attacker, forcedFriendlyFireRule )
{
	if ( !isdefined(owner) ) // owner has disconnected? allow it
		return true;
	
	if ( !level.teamBased ) // not a team based mode? allow it
		return true;

	friendlyFireRule = [[ level.figure_out_friendly_fire ]]( undefined ); // there is no victim here
	if ( isdefined( forcedFriendlyFireRule ) )
		friendlyFireRule = forcedFriendlyFireRule;
	
	if ( friendlyFireRule != 0 ) // friendly fire is on? allow it
		return true;
	
	if ( attacker == owner ) // owner may attack his own items
		return true;
	
	if ( isplayer( attacker ) )
	{
		if ( !isdefined(attacker.pers["team"])) // attacker not on a team? allow it
			return true;
		
		if ( attacker.pers["team"] != owner.pers["team"] ) // attacker not on the same team as the owner? allow it
			return true;
	}
	else if ( IsActor(attacker) )
	{
		if ( attacker.team != owner.pers["team"] ) // attacker not on the same team as the owner? allow it
			return true; 
	}	
	else if ( isvehicle( attacker ) )
	{
		if ( isdefined(attacker.owner) && IsPlayer(attacker.owner) )
		{
			if ( attacker.owner.pers["team"] != owner.pers["team"] )
				return true;
		}
		else
		{
			occupant_team = attacker vehicle::vehicle_get_occupant_team();
			if ( occupant_team != owner.pers["team"] && occupant_team != "spectator" ) // attacker not on the same team as the owner? allow it
				return true; 
		}
	}	

	return false; // disallow it
}

function onSpawnHatchet( watcher, player )
{
	if ( isdefined( level.playThrowHatchet ) )
	{
		player [[level.playThrowHatchet]]();
	}
}

function onSpawnCrossbowBolt( watcher, player )
{
	self.delete_on_death = true;
	self thread onSpawnCrossbowBolt_internal( watcher, player );
}

function onSpawnCrossbowBolt_internal( watcher, player )
{
	player endon( "disconnect" );
	self endon( "death" );
	
	wait 0.25; // wait before setting takedamage

	linkedEnt = self GetLinkedEnt();
	if ( !isdefined( linkedEnt ) || !IsVehicle( linkedEnt ) )
	{
		self.takedamage = false;
	}
	else
	{
		self.takedamage = true;
		
		if ( IsVehicle( linkedEnt ) )
		{
			self thread dieOnEntityDeath( linkedEnt, player );
		}
	}
}

function dieOnEntityDeath( entity, player )
{
	player endon( "disconnect" );
	self endon( "death" );
	
	alreadyDead = ( ( entity.dead === true ) || ( isdefined( entity.health ) && entity.health < 0 ) );
	
	if ( !alreadyDead )
	{
		entity waittill( "death" );
	}

	self notify( "death" );
}

function onSpawnCrossbowBoltImpact( s_watcher, e_player )
{
	self.delete_on_death = true;
	self thread onSpawnCrossbowBoltImpact_internal( s_watcher, e_player );
}

// PORTIZ 7/5/16: crossbow bolt will be deleted when it impacts
function onSpawnCrossbowBoltImpact_internal( s_watcher, e_player )
{
	self endon( "death" );
	e_player endon( "disconnect" );
	
	self waittill( "stationary" );
	
	s_watcher thread waitAndFizzleOut( self, 0 ); // use fizzle out to make use of existing FX and logic
	
	foreach ( n_index, e_object in s_watcher.objectarray )
	{
		if ( self == e_object )
		{
			s_watcher.objectarray[ n_index ] = undefined;
		}
	}
	
	cleanWeaponObjectArray( s_watcher );
}

function onSpawnSpecialCrossbowTrigger( watcher, player ) // self == weapon_instance (for example: the claymore)
{
	self endon( "death" );

	self SetOwner( player );
	self SetTeam( player.pers["team"] );
	self.owner = player;
	self.oldAngles = self.angles;

	self util::waitTillNotMoving();
	waittillframeend;

	if( player.pers["team"] == "spectator" )
	{
		return;
	}
	
	triggerOrigin = self.origin;
	triggerParentEnt = undefined;

	if ( isdefined( self.stuckToPlayer ) )
	{
		if ( IsAlive( self.stuckToPlayer ) || !isdefined( self.stuckToPlayer.body ) )
		{
			if ( IsAlive( self.stuckToPlayer ) )
			{
				// Drop the arrow to the ground if it doesn't kill the player.
				triggerParentEnt = self;
				self Unlink();
				self.angles = self.oldAngles;
				self launch( (5,5,5) );
				self util::waitTillNotMoving();
				waittillframeend;
			}
			else
			{
				triggerParentEnt = self.stuckToPlayer;
			}
		}
		else
		{
			triggerParentEnt = self.stuckToPlayer.body;
		}
	}
	
	if ( isdefined( triggerParentEnt ) )
		triggerOrigin = triggerParentEnt.origin + ( 0, 0, 10 );

	if ( self.weapon.ShownRetrievable )
	{
		self clientfield::set( "retrievable", 1 );
	}
	
	self.hatchetPickUpTrigger = spawn( "trigger_radius", triggerOrigin, 0, 50, 50 );

	self.hatchetPickUpTrigger EnableLinkTo();
	self.hatchetPickUpTrigger LinkTo( self );

	if ( isdefined( triggerParentEnt ) )
	{
		self.hatchetPickUpTrigger linkto( triggerParentEnt );
	}

	self thread watchSpecialCrossbowTrigger( self.hatchetPickUpTrigger, watcher.pickUp, watcher.pickUpSoundPlayer, watcher.pickUpSound );

	/#
	thread switch_team( self, watcher, player );
	#/

	self thread watchShutdown( player );
}

function watchSpecialCrossbowTrigger( trigger, callback, playerSoundOnUse, npcSoundOnUse ) // self == weapon (for example: the claymore)
{
	self endon( "delete" );
	self endon( "hacked" );

	while ( true )
	{
		trigger waittill( "trigger", player );
			
		if ( !isAlive( player ) )
			continue;
			
		if ( isdefined( trigger.claimedBy ) && ( player != trigger.claimedBy ) )
			continue;
		
		crossbow_weapon = player get_player_crossbow_weapon();
		if ( !isdefined( crossbow_weapon ) )
			continue;
		
		stock_ammo = player GetWeaponAmmoStock( crossbow_weapon );
		if( stock_ammo >= crossbow_weapon.maxammo )
			continue;

		if ( isdefined( playerSoundOnUse ) )
			player playLocalSound( playerSoundOnUse );
		if ( isdefined( npcSoundOnUse ) )
			player playSound( npcSoundOnUse );

		self thread [[callback]]( player, crossbow_weapon );
	}
}

function onSpawnHatchetTrigger( watcher, player ) // self == weapon_instance (for example: the claymore)
{
	self endon( "death" );

	self SetOwner( player );
	self SetTeam( player.pers["team"] );
	self.owner = player;
	self.oldAngles = self.angles;

	self util::waitTillNotMoving();
	waittillframeend;

	if( player.pers["team"] == "spectator" )
	{
		return;
	}
	
	triggerOrigin = self.origin;
	triggerParentEnt = undefined;

	if ( isdefined( self.stuckToPlayer ) )
	{
		if ( IsAlive( self.stuckToPlayer ) || !isdefined( self.stuckToPlayer.body ) )
		{
			if ( IsAlive( self.stuckToPlayer ) )
			{
				// Drop the hatchet to the ground if it doesn't kill the player.
				triggerParentEnt = self;
				self Unlink();
				self.angles = self.oldAngles;
				self launch( (5,5,5) );
				self util::waitTillNotMoving();
				waittillframeend;
			}
			else
			{
				triggerParentEnt = self.stuckToPlayer;
			}
		}
		else
		{
			triggerParentEnt = self.stuckToPlayer.body;
		}
	}
	
	if ( isdefined( triggerParentEnt ) )
		triggerOrigin = triggerParentEnt.origin + ( 0, 0, 10 );

	if ( self.weapon.ShownRetrievable )
	{
		self clientfield::set( "retrievable", 1 );
	}
	
	self.hatchetPickUpTrigger = spawn( "trigger_radius", triggerOrigin, 0, 50, 50 );

	self.hatchetPickUpTrigger EnableLinkTo();
	self.hatchetPickUpTrigger LinkTo( self );

	if ( isdefined( triggerParentEnt ) )
	{
		self.hatchetPickUpTrigger linkto( triggerParentEnt );
	}

	self thread watchHatchetTrigger( self.hatchetPickUpTrigger, watcher.pickUp, watcher.pickUpSoundPlayer, watcher.pickUpSound );

	/#
	thread switch_team( self, watcher, player );
	#/

	self thread watchShutdown( player );
}

function watchHatchetTrigger( trigger, callback, playerSoundOnUse, npcSoundOnUse ) // self == weapon (for example: the claymore)
{
	self endon( "delete" );
	self endon( "hacked" );

	while ( true )
	{
		trigger waittill( "trigger", player );
			
		if ( !isAlive( player ) )
			continue;
			
		if ( !player isOnGround() && !player IsPlayerSwimming() )
			continue;
			
		if ( isdefined( trigger.claimedBy ) && ( player != trigger.claimedBy ) )
			continue;

		heldWeapon = player get_held_weapon_match_or_root_match( self.weapon ); 
		if ( !IsDefined( heldWeapon ) )
		{
			continue;
		}

		maxAmmo = 0;
		
		if ( heldWeapon == player.grenadeTypePrimary && isdefined( player.grenadeTypePrimaryCount ) && player.grenadeTypePrimaryCount > 0 )
		{
			maxAmmo = player.grenadeTypePrimaryCount;
		}
		else if ( heldWeapon == player.grenadeTypeSecondary && isdefined( player.grenadeTypeSecondaryCount ) && player.grenadeTypeSecondaryCount > 0 )
		{
			maxAmmo = player.grenadeTypeSecondaryCount;
		}
		
		if ( maxAmmo == 0 )
		{
			continue;
		}
		
		clip_ammo = player GetWeaponAmmoClip( heldWeapon );

		if( clip_ammo >= maxAmmo )
		{
			continue;
		}

		if ( isdefined( playerSoundOnUse ) )
			player playLocalSound( playerSoundOnUse );
		if ( isdefined( npcSoundOnUse ) )
			player playSound( npcSoundOnUse );

		self thread [[callback]]( player );
	}
}

function get_held_weapon_match_or_root_match( weapon )
{
	pweapons = self GetWeaponsList( true ); 
	
	foreach( pweapon in pweapons )
	{
		if ( pweapon == weapon )
		{
			return pweapon; 
		}
	}
	
	foreach( pweapon in pweapons )
	{
		if ( pweapon.rootweapon == weapon.rootweapon )
		{
			return pweapon; 
		}
	}
	
	return undefined; 
}

function get_player_crossbow_weapon()
{
	pweapons = self GetWeaponsList( true ); 

	crossbow = GetWeapon( "special_crossbow" );
	crossbow_dw = GetWeapon( "special_crossbow_dw" );	
	// crossbowlh = GetWeapon( "special_crossbowlh" ); // doesn't seem to be needed
	
	foreach( pweapon in pweapons )
	{
		if ( pweapon.rootweapon == crossbow || pweapon.rootweapon == crossbow_dw )
		{
			return pweapon; 
		}
	}
	
	return undefined; 	
}

function onSpawnRetrievableWeaponObject( watcher, player ) // self == weapon (for example: the claymore)
{
	self endon( "death" );
	self endon( "hacked" );

	self SetOwner( player );
	self SetTeam( player.pers["team"] );
	self.owner = player;
	self.oldAngles = self.angles;

	self util::waitTillNotMoving();

	if ( watcher.activationDelay )
	{
		wait( watcher.activationDelay );
	}

	waittillframeend;

	if(player.pers["team"] == "spectator")
	{
		return;
	}
	
	triggerOrigin = self.origin;
	triggerParentEnt = undefined;
	if ( isdefined( self.stuckToPlayer ) )
	{
		if ( IsAlive( self.stuckToPlayer ) || !isdefined( self.stuckToPlayer.body ) )
		{
			triggerParentEnt = self.stuckToPlayer;
		}
		else
		{
			triggerParentEnt = self.stuckToPlayer.body;
		}
	}
	
	if ( isdefined( triggerParentEnt ) )
	{
		triggerOrigin = triggerParentEnt.origin + ( 0, 0, 10 );
	}
	else
	{
		// move the trigger 1 unit along the up axis to prevent the it
		// from failing the sight trace in player_use
		up = AnglesToUp( self.angles );
		triggerOrigin = self.origin + up;
	}
	
	if ( !self util::isHacked() )
	{
		if ( self.weapon.ShownRetrievable )
		{
			self clientfield::set( "retrievable", 1 );
		}
	
		self.pickUpTrigger = spawn( "trigger_radius_use", triggerOrigin );
		self.pickUpTrigger SetHintLowPriority( true );
		self.pickUpTrigger SetCursorHint( "HINT_NOICON", self );
		self.pickUpTrigger EnableLinkTo();
		self.pickUpTrigger LinkTo( self );
		self.pickUpTrigger SetInvisibleToAll();
		self.pickUpTrigger SetVisibleToPlayer( player );
		
		if( isdefined(level.retrieveHints[watcher.name]) )
		{
			self.pickUpTrigger SetHintString( level.retrieveHints[watcher.name].hint );
		}
		else
		{
			self.pickUpTrigger SetHintString( &"MP_GENERIC_PICKUP" );
		}
	
		self.pickUpTrigger SetTeamForTrigger( player.pers["team"] );
		
		if ( isdefined( triggerParentEnt ) )
		{
			self.pickUpTrigger linkto( triggerParentEnt );
		}
		
		self thread watchUseTrigger( self.pickUpTrigger, watcher.pickUp, watcher.pickUpSoundPlayer, watcher.pickUpSound );
		
		if ( isdefined( watcher.pickup_trigger_listener ) )
		{
			self thread [[watcher.pickup_trigger_listener]]( self.pickUpTrigger, player );
		}
	}	

	if ( watcher.enemyDestroy )
	{
		self.enemyTrigger = spawn( "trigger_radius_use", triggerOrigin );
		self.enemyTrigger SetCursorHint( "HINT_NOICON", self );
		self.enemyTrigger EnableLinkTo();
		self.enemyTrigger LinkTo( self );
		self.enemyTrigger SetInvisibleToPlayer( player );

		if ( level.teamBased )
		{
			self.enemyTrigger SetExcludeTeamForTrigger( player.team );
			self.enemyTrigger.triggerTeamIgnore = self.team;
		}

		if( isdefined(level.destroyHints[watcher.name]) )
		{
			self.enemyTrigger SetHintString( level.destroyHints[watcher.name].hint );
		}
		else
		{
			self.enemyTrigger SetHintString( &"MP_GENERIC_DESTROY" );
		}

		self thread watchUseTrigger( self.enemyTrigger, watcher.onDestroyed );
	}	

	/#
	thread switch_team( self, watcher, player );
	#/	

	self thread watchShutdown( player );
}

function destroyEnt()
{
	self delete();
}

function pickUp( player ) // self == weapon_instance (for example: the claymore)
{
	if ( !self.weapon.anyPlayerCanRetrieve && isdefined( self.owner ) && self.owner != player )
	{
		return;
	}	
	
	pikedWeapon = self.weapon;
	
	if ( self.weapon.ammoCountEquipment > 0 && isdefined( self.ammo ) )
	{
		ammoLeftEquipment = self.ammo;
	}
	
	self notify( "picked_up" );
	self.playDialog = false;
	self destroyEnt();	
	
	heldWeapon = player get_held_weapon_match_or_root_match( self.weapon ); 	
	if ( !IsDefined( heldWeapon ) )
	{
		return;
	}	
	
	maxAmmo = 0;
		
	if ( heldWeapon == player.grenadeTypePrimary && isdefined( player.grenadeTypePrimaryCount ) && player.grenadeTypePrimaryCount > 0 )
	{
		maxAmmo = player.grenadeTypePrimaryCount;
	}
	else if ( heldWeapon == player.grenadeTypeSecondary && isdefined( player.grenadeTypeSecondaryCount ) && player.grenadeTypeSecondaryCount > 0 )
	{
		maxAmmo = player.grenadeTypeSecondaryCount;
	}
	
	if ( maxAmmo == 0 )
	{
		return;
	}
	
	clip_ammo = player GetWeaponAmmoClip( heldWeapon );

	if( clip_ammo < maxAmmo )
	{
		clip_ammo++;
	}	
	
	if ( isdefined ( ammoLeftEquipment ) )
	{
		if ( pikedWeapon.rootWeapon == GetWeapon( "trophy_system" ) )
		{
			player trophy_system::ammo_weapon_pickup( ammoLeftEquipment );
		}
	}	
	
	player setWeaponAmmoClip( heldWeapon, clip_ammo );
}

function pickUpCrossbowBolt( player, heldWeapon ) // self == weapon_instance (for example: the crossbow bolt)
{
	self notify( "picked_up" );
	self.playDialog = false;
	self destroyEnt();	

	// note: ammo limits should be regulated in watchSpecialCrossbowTrigger
	stock_ammo = player GetWeaponAmmoStock( heldWeapon );
	stock_ammo++;
	player SetWeaponAmmoStock( heldWeapon, stock_ammo );
}

function onDestroyed( attacker ) // self == weapon object
{
	PlayFX( level._effect["tacticalInsertionFizzle"], self.origin );
	self playsound ("dst_tac_insert_break");

	if( isdefined( level.playEquipmentDestroyedOnPlayer ) )
	{
		self.owner [[level.playEquipmentDestroyedOnPlayer]]();
	}	
	
	self delete(); // will call watchShutdow() to clean up the trigger ents
}

function watchShutdown( player ) // self == weapon (for example: the claymore)
{
	self util::waittill_any( "death", "hacked", "detonating" );

	pickUpTrigger = self.pickUpTrigger;
	hackerTrigger = self.hackerTrigger;
	hatchetPickUpTrigger = self.hatchetPickUpTrigger;
	enemyTrigger = self.enemyTrigger;
	
	if( isdefined( pickUpTrigger ) )
		pickUpTrigger delete();

	if( isdefined( hackerTrigger ) )
	{
		if ( isdefined( hackerTrigger.progressBar ) )
		{
			hackerTrigger.progressBar hud::destroyElem();
			hackerTrigger.progressText hud::destroyElem();
		}
		hackerTrigger delete();
	}

	if( isdefined( hatchetPickUpTrigger ) )
		hatchetPickUpTrigger delete();

	if ( isdefined( enemyTrigger ))
	{
		enemyTrigger delete();
	}
}

function watchUseTrigger( trigger, callback, playerSoundOnUse, npcSoundOnUse ) // self == weapon (for example: the claymore)
{
	self endon( "delete" );
	self endon( "hacked" );
	
	while ( true )
	{
		trigger waittill( "trigger", player );
			
		if ( isdefined( self.detonated ) && self.detonated == true )
		{
			if ( isdefined( trigger ) )
			{
				trigger delete();
			}
			return;
		}
		
		if ( !isAlive( player ) )
			continue;		
				
		if ( isdefined( trigger.triggerTeam ) && ( player.pers["team"] != trigger.triggerTeam ) )
			continue;
			
		if ( isdefined( trigger.triggerTeamIgnore ) && ( player.team == trigger.triggerTeamIgnore ) )
			continue;

		if ( isdefined( trigger.claimedBy ) && ( player != trigger.claimedBy ) )
			continue;

		grenade = player.throwingGrenade;
		weapon = player GetCurrentWeapon();

		if ( weapon.isEquipment )
		{
			grenade = false;
		}
			
		if ( player useButtonPressed() && !grenade && !player meleeButtonPressed() )
		{
			if ( isdefined( playerSoundOnUse ) )
				player playLocalSound( playerSoundOnUse );
			if ( isdefined( npcSoundOnUse ) )
				player playSound( npcSoundOnUse );
			self thread [[callback]]( player );
		}
	}
}

function createRetrievableHint(name, hint)
{
	retrieveHint = spawnStruct();

	retrieveHint.name = name;
	retrieveHint.hint = hint;

	level.retrieveHints[name] = retrieveHint;
}

function createHackerHint(name, hint)
{
	hackerHint = spawnStruct();

	hackerHint.name = name;
	hackerHint.hint = hint;

	level.hackerHints[name] = hackerHint;
}

function createDestroyHint(name, hint)
{
	destroyHint = spawnStruct();

	destroyHint.name = name;
	destroyHint.hint = hint;

	level.destroyHints[name] = destroyHint;
}

function setupReconEffect()
{
	if ( !isdefined( self ) )
		return;
		
	if ( self.weapon.ShownEnemyExplo || self.weapon.ShownEnemyEquip )
	{
		if( ( isdefined( self.hacked ) && self.hacked ) )
		{
			self clientfield::set( "enemyequip", 2 );
		}
		else
		{
			self clientfield::set( "enemyequip", 1 );
		}
	}
}

function useTeamEquipmentClientField( watcher )
{
	if ( isdefined( watcher ) )
	{
		if ( !isdefined( watcher.notEquipment ) )
		{
			if ( isdefined( self ) )
			{
				//if ( self.type == "missile" )
				{
					return true;
				}
			}
		}
	}	
	
	return false;
}

function GetWatcherForWeapon( weapon )
{
	if ( !isdefined( self ) )
	{
		return undefined;
	}

	if ( !IsPlayer( self ) )
	{
		return undefined;
	}

	for ( i = 0; i < self.weaponObjectWatcherArray.size; i++ )
	{
		if ( self.weaponObjectWatcherArray[i].weapon != weapon )
		{ 
			continue;
		}

		return ( self.weaponObjectWatcherArray[i] );
	}

	return undefined;
}

// secondary items which need to be cleaned up during an emp
// most primary watcher items are in the level.missileEntities list
function destroy_other_teams_supplemental_watcher_objects( attacker, weapon )
{
	if( level.teamBased )
	{
		foreach( team in level.teams )
		{
			if( team == attacker.team )
			{
				continue;
			}
				
			destroy_supplemental_watcher_objects( attacker, team, weapon );
		}
	}
	
	destroy_supplemental_watcher_objects( attacker, "free", weapon );
}

function destroy_supplemental_watcher_objects( attacker, team, weapon )
{
	foreach ( item in level.supplementalWatcherObjects )
	{
		if ( !isdefined( item.weapon ) )
		{
			continue;
		}

		if ( !isdefined( item.owner ) )
		{
			continue;
		}

		if ( isdefined( team ) && item.owner.team != team ) 
		{
			continue;
		}
		else if ( item.owner == attacker )
		{
			continue;
		}

		watcher = item.owner getWatcherForWeapon( item.weapon );

		if ( !isdefined( watcher ) || !IsDefined( watcher.onSupplementalDetonateCallback ) )
		{
			continue;
		}

		item thread [[watcher.onSupplementalDetonateCallback]]();
	}
}

function add_supplemental_object( object )
{
	level.supplementalWatcherObjects[ level.supplementalWatcherObjects.size ] = object;
	object thread watch_supplemental_object_death();
}

function watch_supplemental_object_death() // self == object
{
	self waittill( "death" );
	ArrayRemoveValue( level.supplementalWatcherObjects, self ); 
}

/#
function switch_team( entity, watcher, owner ) // self == ??
{
	self notify( "stop_disarmthink" );
	self endon( "stop_disarmthink" );
	self endon( "death" );

	//Init my dvar
	SetDvar("scr_switch_team", "");

	while( true )
	{
		wait(0.5);

		//Grab my dvar every .5 seconds in the form of an int
		devgui_int = GetDvarint( "scr_switch_team");

		//"" returns as zero with GetDvarInt
		if(devgui_int != 0)
		{
			// spawn a larry to be the opposing team
			team = "autoassign";
			
			if( isdefined( level.getEnemyTeam ) && isdefined( owner ) && isdefined( owner.team ) )
			{
				team = [[level.getEnemyTeam]]( owner.team );
			}

			if ( isdefined( level.devOnGetOrMakeBot ) )
			{
				player = [[level.devOnGetOrMakeBot]]( team );
			}
			
			if( !isdefined( player ) ) 
			{
				println("Could not add test client");
				wait 1;
				continue;
			}

			entity ItemHacked( watcher, player );

			SetDvar("scr_switch_team", "0");
		}
	}
}

#/