#include maps\mp\_utility;
#include common_scripts\utility;


// common functionality between SR and SD


onPrecacheGameType()
{
	game["bomb_dropped_sound"] = "mp_obj_notify_neg_sml";
	game["bomb_dropped_enemy_sound"] = "mp_obj_notify_pos_sml";
	game["bomb_recovered_sound"] = "mp_obj_notify_pos_sml";
	game["bomb_grabbed_sound"] = "mp_snd_bomb_pickup";
	game["bomb_planted_sound"] = "mp_obj_notify_pos_med";
	game["bomb_planted_enemy_sound"] = "mp_obj_notify_neg_med";
	game["bomb_disarm_sound"] = "mp_obj_notify_pos_lrg";
	game["bomb_disarm_enemy_sound"] = "mp_obj_notify_neg_lrg";
}


updateGametypeDvars()
{
	level.plantTime = dvarFloatValue( "planttime", 5, 0, 20 );
	level.defuseTime = dvarFloatValue( "defusetime", 5, 0, 20 );
	level.bombTimer = dvarFloatValue( "bombtimer", 45, 1, 300 );
	level.multiBomb = dvarIntValue( "multibomb", 0, 0, 1 );
	level.silentPlant = dvarIntValue( "silentplant", 0, 0, 1 );
}


setSpecialLoadout()
{
	//	attacker bomb carrier
	if ( isUsingMatchRulesData() && GetMatchRulesData( "defaultClasses", game["attackers"], 5, "class", "inUse" ) )
	{
		level.sd_loadout[game["attackers"]] = getMatchRulesSpecialClass( game["attackers"], 5 );	
	}		
}


isPlayerOutsideOfAnyBombSite( weaponName )
{
	if( IsDefined( level.bombZones ) )
	{
		foreach (bombZone in level.bombZones )
		{
			if( DistanceSquared( self.origin, bombZone.trigger.origin ) < (64*64) )
				return false;
		}
	}
	
	return true;
}

onNormalDeath( victim, attacker, killId )
{
	if( game["state"] == "postgame" && (victim.team == game["defenders"] || !level.bombPlanted) )
		attacker.finalKill = true;
		
	if( victim.isPlanting || victim.isDefusing )
	{
		attacker thread maps\mp\_events::defendObjectiveEvent( victim, killId );
		
		if( victim.isPlanting )
			attacker maps\mp\gametypes\_missions::processChallenge( "ch_" + level.gametype + "_interrupt" );
		
		if( victim.isDefusing )
			attacker maps\mp\gametypes\_missions::processChallenge( "ch_" + level.gametype + "_protector" );
	}
	
	if( wasElminatedByEnemy( victim, attacker) )
	{
		isLastPlayerAlive = isLastPlayerAlive( attacker );
		
		attacker thread maps\mp\_events::eliminatePlayerEvent( isLastPlayerAlive, victim );
	}
}

wasElminatedByEnemy( victim, attacker )
{
	if( maps\mp\gametypes\_damage::isFriendlyFire( victim, attacker ) )
		return false;
	
	if( victim maps\mp\gametypes\_playerlogic::maySpawn() )
		return false;
	
	return true;
}


onPickup( player )
{
	player.isBombCarrier = true;
	if ( player.team == "allies" )
		player.objective = 1;
	else
		player.objective = 2;
	
	if( IsPlayer(player) )
	{
		player thread maps\mp\_matchdata::logGameEvent( "pickup", player.origin );
		player SetClientOmnvar( "ui_carrying_bomb", true );
	}

	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_escort" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_escort" );
	
	//	bomb carrier class for custom games
	if( isDefined( level.sd_loadout ) && isDefined( level.sd_loadout[player.team] ) )
	{
		player.isRespawningWithBombCarrierClass = true;
		player thread applyBombCarrierClass();
	}

	if( !level.bombDefused )
	{
		teamPlayerCardSplash( "callout_bombtaken", player, player.team );		
		leaderDialog( "bomb_taken", player.pers["team"] );
	}
	
	maps\mp\_utility::playSoundOnPlayers( game["bomb_recovered_sound"], game["attackers"] );
	player PlayLocalSound(game["bomb_grabbed_sound"]);
}


