#using scripts\codescripts\struct;

#using scripts\shared\array_shared;
#using scripts\shared\bb_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\gameobjects_shared;
#using scripts\shared\killstreaks_shared;
#using scripts\shared\loadout_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons_shared;
#using scripts\shared\weapons\_hive_gun;
#using scripts\shared\weapons\_empgrenade;
#using scripts\shared\weapons\_flashgrenades;
#using scripts\shared\weapons\_hacker_tool;
#using scripts\shared\weapons\_proximity_grenade;
#using scripts\shared\weapons\_sticky_grenade;
#using scripts\shared\weapons\_riotshield;
#using scripts\shared\weapons\_tabun;
#using scripts\shared\weapons\_trophy_system;
#using scripts\shared\weapons\_weapon_utils;
#using scripts\shared\weapons\_weaponobjects;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          

                                                                                                                                                                                       	   	                                                                      	  	  	

#precache( "material", "hud_scavenger_pickup" );

#namespace weapons;
	
function init_shared()
{
	level.weaponNone = GetWeapon( "none" );
	level.weaponNull = GetWeapon( "weapon_null" );
	level.weaponBaseMelee = GetWeapon( "knife" );
	level.weaponBaseMeleeHeld = GetWeapon( "knife_held" );
	level.weaponBallisticKnife = GetWeapon( "knife_ballistic" );
	level.weaponRiotshield = GetWeapon( "riotshield" );
	level.weaponFlashGrenade = GetWeapon( "flash_grenade" );
	level.weaponSatchelCharge = GetWeapon( "satchel_charge" );
	
	if ( !IsDefined(level.trackWeaponStats) )
	{
		level.trackWeaponStats = true;
	}
	
	level._effect["flashNineBang"] = "_t6/misc/fx_equip_tac_insert_exp";

	callback::on_start_gametype( &init );
}

function init()
{
	// assigns weapons with stat numbers from 0-99
	// attachments are now shown here, they are per weapon settings instead
	

	//TODO - add to statstable so its a regular weapon?
	
	level.missileEntities = [];
	level.hackerToolTargets = [];

	level.missileDudDeleteDelay = GetDvarInt( "scr_missileDudDeleteDelay", 3 ); //Seconds

	if ( !isdefined(level.roundStartExplosiveDelay) )
		level.roundStartExplosiveDelay = 0;

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

function on_player_connect()
{
	self.usedWeapons = false;
	self.lastFireTime = 0;
	self.hits = 0;
	self scavenger_hud_create();
}

function on_player_spawned()
{
	self.concussionEndTime = 0;
	self.scavenged = false;
	self.hasDoneCombat = false;
	self.shieldDamageBlocked = 0;
	self thread watch_usage();
	self thread watch_grenade_usage();
	self thread watch_missile_usage();
	self thread watch_weapon_change();

	if ( level.trackWeaponStats )
	{
		self thread track();
	}

	self.droppedDeathWeapon = undefined;
	self.tookWeaponFrom = [];
	self.pickedUpWeaponKills = [];
	
	self thread update_stowed_weapon();
}

function watch_weapon_change()
{
	self endon("death");
	self endon("disconnect");
	
	self.lastDroppableWeapon = self GetCurrentWeapon();

	self.lastWeaponChange = 0;
	while(1)
	{
		previous_weapon = self GetCurrentWeapon();
		self waittill( "weapon_change", newWeapon );
		
		if ( may_drop( newWeapon ) )
		{
			self.lastDroppableWeapon = newWeapon;
			self.lastWeaponChange = getTime();
		}

		if ( DoesWeaponReplaceSpawnWeapon( self.spawnWeapon, newWeapon ) )
		{
			self.spawnWeapon = newWeapon;
			self.pers["spawnWeapon"] = newWeapon;
		}
	}
}


function update_last_held_weapon_timings( newTime )
{
	if ( isdefined( self.currentWeapon ) && isdefined( self.currentWeaponStartTime ) )
	{
		totalTime = int ( ( newTime - self.currentWeaponStartTime ) / 1000 );
		if ( totalTime > 0 )
		{
			weaponPickedUp = false;
			if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[self.currentWeapon] ) )
			{
				weaponPickedUp = true;
			}

			if ( isdefined( self.class_num ) )
			{
				self AddWeaponStat( self.currentWeapon, "timeUsed", totalTime, self.class_num, weaponPickedUp );
				self.currentWeaponStartTime = newTime;
			}
		}
	}
}

function update_timings( newTime )
{
	if ( self util::is_bot() )
	{
		return;
	}

	update_last_held_weapon_timings( newTime );
	
	if ( !isdefined( self.staticWeaponsStartTime ) )
	{
		return;
	}
	
	totalTime = int ( ( newTime - self.staticWeaponsStartTime ) / 1000 );
	
	if ( totalTime < 0 )
	{
		return;
	}
	
	self.staticWeaponsStartTime = newTime;
	
		
	// Record grenades and equipment 
	if( isdefined( self.weapon_array_grenade ) )
	{
		for( i=0; i<self.weapon_array_grenade.size; i++ )
		{
			self AddWeaponStat( self.weapon_array_grenade[i], "timeUsed", totalTime, self.class_num );	
		}
	}
	if( isdefined( self.weapon_array_inventory ) )
	{
		for( i=0; i<self.weapon_array_inventory.size; i++ )
		{
			self AddWeaponStat( self.weapon_array_inventory[i], "timeUsed", totalTime, self.class_num );
		}
	}

	// Record killstreaks
	if( isdefined( self.killstreak ) )
	{
		for( i=0; i<self.killstreak.size; i++ )
		{
			killstreakType = level.menuReferenceForKillStreak[ self.killstreak[i] ];

			if ( isdefined( killstreakType ) )
			{
				killstreakWeapon = killstreaks::get_killstreak_weapon( killstreakType );
				self AddWeaponStat( killstreakWeapon, "timeUsed", totalTime, self.class_num );
			}
		}
	}

	// Record all of the equipped perks
	if ( level.rankedmatch && level.perksEnabled  )
	{
		perksIndexArray = [];

		specialtys = self.specialty;
		
		if ( !isdefined( specialtys ) )
		{
			return;
		}

		if ( !isdefined( self.curClass ) )
		{
			return;
		}
		
		if ( isdefined( self.class_num ) )
		{
			for ( numSpecialties = 0; numSpecialties < level.maxSpecialties; numSpecialties++ )
			{
				perk = self GetLoadoutItem( self.class_num, "specialty" + ( numSpecialties + 1 ) );
				if ( perk != 0 )
				{
					perksIndexArray[ perk ] = true;
				}
			}
			
			perkIndexArrayKeys = getarraykeys( perksIndexArray );
			for ( i = 0; i < perkIndexArrayKeys.size; i ++ )
			{
				if ( perksIndexArray[ perkIndexArrayKeys[i] ] == true )
				{
					self AddDStat( "itemStats", perkIndexArrayKeys[i], "stats", "timeUsed", "statValue", totalTime );
				}
			}
		}
	}
}