bombs()
{
	level.bombPlanted = false;
	level.bombDefused = false;
	level.bombExploded = false;

	trigger = getEnt( "sd_bomb_pickup_trig", "targetname" );
	if( !isDefined( trigger ) )
	{
		error("No sd_bomb_pickup_trig trigger found in map.");
		return;
	}
	
	visuals[0] = getEnt( "sd_bomb", "targetname" );
	if( !isDefined( visuals[0] ) )
	{
		error("No sd_bomb script_model found in map.");
		return;
	}
	
	visuals[0] setModel( "npc_search_dstry_bomb" );
	
	if( !level.multiBomb )
	{
		level.sdBomb = maps\mp\gametypes\_gameobjects::createCarryObject( game["attackers"], trigger, visuals, (0,0,32) );
		level.sdBomb maps\mp\gametypes\_gameobjects::allowCarry( "friendly" );
		level.sdBomb maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_bomb" );
		level.sdBomb maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_bomb" );
		level.sdBomb maps\mp\gametypes\_gameobjects::setVisibleTeam( "friendly" );
		if (game["attackers"] == "allies")
			setMLGIcons( level.sdBomb, "waypoint_bomb_green" );
		else
			setMLGIcons( level.sdBomb, "waypoint_bomb_red" );		
		level.sdBomb.allowWeapons = true;
		level.sdBomb.onPickup = ::onPickup;
		level.sdBomb.onDrop = ::onDrop;
		level.sdBomb.canUseObject = ::canUse;
		level.sdBomb.goliathThink = ::goliathDropBomb;
	}
	else
	{
		trigger delete();
		visuals[0] delete();
	}
	
	
	level.bombZones = [];
	
	bombZones 			= getEntArray( "bombzone", "targetname" );
	bombZoneAugmented 	= getEntArray( "bombzone_augmented", "targetname" );	
	
	if( isAugmentedGameMode() )
	{
		for( aIndex = 0; aIndex < bombZoneAugmented.size; aIndex++ )
		{
			replacement = bombZoneAugmented[aIndex].script_label;
			
			for( pIndex = 0; pIndex < bombZones.size; pIndex++ )
			{
				if( bombZones[pIndex].script_label == replacement )
				{
					removeBombZone( bombZones[pIndex] );
					break;
				}
			}
			
		}
	}
	else
	{
		for( index = 0; index < bombZoneAugmented.size; index++ )
			removeBombZone( bombZoneAugmented[index] );
	}
	
	bombZones = array_combine( bombZones, bombZoneAugmented );
	
	for( index = 0; index < bombZones.size; index++ )
	{
		trigger = bombZones[index];
		visuals = getEntArray( bombZones[index].target, "targetname" );
		//play blinking light vfx on model
		playfxontag( getfx( "bomb_light_blinking" ), visuals[0], "tag_fx_1" );
		playfxontag( getfx( "bomb_light_blinking" ), visuals[0], "tag_fx_2" );
		
		bombZone = maps\mp\gametypes\_gameobjects::createUseObject( game["defenders"], trigger, visuals, (0,0,64) );
		bombZone maps\mp\gametypes\_gameobjects::allowUse( "enemy" );
		bombZone maps\mp\gametypes\_gameobjects::setUseTime( level.plantTime );
		bombZone maps\mp\gametypes\_gameobjects::setUseHintText( &"PLATFORM_HOLD_TO_PLANT_EXPLOSIVES" );
		
		if( !level.multiBomb )
			bombZone maps\mp\gametypes\_gameobjects::setKeyObject( level.sdBomb );
		
		label = bombZone maps\mp\gametypes\_gameobjects::getLabel();
		bombZone.label = label;
		bombZone maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_defend" + label );
		bombZone maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_defend" + label );
		bombZone maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_target" + label );
		bombZone maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_target" + label );
		bombZone maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
		setMLGIcons( bombZone, "waypoint_esports_snd_target" + label + "_white" );
		bombZone.onBeginUse = ::onBeginUse;
		bombZone.onEndUse = ::onEndUse;
		bombZone.noUseBar = true;
		bombZone.id = "bombZone";
		bombZone.onUse = ::onUsePlantObject;
		bombZone.onCantUse = ::onCantUse;
		bombZone.useWeapon = "search_dstry_bomb_mp";
		
		for ( i = 0; i < visuals.size; i++ )
		{
			if ( isDefined( visuals[i].script_exploder ) )
			{
				bombZone.exploderIndex = visuals[i].script_exploder;

				visuals[i] thread setupKillCamEnt( bombZone );
				break;
			}
		}
		
		level.bombZones[level.bombZones.size] = bombZone;
		
		bombZone.bombDefuseTrig = getent( visuals[0].target, "targetname" );
		assert( isdefined( bombZone.bombDefuseTrig ) );
		bombZone.bombDefuseTrig.origin += (0,0,-10000);
		bombZone.bombDefuseTrig.label = label;
		bombZone.bombDefuseTrig UseTriggerTouchCheckStance(true);
	}
	
	for( index = 0; index < level.bombZones.size; index++ )
	{
		array = [];
		for( otherindex = 0; otherindex < level.bombZones.size; otherindex++ )
		{
			if( otherindex != index )
				array[ array.size ] = level.bombZones[otherindex];
		}
		level.bombZones[index].otherBombZones = array;
	}

	SetOmnvar( "ui_mlg_game_mode_status_1", 0 );
}