function track()
{
	currentWeapon = self getCurrentWeapon();
	currentTime = getTime();
	spawnid = getplayerspawnid( self );

	while(1)
	{
		event = self util::waittill_any_return( "weapon_change", "death", "disconnect" );
		newTime = getTime();

		if( event == "weapon_change" )
		{
			self bb::commit_weapon_data( spawnid, currentWeapon, currentTime );

			newWeapon = self getCurrentWeapon();
			if( newWeapon != level.weaponNone && newWeapon != currentWeapon )
			{
				update_last_held_weapon_timings( newTime );
				self loadout::initWeaponAttachments( newWeapon );
				
				currentWeapon = newWeapon;
				currentTime = newTime;
			}
		}
		else
		{
			if( event != "disconnect" && isdefined( self ) )
			{
				self bb::commit_weapon_data( spawnid, currentWeapon, currentTime );
				update_timings( newTime );
			}
			
			return;
		}
	}
}

function may_drop( weapon )
{
	if ( level.disableWeaponDrop == 1 )
		return false;
		
	if ( weapon == level.weaponNone )
		return false;
		
	if ( killstreaks::is_killstreak_weapon( weapon ) )
		return false;
	
	if ( weapon.isGameplayWeapon )
		return false;
	
	if ( !weapon.isPrimary )
		return false;
	
	if ( IsDefined( level.mayDropWeapon ) && ![[level.mayDropWeapon]]( weapon ) )
		return false;
	
	return true;
}

function drop_for_death( attacker, sWeapon, sMeansOfDeath )
{
	if ( level.disableWeaponDrop == 1 )
		return;
	
	weapon = self.lastDroppableWeapon;
	
	if ( isdefined( self.droppedDeathWeapon ) )
		return;
	
	if ( !isdefined( weapon ) )
	{
		/#
		if ( GetDvarString( "scr_dropdebug") == "1" )
			println( "didn't drop weapon: not defined" );
		#/
		return;
	}

	if ( weapon == level.weaponNone )
	{
		/#
		if ( GetDvarString( "scr_dropdebug") == "1" )
			println( "didn't drop weapon: weapon == none" );
		#/
		return;
	}
	
	if ( !self hasWeapon( weapon ) )
	{
		/#
		if ( GetDvarString( "scr_dropdebug") == "1" )
			println( "didn't drop weapon: don't have it anymore (" + weapon.name + ")" );
		#/
		return;
	}

	if ( !(self AnyAmmoForWeaponModes( weapon )) )
	{
		/#
		if ( GetDvarString( "scr_dropdebug") == "1" )
			println( "didn't drop weapon: no ammo for weapon modes" );
		#/
		return;
	}
	
	if ( !should_drop_limited_weapon( weapon, self ) )
	{
		return;
	}

	if( weapon.isCarriedKillstreak )
		return;

	clipAmmo = self GetWeaponAmmoClip( weapon );
	stockAmmo = self GetWeaponAmmoStock( weapon );
	clip_and_stock_ammo = clipAmmo + stockAmmo;

	//Check if the weapon has ammo
	if( !clip_and_stock_ammo && !( isdefined( weapon.unlimitedammo ) && weapon.unlimitedammo ) )
	{
		/#
		if ( GetDvarString( "scr_dropdebug") == "1" )
			println( "didn't drop weapon: no ammo" );
		#/
		return;
	}
	
	if( ( isdefined( weapon.isnotdroppable ) && weapon.isnotdroppable ) )
	{
		return;
	}

	stockMax = weapon.maxAmmo;
	if ( stockAmmo > stockMax )
		stockAmmo = stockMax;

	item = self dropItem( weapon );
	
	if ( !isdefined( item ) )
	{
		/# iprintlnbold( "dropItem: was not able to drop weapon " + weapon.name ); #/
		return;
	}
	
	/#
	if ( GetDvarString( "scr_dropdebug") == "1" )
		println( "dropped weapon: " + weapon.name );
	#/

	drop_limited_weapon( weapon, self, item );
	
	self.droppedDeathWeapon = true;

	item ItemWeaponSetAmmo( clipAmmo, stockAmmo );
	
	if ( isdefined( level.gameModeWeaponDropped ) )
		self [[ level.gameModeWeaponDropped ]]( item );

	item.owner = self;
	item.ownersattacker = attacker;
	item.sWeapon = sWeapon;
	item.sMeansOfDeath = sMeansOfDeath;
	
	item thread watch_pickup();
	
	item thread delete_pickup_after_aWhile();
}

function delete_pickup_after_aWhile()
{
	self endon("death");
	
	wait 60;

	if ( !isdefined( self ) )
		return;

	self delete();
}

function watch_pickup()
{
	self endon("death");
	
	weapon = self.item;

	self waittill( "trigger", player, droppedItem, pickedUpOnTouch );

	// As far as the mp_match_record weapon stats are concerned, any weapon you pick up
	// is a picked-up weapon, even if you were the previous owner.  This way we can still
	// map the weapons of your *current* loadout directly to loadoutWeaponStats slots.

	// Also, in contrast to the player.tookWeaponFrom array, you don't have to be the one who 
	// killed the previous owner.

	if( 1 ) // isdefined(self.owner) && self.owner != player ) <-- intentionally not checking this
	{				
		if( isdefined( player ) && isPlayer( player ) )
		{
			if( isdefined( player.weaponPickupsCount ) )
			{
				player.weaponPickupsCount++;
			}
			else
			{
				player.weaponPickupsCount = 1;
			}

			player incrementSpecificWeaponPickedUpCount( weapon );

			if( !isdefined( player.pickedUpWeapons) )
			{
				player.pickedUpWeapons = [];	// this array will be reset on each death
			}

			player.pickedUpWeapons[weapon] = 1;		// value not important, just that the entry exists

		}
	}
	/#
	if ( GetDvarString( "scr_dropdebug") == "1" )
		println( "picked up weapon: " + weapon.name + ", " + isdefined( self.ownersattacker ) );
	#/
	
	assert( isdefined( player.tookWeaponFrom ) );
	assert( isdefined( player.pickedUpWeaponKills ) );
	
	if ( isdefined( droppedItem ) )
	{
		for ( i = 0; i < droppedItem.size; i++ )
		{
			if ( !IsDefined( droppedItem[i] ) )
			{
				continue;
			}

			// make sure the owner information on the dropped item is preserved
			droppedWeapon = droppedItem[i].item;
			if ( isdefined( player.tookWeaponFrom[ droppedWeapon ] ) )
			{
				droppedItem[i].owner = player.tookWeaponFrom[ droppedWeapon ];
				droppedItem[i].ownersattacker = player;
				player.tookWeaponFrom[ droppedWeapon ] = undefined;
			}
			droppedItem[i] thread watch_pickup();
		}
	}
	
	// take owner information from self and put it onto player
	if ( !isdefined( pickedUpOnTouch ) || !pickedUpOnTouch )
	{
		if ( isdefined( self.ownersattacker ) && self.ownersattacker == player )
		{
			player.tookWeaponFrom[ weapon ] = SpawnStruct();
			
			player.tookWeaponFrom[ weapon ].previousOwner = self.owner;
			player.tookWeaponFrom[ weapon ].sWeapon = self.sWeapon;
			player.tookWeaponFrom[ weapon ].sMeansOfDeath = self.sMeansOfDeath;
		
			player.pickedUpWeaponKills[ weapon ] = 0;
		}
		else
		{
			player.tookWeaponFrom[ weapon ] = undefined;
			player.pickedUpWeaponKills[ weapon ] = undefined;
		}
	}
}