removeBombZone( bombZone )
{
	brushModels = getEntArray( "script_brushmodel", "classname" );
	
	// remove collision
	foreach( brushModel in BrushModels )
	{
		// only consider bombzone brush models
		if( !isDefined(brushModel.script_gameobjectname) || (brushModel.script_gameobjectname != "bombzone") )
			continue;

		if( !isDefined(brushModel.script_label) )
			continue;
			
		// remove bombzone collision
		if( isSubStr(brushModel.script_label, bombZone.script_label) && isSubStr(brushModel.script_label, bombZone.targetname) )
		{
			brushModel delete();
		}					
	}	
	
	visuals = getEntArray( bombZone.target, "targetname" );
	
	// remove any targeted entities
	foreach( visual in visuals )
	{
		if( IsDefined(visual.target) )
		{
			targets = getEntArray( visual.target, "targetname" );
			
			foreach( target in targets )
				target delete();
		}
		
		visual delete();
	}
	
	bombZone delete();	
}



onUsePlantObject( player )
{
	// planted the bomb
	if ( !self maps\mp\gametypes\_gameobjects::isFriendlyTeam( player.pers["team"] ) )
	{
		level thread bombPlanted( self, player );
		
		// disable all bomb zones except this one
		for ( index = 0; index < level.bombZones.size; index++ )
		{
			if ( level.bombZones[index] == self )
			{
				label = level.bombZones[index] maps\mp\gametypes\_gameobjects::getLabel();
				setMLGIcons( level.bombZones[index], "waypoint_esports_snd_planted" + label );
				continue;
			}
				
			level.bombZones[index] maps\mp\gametypes\_gameobjects::disableObject();
			setMLGIcons( level.bombZones[index], undefined );
		}
		
		player playSound( "mp_bomb_plant" );
		player notify ( "bomb_planted" );
		
		player thread maps\mp\_events::bombPlantEvent();
		leaderDialog( "bomb_planted" );

		maps\mp\_utility::playSoundOnPlayers( game["bomb_planted_sound"], game["attackers"] );
		maps\mp\_utility::playSoundOnPlayers( game["bomb_planted_enemy_sound"], game["defenders"] );
		
		//	bomb carrier class
		if ( isDefined( level.sd_loadout ) && isDefined( level.sd_loadout[player.team] ) )
			player thread removeBombCarrierClass();	

		level.bombOwner = player;
		player.bombPlantedTime = getTime();	
	}
}

play_looping_beep_on_player( player )
{
	// Play the beep for everyone BUT this player
	players = array_remove( level.players, player );
	if ( players.size )
		player playLoopSoundToPlayers( "snd_bomb_button_press_lp", undefined, players );
}

stop_looping_beep_on_player( player )
{
	// Stop the looping beep
	player stop_loop_sound_on_entity( "snd_bomb_button_press_lp" );
}

setupKillCamEnt( bombZone ) // self == visuals[i]
{
	tempOrg = Spawn( "script_origin", self.origin );
	tempOrg.angles = self.angles;
	tempOrg RotateYaw( -45, 0.05 );
	
	wait( 0.05 );
	
	bulletStart = self.origin + ( 0, 0, 5 );
	bulletEnd = ( self.origin + ( AnglesToForward( tempOrg.angles ) * 100 ) ) + ( 0, 0, 128 );
	result = BulletTrace( bulletStart, bulletEnd, false, self );
	
	self.killCamEnt = Spawn( "script_model", result[ "position" ] );
	self.killCamEnt SetScriptMoverKillCam( "explosive" );
	
	bombZone.killCamEntNum = self.killCamEnt GetEntityNumber();
	tempOrg delete();
/#
	self.killCamEnt thread debugKillCamEnt( self );
#/		
}