function watch_usage()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon ( "game_ended" );
	
	for ( ;; )
	{	
		self waittill ( "weapon_fired", curWeapon );
		self.lastFireTime = GetTime();

		self.hasDoneCombat = true;
		
		switch ( curWeapon.weapClass )
		{
			case "rifle":
			case "pistol":
			case "pistol spread":
			case "mg":
			case "smg":
			case "spread":
				self track_fire( curWeapon );
				level.globalShotsFired++;
				break;
			case "rocketlauncher":
			case "grenade":
				self AddWeaponStat( curWeapon, "shots", 1, self.class_num, false );
				break;
			default:
				break;
		}

		if( isDefined(curWeapon.gadget_type) && curWeapon.gadget_type == 14 )
		{
			if( isDefined(self.heroweaponShots) )
			{
				self.heroweaponShots++;
			}
		}

		if( curWeapon.isCarriedKillstreak )
		{
			if ( IsDefined( self.pers["held_killstreak_ammo_count"][curWeapon] ) )
			{
				self.pers["held_killstreak_ammo_count"][curWeapon]--;
			}
		}
	}
}



function track_fire( curWeapon )
{
	if ( ( isdefined( level.disableWeaponAccuracyTracking ) && level.disableWeaponAccuracyTracking ) )
	{
		return;
	}
	
	pixbeginevent("trackWeaponFire");

	weaponPickedUp = false;
	if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[curWeapon] ) )
	{
		weaponPickedUp = true;
	}

	self TrackWeaponFireNative( curWeapon, 1, self.hits, 1, self.class_num, weaponPickedUp, self.primaryLoadoutGunSmithVariantIndex, self.secondaryLoadoutGunSmithVariantIndex );
	// old:
	//self AddWeaponStat( curWeapon, "shots", TRACK_WEAPON_SHOT_FIRED );
	//self AddWeaponStat( curWeapon, "hits", self.hits );
	
	//self AddPlayerStat( "total_shots", TRACK_WEAPON_SHOT_FIRED );		
	//self AddPlayerStat( "hits", self.hits );
	//self AddPlayerStat( "misses", int( max( 0, TRACK_WEAPON_SHOT_FIRED - self.hits ) ) );
	
	if ( isdefined( self.totalMatchShots ) )
		self.totalMatchShots++;
	
	self bb::add_to_stat( "shots", 1 );
	self bb::add_to_stat( "hits", self.hits );

	if( level.mpCustomMatch === true )
	{
		self.pers["shotsfired"]++;
		self.shotsfired = self.pers["shotsfired"];
	
		self.pers["shotshit"] += self.hits;
		self.shotshit = self.pers["shotshit"];
		
		self.pers["shotsmissed"] = self.shotsfired - self.shotshit;
		self.shotsmissed = self.pers["shotsmissed"];
	}
			
	self.hits = 0;
	pixendevent();
}

function watch_grenade_usage()
{
	self endon( "death" );
	self endon( "disconnect" );
	
	self.throwingGrenade = false;
	self.gotPullbackNotify = false;
		
	self thread begin_other_grenade_tracking();
	
	self thread watch_for_throwbacks();
	self thread watch_for_grenade_duds();
	self thread watch_for_grenade_launcher_duds();

	for ( ;; )
	{
		self waittill ( "grenade_pullback", weapon );
		
		self AddWeaponStat( weapon, "shots", 1, self.class_num );

		self.hasDoneCombat = true;

		self.throwingGrenade = true;
		self.gotPullbackNotify = true;
		
		if ( weapon.drawOffhandModelInHand )
		{
			self SetOffhandVisible( true );
			self thread watch_offhand_end();
		}

		self thread begin_grenade_tracking();
	}
}

function watch_missile_usage()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon ( "game_ended" );

	for ( ;; )
	{	
		self waittill ( "missile_fire", missile, weapon );

		self.hasDoneCombat = true;

		/#assert( isdefined( missile ));#/
		level.missileEntities[ level.missileEntities.size ] = missile;
		missile.weapon = weapon;
		missile thread watch_missile_death();
	}
}

function watch_missile_death() // self == missile
{
	self waittill( "death" );
	ArrayRemoveValue( level.missileEntities, self ); 
}

function drop_all_to_ground( origin, radius )
{
	weapons = GetDroppedWeapons();
	for ( i = 0 ; i < weapons.size ; i++ )
	{
		if ( DistanceSquared( origin, weapons[i].origin ) < radius * radius )
		{
			trace = bullettrace( weapons[i].origin, weapons[i].origin + (0,0,-2000), false, weapons[i] );
			weapons[i].origin = trace["position"];
		}
	}
}

function drop_grenades_to_ground( origin, radius )
{
	grenades = getentarray( "grenade", "classname" );
	for( i = 0 ; i < grenades.size ; i++ )
	{
		if( DistanceSquared( origin, grenades[i].origin )< radius * radius )
		{
			grenades[i] launch( (5,5,5) );
		}
	}
}

function watch_grenade_cancel()
{
	self endon ( "death" );
	self endon ( "disconnect" );
	self endon ( "grenade_fire" );
	
	waittillframeend;
	weapon = level.weaponNone;
	
	while( self IsThrowingGrenade() && weapon == level.weaponNone ) 
	{
		self waittill( "weapon_change", weapon );
	}
	
	self.throwingGrenade = false;
	self.gotPullbackNotify = false;
	
	self notify( "grenade_throw_cancelled" );
}

function watch_offhand_end() // self == player
{
	self notify( "watchOffhandEnd" );
	self endon( "watchOffhandEnd" );

	while ( self is_using_offhand_equipment() )
	{
		msg = self util::waittill_any_return( "death", "disconnect", "grenade_fire", "weapon_change", "watchOffhandEnd" );

		if (( msg == "death" ) || ( msg == "disconnect" ))
		{
			break;
		}
	}
	
	if ( isdefined( self ) )
	{
		self SetOffhandVisible( false );
	}	
}

function is_using_offhand_equipment() // self == player
{
	if ( self IsUsingOffhand() )
	{
		weapon = self GetCurrentOffhand();
		if ( weapon.isEquipment )
		{
			return true;
		}
	}

	return false;
}


function begin_grenade_tracking()
{
	self endon ( "death" );
	self endon ( "disconnect" );
	self endon ( "grenade_throw_cancelled" );
	
	startTime = getTime();
	
	self thread watch_grenade_cancel();
	
	self waittill ( "grenade_fire", grenade, weapon, cookTime );

	/#assert( isdefined( grenade ));#/
	level.missileEntities[ level.missileEntities.size ] = grenade;
	grenade.weapon = weapon;
	grenade thread watch_missile_death();

	if( ( SessionModeIsCampaignZombiesGame() ) || ( isdefined( level.projectiles_should_ignore_world_pause ) && level.projectiles_should_ignore_world_pause ) )
	{
		grenade SetIgnorePauseWorld( true );
	}	
	
	if ( grenade util::isHacked() )
	{
		return;
	}
	
	
	blackBoxEventName = "mpequipmentuses";
	
	if ( SessionModeIsCampaignGame() )
	{
		blackBoxEventName = "cpequipmentuses";
	}
	else if ( SessionModeIsZombiesGame() )
	{
		blackBoxEventName = "zmequipmentuses";
	}
	
	
	bbPrint( blackBoxEventName, "gametime %d spawnid %d weaponname %s", gettime(), getplayerspawnid( self ), weapon.name );

	cookedTime = getTime() - startTime;
	
	if ( cookedTime > 1000 )
	{
		grenade.isCooked = true;
	}
	
	if (isDefined(self.grenadesUsed))
	{
		self.grenadesUsed++;
	}
	
	switch ( weapon.rootWeapon.name )
	{
	case "frag_grenade":
		level.globalFragGrenadesFired++;
		// fall through on purpose
	case "sticky_grenade":
		self AddWeaponStat( weapon, "used", 1 );
		grenade SetTeam( self.pers["team"] );
		grenade SetOwner( self );
		// fall through on purpose
	case "explosive_bolt":
		grenade.originalOwner = self;
		break;
	case "satchel_charge":
		level.globalSatchelChargeFired++;
		break;
	case "concussion_grenade":
	case "flash_grenade":
		self AddWeaponStat( weapon, "used", 1 );
		break;
	}

	self.throwingGrenade = false;
	
	if ( weapon.cookOffHoldTime > 0 )
	{				
		grenade thread track_cooked_detonation( self, weapon, cookTime );
	}	
	else if ( weapon.multiDetonation > 0 )
	{
		grenade thread track_multi_detonation( self, weapon, cookTime );
	}
}

function begin_other_grenade_tracking()
{
	self notify( "otherGrenadeTrackingStart" );
	
	self endon( "otherGrenadeTrackingStart" );
	self endon( "disconnect" );
	
	for (;;)
	{
		self waittill ( "grenade_fire", grenade, weapon );

		if ( grenade util::isHacked() )
		{
			continue;
		}
		
		switch( weapon.rootweapon.name )
		{		
		case "tabun_gas":
			grenade thread tabun::watchTabunGrenadeDetonation( self );
			break;
		case "sticky_grenade":
			grenade thread check_stuck_to_player( true, true, weapon );
			grenade thread riotshield::check_stuck_to_shield();
			break;
		case "satchel_charge":
		case "c4":
			grenade thread check_stuck_to_player( true, false, weapon );
			break;
		case "hatchet":
			grenade.lastWeaponBeforeToss = self util::getLastWeapon();
			grenade thread check_hatchet_bounce();
			grenade thread check_stuck_to_player( false, false, weapon );
			self AddWeaponStat( weapon, "used", 1 );
			break;
		default:
			break;
		}
	}
}

function check_stuck_to_player( deleteOnTeamChange, awardScoreEvent, weapon )
{
	self endon( "death" );

	self waittill( "stuck_to_player", player );
	if ( isdefined ( player ) )
	{
		if ( deleteOnTeamChange )
			self thread stuck_to_player_team_change( player );

		if ( awardScoreEvent && isdefined ( self.originalOwner ) )
		{
			if ( self.originalOwner util::IsEnemyPlayer( player ) )
			{
				scoreevents::processScoreEvent( "stick_explosive_kill", self.originalOwner, player, weapon );
			}
		}

		self.stuckToPlayer = player;
	}
}

function check_hatchet_bounce()
{
	self endon( "stuck_to_player" );
	self endon( "death");
	self waittill( "grenade_bounce" );
	self.bounced = true;
}

function stuck_to_player_team_change( player )
{
	self endon("death");
	player endon("disconnect");
	originalTeam = player.pers["team"];

	while(1)
	{
		player waittill("joined_team");
		
		if ( player.pers["team"] != originalTeam )
		{
			self detonate();
			return;
		}
	}
}

function watch_for_throwbacks()
{
	self endon ( "death" );
	self endon ( "disconnect" );
	
	for ( ;; )
	{
		self waittill ( "grenade_fire", grenade, weapon );
		
		if ( self.gotPullbackNotify )
		{
			self.gotPullbackNotify = false;
			continue;
		}
		
		if ( !isSubStr( weapon.name, "frag_" ) )
			continue;
		
		// no grenade_pullback notify! we must have picked it up off the ground.
		grenade.threwBack = true;
		
		grenade.originalOwner = self;
	}
}

function wait_and_delete_dud( waitTime )
{
	self endon( "death" );

	wait( waitTime );

	if( isDefined( self ) )
		self delete();
}

function getTimeFromLevelStart()
{
	if ( !isdefined( level.startTime ) )
		return 0;
	
	return (gettime() - level.startTime);

}

function turn_grenade_into_a_dud( weapon, isThrownGrenade, player )
{
	time = (getTimeFromLevelStart() / 1000);
	if ( level.roundStartExplosiveDelay >= time )
	{
		if ( weapon.disallowatmatchstart || WeaponHasAttachment( weapon, "gl" ) )
		{
			timeLeft = Int( level.roundStartExplosiveDelay - time );

			if ( !timeLeft )
				timeLeft = 1;
			
			// these prints need to be changed to the correct location and they should include the weapon name
			if ( isThrownGrenade )
				player iPrintLnBold( &"MP_GRENADE_UNAVAILABLE_FOR_N", " " + timeLeft + " ", &"EXE_SECONDS" );
			else			
				player iPrintLnBold( &"MP_LAUNCHER_UNAVAILABLE_FOR_N", " " + timeLeft + " ", &"EXE_SECONDS" );


			self makeGrenadeDud();
		}
	}
}