onBeginUse( player )
{
	player AllowMelee( false );
	if( self maps\mp\gametypes\_gameobjects::isFriendlyTeam( player.pers["team"] ) )
	{
		if ( !level.silentPlant )
		{
			player notify_enemy_bots_bomb_used("defuse");
			player playSound( "mp_snd_bomb_disarming" );
			level thread play_looping_beep_on_player( player );
		}
		
		player.isDefusing = true;
		
		if ( isDefined( level.sdBombModel ) )
		{
			level.sdBombModel hide();
			stopfxontag( getfx( "bomb_light_planted" ), level.sdBombModel, "tag_flash" );
		}
	}
	else
	{
		if ( !level.silentPlant )
		{
			player notify_enemy_bots_bomb_used("plant");
			player playSound( "mp_snd_bomb_arming" );
			level thread play_looping_beep_on_player( player );
		}
		
		player.isPlanting = true;

		if( level.multibomb )
		{
			for( i = 0; i < self.otherBombZones.size; i++ )
			{
				self.otherBombZones[i] maps\mp\gametypes\_gameobjects::allowUse( "none" );
				self.otherBombZones[i] maps\mp\gametypes\_gameobjects::setVisibleTeam( "friendly" );
			}
		}
	}
}


onEndUse( team, player, result )
{
	if( !isDefined( player ) )
		return;
	
	player AllowMelee( true );
	if( isAlive( player ) )
	{
		player.isDefusing = false;
		player.isPlanting = false;
	}
	
	level thread stop_looping_beep_on_player( player );
	
	if ( self maps\mp\gametypes\_gameobjects::isFriendlyTeam( player.pers["team"] ) )
	{
		if ( isDefined( level.sdBombModel ) && !result )
		{
			level.sdBombModel show();
		}
	}
	else
	{
		if ( level.multibomb && !result )
		{
			for ( i = 0; i < self.otherBombZones.size; i++ )
			{
				self.otherBombZones[i] maps\mp\gametypes\_gameobjects::allowUse( "enemy" );
				self.otherBombZones[i] maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
			}
		}
	}
}

bombPlantedAnim(player)
{
	player endon("death");
	player endon("disconnect");
	
	player PlayerLinkTo(level.sdBombModel);
	player PlayerLinkedOffsetEnable();
	while(player GetCurrentWeapon() == self.useWeapon)
		waitframe();
	player Unlink();
}

bombPlanted( destroyedObj, player )
{
	level notify( "bomb_planted", destroyedObj );
	
	maps\mp\gametypes\_gamelogic::pauseTimer();
	level.bombPlanted = true;

	player.objective = 0;
	if( IsPlayer(player) )
		player SetClientOmnvar( "ui_carrying_bomb", false );

	destroyedObj.visuals[0] thread maps\mp\gametypes\_gamelogic::playTickingSound();
	level.tickingObject = destroyedObj.visuals[0];

	level.timeLimitOverride = true;
	level.defuseEndTime = int( gettime() + (level.bombTimer * 1000) );
	setGameEndTime( level.defuseEndTime );
	SetOmnvar( "ui_bomb_timer", 1 );
	
	if( !level.multiBomb )
	{
		level.sdBomb maps\mp\gametypes\_gameobjects::allowCarry( "none" );
		level.sdBomb maps\mp\gametypes\_gameobjects::setVisibleTeam( "none" );
		setMLGIcons( level.sdBomb, undefined );
		level.sdBomb maps\mp\gametypes\_gameobjects::setDropped();
		level.sdBombModel = level.sdBomb.visuals[0];
	}
	else
	{						
		level.sdBombModel = spawn( "script_model", player.origin );
		level.sdBombModel.angles = player.angles;
		level.sdBombModel setModel( "npc_search_dstry_bomb" );
	}
	
	level.sdBombModel thread invalidateCarePackageAndGoliathDrop();
	//play blinking light vfx
	playfxontag( getfx( "bomb_light_planted" ), level.sdBombModel, "tag_flash" );
	destroyedObj maps\mp\gametypes\_gameobjects::allowUse( "none" );
	destroyedObj maps\mp\gametypes\_gameobjects::setVisibleTeam( "none" );
	label = destroyedObj maps\mp\gametypes\_gameobjects::getLabel();
	
	destroyedObj thread bombPlantedAnim(player);
	
	// create a new object to defuse with.
	trigger = destroyedObj.bombDefuseTrig;
	trigger.origin = level.sdBombModel.origin;
	visuals = [];
	defuseObject = maps\mp\gametypes\_gameobjects::createUseObject( game["defenders"], trigger, visuals, (0,0,32) );
	defuseObject maps\mp\gametypes\_gameobjects::allowUse( "friendly" );
	defuseObject maps\mp\gametypes\_gameobjects::setUseTime( level.defuseTime );
	defuseObject maps\mp\gametypes\_gameobjects::setUseHintText( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" );
	defuseObject maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
	defuseObject maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_defuse" + label );
	defuseObject maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_defend" + label );
	defuseObject maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_defuse" + label );
	defuseObject maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_defend" + label );
	//setMLGIcons( defuseObject, "waypoint_esports_snd_planted" + label ); // we're moving the MLG icon to the bomb site instead of the bomb itself
	defuseObject.label = label;
	defuseObject.onBeginUse = ::onBeginUse;
	defuseObject.onEndUse = ::onEndUse;
	defuseObject.onUse = ::onUseDefuseObject;
	defuseObject.noUseBar = true;
	defuseObject.id = "defuseObject";
	defuseObject.useWeapon = "search_dstry_bomb_defuse_mp";

	if ( label == "_a" || label == "_A" )
		SetOmnvar( "ui_mlg_game_mode_status_1", 1 );
	else if ( label == "_b" || label == "_B" )
		SetOmnvar( "ui_mlg_game_mode_status_1", 2 );
	
	playSoundinSpace( "mp_snd_bomb_planted", level.sdBombModel.origin + (0,0,1) );

	BombTimerWait();
	SetOmnvar( "ui_bomb_timer", 0 );
	
	destroyedObj.visuals[0] maps\mp\gametypes\_gamelogic::stopTickingSound();
	
	if( level.gameEnded || level.bombDefused )
		return;
	
	level.bombExploded = true;
	SetOmnvar( "ui_mlg_game_mode_status_1", 0 );
	
	//	allow for map specific custom event
	if( isDefined( level.sd_onBombTimerEnd ) )
	{
		level thread [[level.sd_onBombTimerEnd]]();
	}
	else
	{
		explosionOrigin = level.sdBombModel.origin;
		explosionOrigin += (0,0,10); //Get the explosion origin out of the ground.
		level.sdBombModel hide();
		stopfxontag( getfx( "bomb_light_planted" ), level.sdBombModel, "tag_flash" );
		
		if( isdefined( player ) )
		{
			destroyedObj.visuals[0] RadiusDamage( explosionOrigin, 512, 300, 20, player, "MOD_EXPLOSIVE", "bomb_site_mp" );
			
			player thread maps\mp\_events::bombDetonateEvent();		
		}
		else
		{
			destroyedObj.visuals[0] RadiusDamage( explosionOrigin, 512, 300, 20, undefined, "MOD_EXPLOSIVE", "bomb_site_mp" );
		}
		
		effect = "bomb_explosion";	
		if( isDefined( destroyedObj.trigger.effect ) )
			effect = destroyedObj.trigger.effect;
		
		rot = randomfloat(360);
		explosionPos = explosionOrigin + (0,0,50);		
		explosionEffect = spawnFx( level._effect[effect], explosionPos + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) );
		triggerFx( explosionEffect );
		PhysicsExplosionSphere( explosionPos, 200, 100, 3 );
	
		PlayRumbleOnPosition( "grenade_rumble", explosionOrigin );
		earthquake( 0.75, 2.0, explosionOrigin, 2000 );
		
		thread playSoundinSpace( "mp_snd_bomb_detonated", explosionOrigin );
		
		if ( isDefined( destroyedObj.exploderIndex ) )
			common_scripts\_exploder::exploder( destroyedObj.exploderIndex );
	}
		
	for( index = 0; index < level.bombZones.size; index++ )
		level.bombZones[index] maps\mp\gametypes\_gameobjects::disableObject();
	
	defuseObject maps\mp\gametypes\_gameobjects::disableObject();

	setGameEndTime( 0 );	
	wait( 3 );
	
	sd_endGame( game["attackers"], game[ "end_reason" ][ "target_destroyed" ] );
}