function watch_for_grenade_duds()
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	
	while ( 1 )
	{
		self waittill( "grenade_fire", grenade, weapon );
		
		grenade turn_grenade_into_a_dud( weapon, true, self );
	}
}

function watch_for_grenade_launcher_duds()
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	
	while ( 1 )
	{
		self waittill( "grenade_launcher_fire", grenade, weapon );
		grenade turn_grenade_into_a_dud( weapon, false, self );

		/#assert( isDefined( grenade ));#/
		level.missileEntities[ level.missileEntities.size ] = grenade;
		grenade.weapon = weapon;
		grenade thread watch_missile_death();
	}
}


// these functions are used with scripted weapons (like satchels, shoeboxs, artillery)
// returns an array of objects representing damageable entities (including players) within a given sphere.
// each object has the property damageCenter, which represents its center (the location from which it can be damaged).
// each object also has the property entity, which contains the entity that it represents.
// to damage it, call damageEnt() on it.
function get_damageable_ents(pos, radius, doLOS, startRadius)
{
	ents = [];
	
	if (!isdefined(doLOS))
		doLOS = false;
		
	if ( !isdefined( startRadius ) )
		startRadius = 0;
	
	// players
	players = level.players;
	for (i = 0; i < players.size; i++)
	{
		if (!isalive(players[i]) || players[i].sessionstate != "playing")
			continue;
		
		playerpos = players[i].origin + (0,0,32);
		distsq = distancesquared(pos, playerpos);
		if (distsq < radius*radius && (!doLOS || damage_trace_passed(pos, playerpos, startRadius, undefined)))
		{
			newent = spawnstruct();
			newent.isPlayer = true;
			newent.isADestructable = false;
			newent.isADestructible = false;
			newent.isActor = false;
			newent.entity = players[i];
			newent.damageCenter = playerpos;
			ents[ents.size] = newent;
		}
	}
	
	// grenades
	grenades = getentarray("grenade", "classname");
	for (i = 0; i < grenades.size; i++)
	{
		entpos = grenades[i].origin;
		distsq = distancesquared(pos, entpos);
		if (distsq < radius*radius && (!doLOS || damage_trace_passed(pos, entpos, startRadius, grenades[i])))
		{
			newent = spawnstruct();
			newent.isPlayer = false;
			newent.isADestructable = false;
			newent.isADestructible = false;
			newent.isActor = false;
			newent.entity = grenades[i];
			newent.damageCenter = entpos;
			ents[ents.size] = newent;
		}
	}
	
	// THIS IS NOT THE SAME AS THE destruct-A-bles BELOW
	destructibles = getentarray("destructible", "targetname");		
	for (i = 0; i < destructibles.size; i++)
	{
		entpos = destructibles[i].origin;
		distsq = distancesquared(pos, entpos);
		if (distsq < radius*radius && (!doLOS || damage_trace_passed(pos, entpos, startRadius, destructibles[i])))
		{
			newent = spawnstruct();
			newent.isPlayer = false;
			newent.isADestructable = false;
			newent.isADestructible = true;
			newent.isActor = false;
			newent.entity = destructibles[i];
			newent.damageCenter = entpos;
			ents[ents.size] = newent;
		}
	}

	// THIS IS NOT THE SAME AS THE destruct-I-bles ABOVE
	destructables = getentarray("destructable", "targetname");
	for (i = 0; i < destructables.size; i++)
	{
		entpos = destructables[i].origin;
		distsq = distancesquared(pos, entpos);
		if (distsq < radius*radius && (!doLOS || damage_trace_passed(pos, entpos, startRadius, destructables[i])))
		{
			newent = spawnstruct();
			newent.isPlayer = false;
			newent.isADestructable = true;
			newent.isADestructible = false;
			newent.isActor = false;
			newent.entity = destructables[i];
			newent.damageCenter = entpos;
			ents[ents.size] = newent;
		}
	}

	dogs = [[level.dogManagerOnGetDogs]]();
	
	if ( isdefined( dogs ) )
	{
		foreach( dog in dogs )
		{
			if ( !IsAlive( dog ) )
			{
				continue;
			}

			entpos = dog.origin;
			distsq = distancesquared(pos, entpos);
			if (distsq < radius*radius && (!doLOS || damage_trace_passed(pos, entpos, startRadius, dog)))
			{
				newent = spawnstruct();
				newent.isPlayer = false;
				newent.isADestructable = false;
				newent.isADestructible = false;
				newent.isActor = true;
				newent.entity = dog;
				newent.damageCenter = entpos;
				ents[ents.size] = newent;
			}
		}
	}
	
	return ents;
}

function damage_trace_passed(from, to, startRadius, ignore)
{
	trace = damage_trace(from, to, startRadius, ignore);	
	return (trace["fraction"] == 1);
}

function damage_trace(from, to, startRadius, ignore)
{
	midpos = undefined;
	
	diff = to - from;
	if ( lengthsquared( diff ) < startRadius*startRadius )
		midpos = to;
	dir = vectornormalize( diff );
	midpos = from + (dir[0]*startRadius, dir[1]*startRadius, dir[2]*startRadius);

	trace = bullettrace(midpos, to, false, ignore);
	
	if ( GetDvarint( "scr_damage_debug") != 0 )
	{
		if (trace["fraction"] == 1)
		{
			thread debugline(midpos, to, (1,1,1));
		}
		else
		{
			thread debugline(midpos, trace["position"], (1,.9,.8));
			thread debugline(trace["position"], to, (1,.4,.3));
		}
	}
	
	return trace;
}

// eInflictor = the entity that causes the damage (e.g. a shoebox)
// eAttacker = the player that is attacking
// iDamage = the amount of damage to do
// sMeansOfDeath = string specifying the method of death (e.g. "MOD_PROJECTILE_SPLASH")
// weapon = the weapon used
// damagepos = the position damage is coming from
// damagedir = the direction damage is moving in
function damage_ent(eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, damagepos, damagedir)
{
	if (self.isPlayer)
	{
		self.damageOrigin = damagepos;
		self.entity thread [[level.callbackPlayerDamage]](
			eInflictor, // eInflictor The entity that causes the damage.(e.g. a turret)
			eAttacker, // eAttacker The entity that is attacking.
			iDamage, // iDamage Integer specifying the amount of damage done
			0, // iDFlags Integer specifying flags that are to be applied to the damage
			sMeansOfDeath, // sMeansOfDeath Integer specifying the method of death
			weapon, // weapon The weapon used to inflict the damage
			damagepos, // vPoint The point the damage is from?
			damagedir, // vDir The direction of the damage
			"none", // sHitLoc The location of the hit
			damagepos, // sDamageOrigin
			0, // psOffsetTime The time offset for the damage
			0, // boneIndex
			undefined // surfaceNormal
		);
	}
	else if (self.isactor)
	{
		self.damageOrigin = damagepos;
		self.entity thread [[level.callbackActorDamage]](
			eInflictor, // eInflictor The entity that causes the damage.(e.g. a turret)
			eAttacker, // eAttacker The entity that is attacking.
			iDamage, // iDamage Integer specifying the amount of damage done
			0, // iDFlags Integer specifying flags that are to be applied to the damage
			sMeansOfDeath, // sMeansOfDeath Integer specifying the method of death
			weapon, // weapon The weapon used to inflict the damage
			damagepos, // vPoint The point the damage is from?
			damagedir, // vDir The direction of the damage
			"none", // sHitLoc The location of the hit
			damagepos, // vDamageOrigin the point the damage originates			
			0, // psOffsetTime The time offset for the damage
			0, // boneIndex
			0, // modelIndex
			0, // surfaceType
			(1.0, 0, 0) // surfaceNormal
		);
	}
	else if (self.isADestructible)
	{
		self.damageOrigin = damagepos;
		self.entity DoDamage(
			iDamage, // iDamage Integer specifying the amount of damage done
			damagepos, // vPoint The point the damage is from?
			eAttacker, // eAttacker The entity that is attacking.
			eInflictor, // eInflictor The entity that causes the damage.(e.g. a turret)
			0, 
			sMeansOfDeath, // sMeansOfDeath Integer specifying the method of death
			0, // iDFlags Integer specifying flags that are to be applied to the damage
			weapon // weapon The weapon used to inflict the damage
		);
	}
	else
	{
		self.entity util::damage_notify_wrapper( iDamage, eAttacker, (0,0,0), (0,0,0), "mod_explosive", "", "" );		
	}
}

function debugline(a, b, color)
{
	/#
	for (i = 0; i < 30*20; i++)
	{
		line(a,b, color);
		wait .05;
	}
	#/
}


function on_damage( eAttacker, eInflictor, weapon, meansOfDeath, damage )
{
	self endon ( "death" );
	self endon ( "disconnect" );

	if ( isdefined( level._custom_weapon_damage_func ) )
	{
		is_weapon_registered = self [[level._custom_weapon_damage_func]]( eAttacker, eInflictor, weapon, meansOfDeath, damage );
		if( is_weapon_registered )
		{
			return;
		}
	}

	switch ( weapon.rootWeapon.name )
	{
		case "concussion_grenade":			
			if ( ( isdefined( self.concussionImmune ) && self.concussionImmune ) )
				return;

			radius = weapon.explosionRadius;
			
			if (self == eAttacker) // TFLAME 8/1/12 - reduce effects on attacker 
				radius *= 0.5;
			
			scale = 1 - (distance( self.origin, eInflictor.origin ) / radius);
			
			if ( scale < 0 )
				scale = 0;
			
			time = 0.25 + (4 * scale);
			
			{wait(.05);};

			if ( meansOfDeath != "MOD_IMPACT" )
			{
				if ( self HasPerk ( "specialty_stunprotection" ) )
				{
					time *= 0.1;
				}
				else
				{
					if ( self util::mayApplyScreenEffect() ) 
					{
						self shellShock( "concussion_grenade_mp", time, false ); 
					}
				}
				
				self thread play_concussion_sound( time );
				self.concussionEndTime = getTime() + (time * 1000); 
				self.lastConcussedBy = eAttacker;
			}
			break;

		default:
			// shellshock will only be done if meansofdeath is an appropriate type and if there is enough damage.
			if ( isdefined( level.shellshockOnPlayerDamage ) )
			{
				 [[level.shellshockOnPlayerDamage]]( meansOfDeath, damage, weapon );
			}
			break;
	}
}

function play_concussion_sound( duration )
{	
	self endon( "death" );
	self endon( "disconnect" );

	concussionSound = spawn ("script_origin",(0,0,1));
	concussionSound.origin = self.origin;
	concussionSound linkTo( self );
	concussionSound thread delete_ent_on_owner_death( self );
	concussionSound playsound( "" );
	concussionSound playLoopSound ( "" );
	if ( duration > 0.5 )
		wait( duration - 0.5 );
	concussionSound playsound( "" );
	concussionSound StopLoopSound( .5);
	wait(0.5);

	concussionSound notify ( "delete" );
	concussionSound delete();
}

function delete_ent_on_owner_death( owner )
{
	self endon( "delete" );
	owner waittill( "death" );
	self delete();	
}

// weapon stowing logic ===================================================================

// thread loop life = player's life
function update_stowed_weapon()
{
	self endon( "spawned" );
	self endon( "killed_player" );
	self endon( "disconnect" );
	
	//weapons::detach_all_weapons();
	
	self.tag_stowed_back = undefined;
	self.tag_stowed_hip = undefined;
	
	team = self.pers["team"];
	playerclass = self.pers["class"];
	
	while ( true )
	{
		self waittill( "weapon_change", newWeapon );

		if ( self IsMantling() )
		{
			continue;
		}
		
		currentStowed = self GetStowedWeapon();
		hasStowed = false;
		
		// weapon array reset, might have swapped weapons off the ground
		self.weapon_array_primary =[];
		self.weapon_array_sidearm = [];
		self.weapon_array_grenade = [];
		self.weapon_array_inventory =[];
	
		// populate player's weapon stock arrays
		weaponsList = self GetWeaponsList();
		for( idx = 0; idx < weaponsList.size; idx++ )
		{
			// we don't want these in the primary list
			switch( weaponsList[idx].name )
			{
			case "minigun":		// death machine
			case "m32":			// grenade launcher
				continue;

			default:
				break;
			}
			
			if ( !hasStowed || currentStowed == weaponsList[idx] )
			{
				currentStowed = weaponsList[idx];
				hasStowed = true;
			}
			
			if ( weapons::is_primary_weapon( weaponsList[idx] ) )
				self.weapon_array_primary[self.weapon_array_primary.size] = weaponsList[idx];
			else if ( weapons::is_side_arm( weaponsList[idx] ) )
				self.weapon_array_sidearm[self.weapon_array_sidearm.size] = weaponsList[idx];
			else if ( weapons::is_grenade( weaponsList[idx] ) )
				self.weapon_array_grenade[self.weapon_array_grenade.size] = weaponsList[idx];
			else if ( weapons::is_inventory( weaponsList[idx] ) )
				self.weapon_array_inventory[self.weapon_array_inventory.size] = weaponsList[idx];
			else if ( weaponsList[idx].isPrimary )
				self.weapon_array_primary[self.weapon_array_primary.size] = weaponsList[idx];
		}

		if ( newWeapon != level.weaponNone || !hasStowed )
		{
			weapons::detach_all_weapons();
			weapons::stow_on_back();
			weapons::stow_on_hip();
		}
	}
}