invalidateCarePackageAndGoliathDrop()
{
	// Fix for BS-110219 
	bombmodel = self;
	trigger_radius = 64;
	trigger_height = 64;
	trigger_origin = bombmodel.origin + (0,0,-4);
	
	BadZoneTrigger = spawn("trigger_radius", trigger_origin, 0, trigger_radius, trigger_height);
	BadZoneTrigger.targetname = "orbital_node_covered";
	BadZoneTrigger.script_noteworthy = "dont_move_me"; // for mp_laser2.gsc to not move triggers during dynamic event

	if(!IsDefined(level.orbital_util_covered_volumes))
	{
		level.orbital_util_covered_volumes = [];
	}
	FoundTrigger = false;
	if(level.orbital_util_covered_volumes.size > 0)
	{
		foreach(trigger in level.orbital_util_covered_volumes)
		{
			if(trigger == BadZoneTrigger)
			{
				FoundTrigger = true;
				break;
			}
		}
	}
	if(FoundTrigger == false)
	{	
		level.orbital_util_covered_volumes[level.orbital_util_covered_volumes.size] = BadZoneTrigger;
	}
	
	
	//including goliath in here so it also does not cause any bugs
	if(!IsDefined(level.goliath_bad_landing_volumes))
	{
		level.goliath_bad_landing_volumes = [];
	}
	FoundGoliathTrigger = false;
	if(level.goliath_bad_landing_volumes.size > 0)
	{
		foreach(trigger in level.goliath_bad_landing_volumes)
		{
			if(trigger == BadZoneTrigger)
			{
				FoundGoliathTrigger = true;
				break;
			}
		}
	}
	if(FoundGoliathTrigger == false)
	{	
		level.goliath_bad_landing_volumes[level.goliath_bad_landing_volumes.size] = BadZoneTrigger;
	}
	
	// set all nodes in this area invalid for remote missile spawns
	nodes = GetNodesInRadiusSorted( trigger_origin , trigger_radius, 0, trigger_height );
	if ( nodes.size > 0 )
	{
		foreach(node in nodes)
		{
			NodeSetRemoteMissileName( node, "none" );
		}
	}
	
	// removing created ents and setting nodes back to useable state
	waittill_any("bomb_defused", "game_ended");
	{
		level.goliath_bad_landing_volumes = array_remove(level.goliath_bad_landing_volumes, BadZoneTrigger);
		level.goliath_bad_landing_volumes = array_remove_duplicates(level.goliath_bad_landing_volumes);
		
		level.orbital_util_covered_volumes = array_remove(level.orbital_util_covered_volumes, BadZoneTrigger);
		level.orbital_util_covered_volumes = array_remove_duplicates(level.orbital_util_covered_volumes);
		
		BadZoneTrigger delete();

		if ( nodes.size > 0 )
		{
			foreach(node in nodes)
			{
				NodeSetRemoteMissileName( node, "up" );
			}
		}
		return;
	}
}


BombTimerWait()
{
	level endon( "game_ended" );
	level endon( "bomb_defused" );

	bombEndMilliseconds = int( ( level.bombTimer * 1000 ) + gettime() );
	SetOmnvar( "ui_bomb_timer_endtime", bombEndMilliseconds );

	level thread handleHostMigration( bombEndMilliseconds );
	maps\mp\gametypes\_hostmigration::waitLongDurationWithGameEndTimeUpdate( level.bombTimer );
}


handleHostMigration( bombEndMilliseconds )
{
	level endon( "game_ended" );
	level endon( "bomb_defused" );
	level endon( "game_ended" );
	level endon( "disconnect" );

	level waittill( "host_migration_begin" );

	SetOmnvar ( "ui_bomb_timer_endtime", 0 );	

	timePassed = maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();

	if ( timePassed > 0 )
	{
		SetOmnvar( "ui_bomb_timer_endtime", bombEndMilliseconds + timePassed );
	}
}


onDrop( player )
{
	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_bomb" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_bomb" );
	
	maps\mp\_utility::playSoundOnPlayers( game["bomb_dropped_sound"], game["attackers"] );
	maps\mp\_utility::playSoundOnPlayers( game["bomb_dropped_enemy_sound"], game["defenders"] );
}


canUse( player )
{
	if ( !isDefined( player ) )
		return false;
	
	if ( player isJuggernaut() )
		return false;
	
	return true;
}


goliathDropBomb()		// self = player
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "joined_team" );
	self endon( "faux_spawn" );
	
	self waittill( "goliath_equipped" );
	
	if ( IsDefined( self.carryObject ) )
	{
		self.isBombCarrier = false;
		self.carryObject thread maps\mp\gametypes\_gameobjects::setDropped();
	}
}