function loadout_get_offhand_weapon( stat )
{
	if ( isdefined( level.giveCustomLoadout ) )
	{
		return level.weaponNone;
	}

	assert( isdefined( self.class_num ) );
	if ( isdefined( self.class_num ) ) 
	{
		index = self loadout::getLoadoutItemFromDDLStats( self.class_num, stat );
	
		if ( isdefined( level.tbl_weaponIDs[index] ) && isdefined( level.tbl_weaponIDs[index]["reference"] ) )
		{
			return GetWeapon( level.tbl_weaponIDs[index]["reference"] );
		}
	}

	return level.weaponNone;
}

function loadout_get_offhand_count( stat )
{
	count = 0;
	if ( isdefined( level.giveCustomLoadout ) )
	{
		return 0;
	}

	assert( isdefined( self.class_num ) );
	if ( isdefined( self.class_num ) )
	{
		count = self loadout::getLoadoutItemFromDDLStats( self.class_num, stat );
	}

	return count;
}

// Self == player
function flash_scavenger_icon()
{
	self.scavenger_icon.alpha = 1;
	self.scavenger_icon fadeOverTime( 1.0 );
	self.scavenger_icon.alpha = 0;
}

function scavenger_think()
{
	self endon( "death" );
	self waittill( "scavenger", player );

	primary_weapons = player GetWeaponsListPrimaries();
	offhand_weapons_and_alts = array::exclude( player GetWeaponsList( true ), primary_weapons );

	ArrayRemoveValue( offhand_weapons_and_alts, level.weaponBaseMelee );
	offhand_weapons_and_alts = array::reverse( offhand_weapons_and_alts ); // Prioritize tacticals over lethals

	player playsound( "wpn_ammo_pickup" );
	player playlocalsound( "wpn_ammo_pickup" );

	player flash_scavenger_icon();
	
	for ( i = 0; i < offhand_weapons_and_alts.size; i++ )
	{
		weapon = offhand_weapons_and_alts[i];

		if ( !weapon.isScavengable || killstreaks::is_killstreak_weapon( weapon ) )
		{
			continue;
		}
		
		maxAmmo = 0;
		
		if ( weapon == player.grenadeTypePrimary && isdefined( player.grenadeTypePrimaryCount ) && player.grenadeTypePrimaryCount > 0 )
		{
			maxAmmo = player.grenadeTypePrimaryCount;
		}
		else if ( weapon == player.grenadeTypeSecondary && isdefined( player.grenadeTypeSecondaryCount ) && player.grenadeTypeSecondaryCount > 0 )
		{
			maxAmmo = player.grenadeTypeSecondaryCount;
		}
		
		if ( isdefined( level.playerGetWeaponScavengeAmmo ) )
		{
			maxAmmo = player [[level.playerGetWeaponScavengeAmmo]]( weapon, maxAmmo );
		}
		
		if ( maxAmmo == 0 )
		{
			continue;
		}
		
		if ( weapon.rootWeapon == level.weaponSatchelCharge )
		{
			if ( player weaponobjects::anyObjectsInWorld( weapon.rootWeapon ) )
			{
				continue;
			}
		}		
			
		stock = player GetWeaponAmmoStock( weapon );

		if ( stock < maxAmmo )
		{
			// just give 1 for each scavenger pick up
			ammo = stock + 1;
			if ( ammo > maxAmmo )
			{
				ammo = maxAmmo;
			}
			player SetWeaponAmmoStock( weapon, ammo );
			player.scavenged = true;
			
			player thread challenges::scavengedGrenade();
		}
		else
		{
			if ( weapon.rootWeapon == GetWeapon( "trophy_system" ) )
			{
				player trophy_system::ammo_scavenger( weapon );
			}
		}
	}

	for ( i = 0; i < primary_weapons.size; i++ )
	{
		weapon = primary_weapons[i];

		if ( !weapon.isScavengable || killstreaks::is_killstreak_weapon( weapon ) )
		{
			continue;
		}

		stock = player GetWeaponAmmoStock( weapon );
		start = player GetFractionStartAmmo( weapon );
		clip = weapon.clipSize;
		clip *= GetDvarFloat( "scavenger_clip_multiplier", 1 );
		clip = Int( clip );
		
		if ( isdefined( level.weaponLauncherEx41 ) && ( weapon.statIndex == level.weaponLauncherEx41.statIndex ) )
			clip = 1;
		
		maxAmmo = weapon.maxAmmo;

		if ( stock < maxAmmo - clip )
		{
			ammo = stock + clip;
			player SetWeaponAmmoStock( weapon, ammo );
			player.scavenged = true;
		}
		else
		{
			player SetWeaponAmmoStock( weapon, maxAmmo );
			player.scavenged = true;
		}
	}
}

function scavenger_hud_destroyOnDisconnect()
{
	self waittill("disconnect");
	if(isDefined(self.scavenger_icon))
		self.scavenger_icon destroy();
}

function scavenger_hud_create()
{
	if( level.wagerMatch )
		return;
	if( ( isdefined( level.noScavenger ) && level.noScavenger ))
		return;
	
	self.scavenger_icon = NewClientHudElem( self );
	if(isDefined(self.scavenger_icon))
	{
		self thread scavenger_hud_destroyOnDisconnect();
		
		self.scavenger_icon.horzAlign = "center";
		self.scavenger_icon.vertAlign = "middle";
		self.scavenger_icon.alpha = 0;
	
		width = 64;
		height = 64;
	
		if ( level.splitscreen )
		{
			width = Int( width * 0.5 );
			height = Int( height * 0.5 );
		}
		
		self.scavenger_icon.x = -width/2;
		self.scavenger_icon.y = 16;
	
		self.scavenger_icon setShader( "hud_scavenger_pickup", width, height );
	}
}

function drop_scavenger_for_death( attacker )
{
	if ( level.wagerMatch )
		return;

	if ( !isdefined( attacker ) )
 		return;

 	if ( attacker == self )
 		return;
 		
	if ( level.gameType == "hack" )
	{
		item = self dropScavengerItem( GetWeapon( "scavenger_item_hack" ) );
	}
	else if ( isplayer( Attacker ) )
	{
		item = self dropScavengerItem( GetWeapon( "scavenger_item" ) );
	}
	else
	{
		return;
	}
	
	item thread scavenger_think();
}


// if we need to store multiple drop limited weapons, we'll need to store an array on the player entity
function add_limited_weapon( weapon, owner, num_drops )
{
	limited_info = SpawnStruct();
	limited_info.weapon = weapon;
	limited_info.drops = num_drops;

	owner.limited_info = limited_info;
}

function should_drop_limited_weapon( weapon, owner )
{
	limited_info = owner.limited_info;
		
	if ( !isdefined( limited_info ) )
	{
		return true;
	}

	if ( limited_info.weapon != weapon )
	{
		return true;
	}

	if ( limited_info.drops <= 0 )
	{
		return false;
	}

	return true;
}


function drop_limited_weapon( weapon, owner, item )
{
	limited_info = owner.limited_info;

	if ( !isdefined( limited_info ) )
	{
		return;
	}

	if ( limited_info.weapon != weapon )
	{
		return;
	}

	limited_info.drops = limited_info.drops - 1;
	owner.limited_info = undefined;

	item thread limited_pickup( limited_info );
}


function limited_pickup( limited_info )
{
	self endon( "death" );
	self waittill( "trigger", player, item );

	if ( !isdefined( item ) )
	{
		return;
	}

	player.limited_info = limited_info;
}

// self is the grenade
function track_cooked_detonation( attacker, weapon, cookTime )
{
	self endon( "trophy_destroyed" );
	
	self waittill( "explode", origin, surface );
	
	if ( weapon.rootWeapon == level.weaponFlashGrenade )
	{	
		level thread nineBang_doNineBang( attacker, weapon, origin, cookTime );		
	}
}

function nineBang_doNineBang( attacker, weapon, pos, cookTime )
{
	level endon( "game_ended" );
	
	maxStages = 4;
	maxRadius = 20;
	minDelay = 0.15;
	maxdelay = 0.3;
	
	explosionRadiusSq = weapon.explosionRadius * weapon.explosionRadius;
	explosionRadiusMinSq = weapon.explosionInnerRadius * weapon.explosionInnerRadius;
	
	cookStages = cookTime / weapon.cookOffHoldTime * maxStages + 1;
				
	detonations = 0;
	
	if ( cookStages < 2 )
	{
		return;
	}
	else if ( cookStages < 3 )
	{
		detonations = 3;
	}
	else if ( cookStages < 4 )
	{
		detonations = 6;
	}
	else // max
	{
		detonations = 9;
		//nineBang_DoEmpDamage( attacker, weapon, pos );
	}	
	
	wait( RandomFloatRange( minDelay, maxDelay ) );
	
	// the nine bang will go off detonations-1 times as it already exploded once, and flash everything in the vacinity
	for( i = 1; i < detonations; i++ )
	{
		newPos = level nineBang_getSubExplosionPos( pos, maxRadius );
		
		PlaySoundAtPosition( "wpn_flash_grenade_explode", newPos );
		PlayFX( level._effect["flashNineBang"], newPos );

		closestPlayers = ArraySort( level.players, newPos, true );
		
		// get players within the radius
		foreach( player in closestPlayers )
		{
			if ( !isdefined( player ) || !IsAlive( player ) )
			{
				continue;
			}
			
			if ( player.sessionstate != "playing" )
			{
				continue;
			}
			
			viewOrigin = player GetEye();			
			
			// first make sure they are within distance
			dist = DistanceSquared( pos, viewOrigin );
			
			if( dist > explosionRadiusSq )
			{
				break;
			}
		
			// now make sure they can be hit by it
			if( !BulletTracePassed( pos, viewOrigin, false, player ) )
				continue;
		
			if ( dist <= explosionRadiusMinSq )
				percent_distance = 1.0;
			else
				percent_distance = 1.0 - ( dist - explosionRadiusMinSq ) / ( explosionRadiusSq - explosionRadiusMinSq );
		
			forward = AnglesToForward( player GetPlayerAngles() );
		
			toBlast = pos - viewOrigin;
			toBlast = VectorNormalize( toBlast );
		
			percent_angle = 0.5 * ( 1.0 + VectorDot( forward, toBlast ) );
		
			player notify( "flashbang", percent_distance, percent_angle, attacker );
		}		

		wait( RandomFloatRange( minDelay, maxDelay ) );
	}	
}

function nineBang_getSubExplosionPos( startPos, range )
{
	offset = ( RandomFloatRange( -1.0 * range, range ), RandomFloatRange( -1.0 * range, range ), 0 );
	newPos = startPos + offset;
	
	// make sure we don't spawn through walls
	if ( BulletTracePassed( startPos, newPos, false, undefined ) )
	{
		return newPos;
	}
	
	return startPos;
}

function nineBang_DoEmpDamage( player, weapon, position )
{
	kNineBangEmpRadius = 512;
	radiusSq = kNineBangEmpRadius * kNineBangEmpRadius;
		
	PlaySoundAtPosition( "wpn_emp_explode", position );
	
	level empgrenade::empExplosionDamageEnts( player, weapon, position, kNineBangEmpRadius, false );
	
	foreach ( targetEnt in level.players )
	{
		if ( nineBang_empCanDamage( targetEnt, position, radiusSq, false, 0 ) )
		{
			targetEnt notify( "emp_grenaded", player );
		}			
	}
}

function nineBang_empCanDamage( ent, pos, radiusSq, doLOS, startRadius )
{
	entpos = ent.origin;
	distSq = DistanceSquared( pos, entpos );
	return ( distSq < radiusSq
	        && ( !doLOS || weapons::weaponDamageTracePassed( pos, entpos, startRadius, ent ) ) );
}


function track_multi_detonation( ownerEnt, weapon, cookTime ) // self is the grenade
{
	self endon( "trophy_destroyed" );
	
	self waittill( "explode", origin, surface );
	
	if ( weapon.rootWeapon == GetWeapon( "frag_grenade_grenade" ) )
	{
		for ( i = 0; i < weapon.multiDetonation; i++ )
		{
			// spawn a magic grenade
			if ( !isdefined( ownerEnt ) )
			{
				return;
			}
			
			multiblastWeapon = GetWeapon( "frag_multi_blast" );
			
			// get a velocity
			dir = level multi_detonation_get_cluster_launch_dir( i, weapon.multiDetonation );
			vel = dir * multiblastWeapon.multiDetonationFragmentSpeed;
			fuseTime = multiblastWeapon.fusetime/1000;
			
			grenade = ownerEnt MagicGrenadeType( multiblastWeapon, origin, vel, fuseTime );
			
			util::wait_network_frame();
		}
	}
}

function multi_detonation_get_cluster_launch_dir( index, multiVal )
{	
	pitch = 45;
	yaw = -180 + ( 360 / multiVal ) * index;
		
	angles = ( pitch, yaw, 45 );

	dir = AnglesToForward( angles );

	return dir;
}

function should_suppress_damage( weapon, inflictor )
{
	if ( !isdefined( weapon ) )
		return false;
	
	if ( !isdefined( self ) )
		return false;
	
	if ( isdefined( level.weaponSpecialDiscGun ) && weapon.statIndex == level.weaponSpecialDiscGun.statIndex )
	{
		if ( isdefined( inflictor ) )
		{
			if(!isdefined(inflictor.hit_info))inflictor.hit_info=[];
			
			victimEntNum = self GetEntityNumber();
			
			if ( isdefined( inflictor.hit_info[ victimEntNum ] ) )
				return true;
			
			inflictor.hit_info[ victimEntNum ] = true;
		}
	}
	
	return false;
}