onUseDefuseObject( player )
{
	player notify ( "bomb_defused" );
	
	level thread bombDefused();
	
	// disable this bomb zone
	self maps\mp\gametypes\_gameobjects::disableObject();
	leaderDialog( "bomb_defused_attackers", game["attackers"] );
	leaderDialog( "bomb_defused_defenders", game["defenders"] );
	
	maps\mp\_utility::playSoundOnPlayers( game["bomb_disarm_enemy_sound"], game["attackers"] );
	maps\mp\_utility::playSoundOnPlayers( game["bomb_disarm_sound"], game["defenders"] );
	
	defuseType = "defuse";

	if( isDefined(level.bombOwner) && isReallyAlive( level.bombOwner ) && ( (level.bombOwner.bombPlantedTime + 6000 + (level.defuseTime*1000)) > getTime() ) )
		defuseType = "ninja_defuse";
	
	if( isLastPlayerAlive( player ) )
		defuseType = "last_man_defuse";
	
	player thread maps\mp\_events::bombDefuseEvent( defuseType );
}

isLastPlayerAlive( alivePlayer )
{
	foreach( player in level.players )
	{
		if( player == alivePlayer )
			continue;
		
		if( player maps\mp\gametypes\_playerlogic::maySpawn() )
			return false;
		
		if( isReallyAlive(player) )
			return false;
	}
	
	return true;
}

bombDefused()
{
	level.tickingObject maps\mp\gametypes\_gamelogic::stopTickingSound();
	level.bombDefused = true;
	SetOmnvar( "ui_bomb_timer", 0 );
	SetOmnvar( "ui_mlg_game_mode_status_1", 0 );
	
	level notify("bomb_defused");
	
	wait 1.5;
	
	setGameEndTime( 0 );
	
	sd_endGame( game["defenders"], game[ "end_reason" ][ "bomb_defused" ] );
}


onCantUse( player )
{
	player iPrintLnBold( &"MP_CANT_PLANT_WITHOUT_BOMB" );
}


onTimeLimit()
{
	sd_endGame( game["defenders"], game[ "end_reason" ][ "time_limit_reached" ] );
}


sd_endGame( winningTeam, endReasonText )
{
	level.finalKillCam_winner = winningTeam;
	
	// don't show the final killcam if the game ends with the bomb blowing up or bomb being defused, unless the bomb kills someone
	if( endReasonText == game[ "end_reason" ][ "target_destroyed" ] || endReasonText == game[ "end_reason" ][ "bomb_defused" ] )
	{
		eraseKillCam = true;
		foreach( bombZone in level.bombZones )
		{
			if(	IsDefined( level.finalKillCam_killCamEntityIndex[ winningTeam ] ) && level.finalKillCam_killCamEntityIndex[ winningTeam ] == bombZone.killCamEntNum )
			{
				eraseKillCam = false;
				break;
			}
		}

		if( eraseKillCam )
			maps\mp\gametypes\_damage::eraseFinalKillCam();
	}

	self maps\mp\gametypes\_gamescore::giveTeamScoreForObjective( winningTeam, 1 );
	thread maps\mp\gametypes\_gamelogic::endGame( winningTeam, endReasonText );
}


checkAllowSpectating()
{
	wait( 0.05 );
	
	update = false;
	if( !level.aliveCount[ game["attackers"] ] )
	{
		level.spectateOverride[game["attackers"]].allowEnemySpectate = 1;
		update = true;
	}
	
	if( !level.aliveCount[ game["defenders"] ] )
	{
		level.spectateOverride[game["defenders"]].allowEnemySpectate = 1;
		update = true;
	}
	
	if( update )
		maps\mp\gametypes\_spectating::updateSpectateSettings();
}


onDeadEvent( team )
{
	if( level.bombExploded || level.bombDefused )
		return;
	
	if( team == "all" )
	{
		if( level.bombPlanted )
		{
			sd_endGame( game["attackers"], game[ "end_reason" ][ game[ "defenders" ]+"_eliminated" ] );
		}
		else
		{
			sd_endGame( game["defenders"], game[ "end_reason" ][ game[ "attackers" ]+"_eliminated" ] );
		}
	}
	else if( team == game["attackers"] )
	{
		if ( level.bombPlanted )
			return;

		level thread sd_endGame( game["defenders"], game[ "end_reason" ][ game[ "attackers" ]+"_eliminated" ] );
	}
	else if( team == game["defenders"] )
	{
		level thread sd_endGame( game["attackers"], game[ "end_reason" ][ game[ "defenders" ]+"_eliminated" ] );
	}
}


onOneLeftEvent( team )
{
	if( level.bombExploded || level.bombDefused )
		return;

	lastPlayer = getLastLivingPlayer( team );

	lastPlayer thread giveLastOnTeamWarning();
}


giveLastOnTeamWarning()
{
	self endon("death");
	self endon("disconnect");
	level endon( "game_ended" );
		
	self waitTillRecoveredHealth( 3 );
	
	otherTeam = getOtherTeam( self.pers["team"] );
	level thread teamPlayerCardSplash( "callout_lastteammemberalive", self, self.pers["team"] );
	level thread teamPlayerCardSplash( "callout_lastenemyalive", self, otherTeam );
	level notify ( "last_alive", self );	
	self maps\mp\gametypes\_missions::lastManSD();
}


onReset()
{
	
}


applyBombCarrierClass()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	//	remove placement item if carrying
	if ( IsDefined( self.isCarrying ) && self.isCarrying == true )
	{
		self notify( "force_cancel_placement" );
		wait( 0.05 );
	}			
	
	//	not while mantling
	while ( self IsMantling() )
		wait( 0.05 );	
	
	//	not while in air
	while ( !self isOnGround() )
		wait( 0.05 );
	
	//	remove jugg
	if ( self isJuggernaut() )
	{
		self notify( "lost_juggernaut" );
		wait( 0.05 );
	}	
	
	//	set the gamemodeloadout for giveLoadout() to use
	self.pers["gamemodeLoadout"] = level.sd_loadout[self.team];	
	
	//	spawnPlayer() calls giveLoadout() passing the player's class
	//	save their chosen class and override their current and last class 
	//	- both so killstreaks don't get reset
	//	- this is automatically set back to chosen class within giveLoadout()
	self.gamemode_chosenClass = self.class;				
	self.gamemode_carrierClass = true;
	self.pers["class"] = "gamemode";
	self.pers["lastClass"] = "gamemode";
	self.class = "gamemode";
	self.lastClass = "gamemode";
	
	//	faux spawn
	self notify( "faux_spawn" );
	self maps\mp\gametypes\_class::giveAndApplyLoadout( self.team, "gamemode" );
	if ( self.loadoutKeepCurrentKillstreaks )
		self maps\mp\killstreaks\_killstreaks::updateKillstreaks( true );
	self refillBattery();
}

refillBattery()
{
	offhandWeapons = self getWeaponsListOffhands();
	
	foreach( offhand in offhandWeapons )
		self BatteryFullRecharge( offhand );
}

removeBombCarrierClass()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	//	remove placement item if carrying
	if ( IsDefined( self.isCarrying ) && self.isCarrying == true )
	{
		self notify( "force_cancel_placement" );
		wait( 0.05 );
	}			
	
	//	not while mantling
	while ( self IsMantling() )
		wait( 0.05 );	
	
	//	not while in air
	while ( !self isOnGround() )
		wait( 0.05 );
	
	//	remove jugg
	if ( self isJuggernaut() )
	{
		self notify( "lost_juggernaut" );
		wait( 0.05 );
	}	
	
	//	unset the gamemodeloadout
	self.pers["gamemodeLoadout"] = undefined;	
	
	//	faux spawn
	self notify( "faux_spawn" );
	self maps\mp\gametypes\_class::giveAndApplyLoadout( self.team, self.class );
	if ( self.loadoutKeepCurrentKillstreaks )
		self maps\mp\killstreaks\_killstreaks::updateKillstreaks( true );
	self refillBattery();
}


/#
debugKillCamEnt( visual ) // self == visuals[i].killCamEnt
{
	self endon( "death" );
	level endon( "game_ended" );
	visual endon( "death" );

	while( true )
	{
		if( GetDvarInt( "scr_sd_debugBombKillCamEnt" ) > 0 )
		{
			// show the kill cam ent
			Line( self.origin, self.origin + ( AnglesToForward( self.angles ) * 10 ), ( 1, 0, 0 ) );
			Line( self.origin, self.origin + ( AnglesToRight( self.angles ) * 10 ), ( 0, 1, 0 ) );
			Line( self.origin, self.origin + ( AnglesToUp( self.angles ) * 10 ), ( 0, 0, 1 ) );

			// line from bomb site to kill cam ent
			Line( visual.origin + ( 0, 0, 5 ), self.origin, ( 0, 0, 1 ) );

			// bomb site angles
			Line( visual.origin, visual.origin + ( AnglesToForward( visual.angles ) * 10 ), ( 1, 0, 0 ) );
			Line( visual.origin, visual.origin + ( AnglesToRight( visual.angles ) * 10 ), ( 0, 1, 0 ) );
			Line( visual.origin, visual.origin + ( AnglesToUp( visual.angles ) * 10 ), ( 0, 0, 1 ) );
		}
		wait( 0.05 );
	}
}
#/