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

init()
{
	if ( !IsDefined( level.placeableConfigs ) )
	{
		level.placeableConfigs = [];
	}
}

givePlaceable( streakName ) // self == player
{
	placeable = self createPlaceable( streakName );
	
	//	returning from this streak activation seems to strip this?
	//	manually removing and restoring
	self removePerks();
	
	self.carriedItem = placeable;
	
	result = self onBeginCarrying( streakName, placeable, true );
	
	self.carriedItem = undefined;
	
	self restorePerks();

	// if the placeable exist, then it was placed
	return ( IsDefined( placeable ) );
}

createPlaceable( streakName )
{
	if( IsDefined( self.isCarrying ) && self.isCarrying )
		return;
	
	config = level.placeableConfigs[ streakName ];
	
	obj = Spawn( "script_model", self.origin );
	obj SetModel( config.modelBase );
	obj.angles = self.angles;
	obj.owner = self;
	obj.team = self.team;
	obj.config = config;
	obj.firstPlacement = true;

	/*
	obj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );

	obj.angles = self.angles;
	obj.owner = self;

	obj SetModel( config.modelBase );

	obj MakeTurretInoperable();
	obj SetTurretModeChangeWait( true );
	obj SetMode( "sentry_offline" );
	obj MakeUnusable();
	obj SetSentryOwner( self );
	*/
	
	// inits happen here
	if ( IsDefined( config.onCreateDelegate ) )
	{
		obj [[ config.onCreateDelegate ]]( streakName );
	}
	
	obj deactivate( streakName );
	
	obj thread timeOut( streakName );
	obj thread handleUse( streakName );
	
	obj thread onKillstreakDisowned( streakName );
	obj thread onGameEnded( streakName );
	
	obj thread createBombSquadModel( streakName );

	return obj;	
}


handleUse( streakName ) // self == placeable
{
	self endon ( "death" );
	level endon ( "game_ended" );
	
	while ( true )
	{
		self waittill ( "trigger", player );
		
		assert( player == self.owner );
		assert( !IsDefined( self.carriedBy ) );

		if ( !isReallyAlive( player ) )
			continue;
		
		if ( IsDefined( self GetLinkedParent() ) )
		{
			self Unlink();
		}
		
		// why does the IMS create a second one?
		
		player onBeginCarrying( streakName, self, false );
	}
}

// setCarrying
onBeginCarrying( streakName, placeable, allowCancel )	// self == player
{
	self endon ( "death" );
	self endon ( "disconnect" );
	
	assert( isReallyAlive( self ) );
	
	placeable thread onCarried( streakName, self );
	
	self _disableWeapon();

	if ( !IsAI(self) )		// Bots handle these internally
	{
		self notifyOnPlayerCommand( "placePlaceable", "+attack" );
		self notifyOnPlayerCommand( "placePlaceable", "+attack_akimbo_accessible" ); // support accessibility control scheme
		self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 4" );
		if( !level.console )
		{
			self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 5" );
			self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 6" );
			self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 7" );
			self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 8" );
		}
	}
	
	while (true)
	{
		result = waittill_any_return( "placePlaceable", "cancelPlaceable", "force_cancel_placement" );

		// object was deleted
		if ( !IsDefined( placeable ) )
		{
			self _enableWeapon();
			return true;
		}
		// !!! 2013-08-08 wallace: this force cancel problem is really ugly
		// it's also being used to indicate player wading in water, which we should use the "bad placement" model instead of canceling outright. But it's too late to fix now.
		else if ( (result == "cancelPlaceable" && allowCancel)
			  || result == "force_cancel_placement" )
		{
			placeable onCancel( streakName, result == "force_cancel_placement" && !IsDefined( placeable.firstPlacement ) );
			return false;
		}
		else if ( placeable.canBePlaced )
		{
			placeable thread onPlaced( streakName );
			self _enableWeapon();
			return true;
		}
	}
}

onCancel( streakName, playDestroyVfx )	// self == placeable
{
	if( IsDefined( self.carriedBy ) )
	{
		owner = self.carriedBy;
		owner ForceUseHintOff();
		owner.isCarrying = undefined;
		
		owner.carriedItem = undefined;
	
		owner _enableWeapon();
	}
	
	if( IsDefined( self.bombSquadModel ) )
	{
		self.bombSquadModel Delete();
	}
	
	if ( IsDefined( self.carriedObj ) )
	{
		self.carriedObj Delete();
	}
	
	config = level.placeableConfigs[ streakName ];
	if ( IsDefined( config.onCancelDelegate ) )
	{
		self [[ config.onCancelDelegate ]]( streakName );
	}

	if ( IsDefined( playDestroyVfx ) && playDestroyVfx )
	{
		self maps\mp\gametypes\_weapons::equipmentDeleteVfx();
	}
	
	self Delete();
}

onPlaced( streakName )	// self == placeable
{
	config = level.placeableConfigs[ streakName ];
	
	self.origin = self.placementOrigin;
	self.angles = self.carriedObj.angles;
	
	self PlaySound( config.placedSfx );
	
	self showPlacedModel( streakName );
	
	if ( IsDefined( config.onPlacedDelegate ) )
	{
		self [[ config.onPlacedDelegate ]]( streakName );
	}
	
	self setCursorHint( "HINT_NOICON" );
	self setHintString( config.hintString );

	owner = self.owner;
	owner ForceUseHintOff();
	owner.isCarrying = undefined;
	self.carriedBy = undefined;
	self.isPlaced = true;
	self.firstPlacement = undefined;

	if ( IsDefined( config.headIconHeight ) )
	{
		if ( level.teamBased )
		{
			self maps\mp\_entityheadicons::setTeamHeadIcon( self.team, (0,0,config.headIconHeight) );
		}
		else
		{
			self maps\mp\_entityheadicons::setPlayerHeadIcon( owner, (0,0,config.headIconHeight) );
		}
	}
	
	self thread handleDamage( streakName );
	self thread handleDeath( streakName );

	self MakeUsable();
	
	self make_entity_sentient_mp( self.owner.team );
	if ( IsSentient( self ) )
	{
		self SetThreatBiasGroup( "DogsDontAttack" );
	}

	foreach ( player in level.players )
	{
		if( player == owner )
			self EnablePlayerUse( player );
		else
			self DisablePlayerUse( player );
	}

	if( IsDefined( self.shouldSplash ) )
	{
		level thread teamPlayerCardSplash( config.splashName, owner );
		self.shouldSplash = false;
	}
	
	// Moving platforms. 
	data = SpawnStruct();
	data.linkParent = self.moving_platform;
	data.playDeathFx = true;
	data.endonString = "carried";
	if ( IsDefined( config.onMovingPlatformCollision ) )
	{
		data.deathOverrideCallback = config.onMovingPlatformCollision;
	}
	self thread maps\mp\_movers::handle_moving_platforms( data );
	
	self thread watchPlayerConnected();
	
	self notify ( "placed" );
	
	self.carriedObj Delete();
	self.carriedObj = undefined;
}

onCarried( streakName, carrier )	// self == placeable
{
	config = level.placeableConfigs[ streakName ];
	
	assert( isPlayer( carrier ) );
	assertEx( carrier == self.owner, "_placeable::onCarried: specified carrier does not own this ims" );
	
	self.carriedObj = carrier createCarriedObject( streakName );
	
	// self SetModel( config.modelPlacement );

	self.isPlaced = undefined;
	self.carriedBy = carrier;
	carrier.isCarrying = true;
	
	self deactivate( streakName );
	
	self hidePlacedModel( streakName );
	
	if ( IsDefined( config.onCarriedDelegate ) )
	{
		self [[ config.onCarriedDelegate ]]( streakName );
	}
	
	self thread updatePlacement( streakName, carrier );
	
	self thread onCarrierDeath( streakName, carrier );
	
	self notify ( "carried" );
}

updatePlacement( streakName, carrier )	// self == placeable
{
	carrier endon ( "death" );
	carrier endon ( "disconnect" );
	level endon ( "game_ended" );
	
	self endon ( "placed" );
	self endon ( "death" );
	
	self.canBePlaced = true;
	prevCanBePlaced = -1; // force initial update
	
	config = level.placeableConfigs[ streakName ];
	
	// allow the visuals be raised up slightly (e.g. iw6 Sat Com, so that player can see it)
	placementOffset = (0 ,0, 0);
	if ( IsDefined( config.placementOffsetZ ) )
	{
		placementOffset = (0 ,0 ,config.placementOffsetZ);
	}
	
	carriedObj = self.carriedObj;

	while ( true )
	{
		placement = carrier CanPlayerPlaceSentry( true, config.placementRadius );

		// NOTE TO SELF: Talk to Simon C about how to get vertical offset / additional rotation working with client prediction
		self.placementOrigin = placement[ "origin" ];
		carriedObj.origin = self.placementOrigin + placementOffset;
		carriedObj.angles = placement[ "angles" ];
		
		self.canBePlaced = carrier IsOnGround() 
			&& placement[ "result" ] 
			&& ( abs(self.placementOrigin[2] - carrier.origin[2]) < config.placementHeightTolerance );
		
		if ( isdefined( placement[ "entity" ] ) )
		{
			self.moving_platform = placement[ "entity" ];
		}
		else 
		{
			self.moving_platform = undefined;
		}
	
		if ( self.canBePlaced != prevCanBePlaced )
		{
			if ( self.canBePlaced )
			{
				carriedObj SetModel( config.modelPlacement );
				carrier ForceUseHintOn( config.placeString );
			}
			else
			{
				carriedObj SetModel( config.modelPlacementFailed );
				carrier ForceUseHintOn( config.cannotPlaceString );
			}
		}
		
		prevCanBePlaced = self.canBePlaced;		
		wait ( 0.05 );
	}
}

deactivate( streakName )	// self == placeable
{
	self MakeUnusable();

	self hideHeadIcons();
	
	self FreeEntitySentient();
	
	config = level.placeableConfigs[ streakName ];
	if ( IsDefined( config.onDeactiveDelegate ) )
	{
		self [[ config.onDeactiveDelegate ]]( streakName );
	}
}

hideHeadIcons()
{
	if ( level.teamBased )
	{
		self maps\mp\_entityheadicons::setTeamHeadIcon( "none", ( 0, 0, 0 ) );
	}
	else if ( IsDefined( self.owner ) )
	{
		self maps\mp\_entityheadicons::setPlayerHeadIcon( undefined, ( 0, 0, 0 ) );
	}	
}

// important callbacks:
// onDamagedDelegate - filter out or amplify damage based on specifics
// onDestroyedDelegate - any extra handling when the object is killed
handleDamage( streakName ) // self == placeable
{
	self endon( "carried" );
	
	config = level.placeableConfigs[ streakName ];
	
	self maps\mp\gametypes\_damage::setEntityDamageCallback(
		config.maxHealth,
		config.damageFeedback,
		::handleDeathDamage,
		::modifyDamage,
		true	// isKillstreak
	);
}

modifyDamage( attacker, weapon, type, damage )
{
	modifiedDamage = damage;
	
	config = self.config;
	if ( IsDefined( config.allowMeleeDamage ) && config.allowMeleeDamage )
	{
		modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
	}
	
	if ( IsDefined( config.allowEmpDamage ) && config.allowEmpDamage )
	{
		modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( weapon, type, modifiedDamage, attacker );
	}
	modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
	modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage );
	modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
	
	if ( IsDefined( config.modifyDamage ) )
	{
		modifiedDamage = self [[ config.modifyDamage ]]( weapon, type, modifiedDamage );
	}
	
	return modifiedDamage;
}

handleDeathDamage( attacker, weapon, type, damage )
{
	config = self.config;
	
	self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, config.xpPopup, config.destroyedVO );
}

handleDeath( streakName )
{
	self endon( "carried" );
	
	self waittill ( "death" );
	
	config = level.placeableConfigs[ streakName ];
	
	// this handles cases of deletion
	if ( IsDefined( self ) )
	{	
		// play sound
		
		self deactivate( streakName );
		
		// set destroyed model
		if ( IsDefined( config.modelDestroyed ) )
		{
			self SetModel( config.modelDestroyed );
		}
		
		// or do it in the callbacks?
		
		if ( IsDefined( config.onDeathDelegate ) )
		{
			self [[ config.onDeathDelegate ]]( streakName );
		}
		
		self Delete();
	}
}

//--------------------------------------------------------------------
onCarrierDeath( streakName, carrier ) // self == placeable
{
	self endon ( "placed" );
	self endon ( "death" );
	carrier endon( "disconnect" );

	carrier waittill ( "death" );
	
	if ( self.canBePlaced )
	{
		self thread onPlaced( streakName );
	}
	else
	{
		self onCancel( streakName );
	}
}


onKillstreakDisowned( streakName ) // self == placeable
{
	self endon ( "death" );
	level endon ( "game_ended" );

	self.owner waittill ( "killstreak_disowned" );
	
	self cleanup( streakName );
}

onGameEnded( streakName ) // self == placeable
{
	self endon ( "death" );

	level waittill ( "game_ended" );
	
	self cleanup( streakName );
}

cleanup( streakName ) 	// self == placeable
{
	if ( IsDefined( self.isPlaced ) )
	{
		self notify( "death" );
	}
	else
	{
		self onCancel( streakName );
	}
}

watchPlayerConnected() // self == ims
{
	self endon( "death" );

	while( true )
	{
		// when new players connect they need to not be able to use the planted ims
		level waittill( "connected", player );
		self thread onPlayerConnected( player );
	}
}

onPlayerConnected( owner ) // self == placeable
{
	self endon( "death" );
	owner endon( "disconnect" );

	owner waittill( "spawned_player" );

	// this can't possibly be the owner because the ims is destroyed if the owner leaves the game, so disable use for this player
	self DisablePlayerUse( owner );
}

timeOut( streakName )
{
	self endon( "death" );
	level endon ( "game_ended" );
	
	config = level.placeableConfigs[ streakName ];
	lifeSpan = config.lifeSpan;
	
	while ( lifeSpan > 0.0 )
	{
		wait ( 1.0 );
		maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
		
		if ( !IsDefined( self.carriedBy ) )
		{
			lifeSpan -= 1.0;
		}
	}
	
	if ( IsDefined( self.owner ) && IsDefined( config.goneVO ) )
	{
		self.owner thread leaderDialogOnPlayer( config.goneVO );
	}
	
	self notify ( "death" );
}

//--------------------------------------------------------------------
removeWeapons()
{
	if ( self HasWeapon( "iw6_riotshield_mp" ) )
	{
		self.restoreWeapon = "iw6_riotshield_mp";
		self takeWeapon( "iw6_riotshield_mp" );
	}	
}

removePerks()
{
	if ( self _hasPerk( "specialty_explosivebullets" ) )
	{
		self.restorePerk = "specialty_explosivebullets";
		self _unsetPerk( "specialty_explosivebullets" );
	}		
}

restoreWeapons()
{
	if ( IsDefined( self.restoreWeapon ) )	
	{
		self _giveWeapon( self.restoreWeapon );
		self.restoreWeapon = undefined;
	}	
}

restorePerks()
{
	if ( IsDefined( self.restorePerk ) )
	{
		self givePerk( self.restorePerk, false );	
		self.restorePerk = undefined;
	}	
}

createBombSquadModel( streakName ) // self == box
{
	config = level.placeableConfigs[ streakName ];
	
	if ( IsDefined( config.modelBombSquad ) )
	{
		bombSquadModel = Spawn( "script_model", self.origin );
		bombSquadModel.angles = self.angles;
		bombSquadModel Hide();
	
		bombSquadModel thread maps\mp\gametypes\_weapons::bombSquadVisibilityUpdater( self.owner );
		bombSquadModel SetModel( config.modelBombSquad );
		bombSquadModel LinkTo( self );
		bombSquadModel SetContents( 0 );
		self.bombSquadModel = bombSquadModel;
	
		self waittill ( "death" );
	
		// Could have been deleted when the player was carrying the ims and died
		if ( IsDefined( bombSquadModel ) )
		{
			bombSquadModel delete();
			self.bombSquadModel = undefined;
		}
	}
}

showPlacedModel( streakname )
{
	self Show();
	
	if ( IsDefined( self.bombSquadModel ) )
	{
		self.bombSquadModel Show();
		level notify( "update_bombsquad" );
	}
}

hidePlacedModel( streakName )
{
	self Hide();
	
	if ( IsDefined( self.bombSquadModel ) )
	{
		self.bombSquadModel Hide();
	}
}

createCarriedObject( streakName )
{
	assertEx( IsDefined( self ), "createIMSForPlayer() called without owner specified" );
		
	// need to make sure we aren't already carrying, this fixes a bug where you could start to pull a new one out as you picked the old one up
	//	this resulted in you being able to plant one and then pull your gun out while having another one attached to you like you're carrying it
	if( IsDefined( self.isCarrying ) && self.isCarrying )
		return;
	
	carriedObj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );

	carriedObj.angles = self.angles;
	carriedObj.owner = self;
	
	config = level.placeableConfigs[ streakName ];
	carriedObj SetModel( config.modelBase );

	carriedObj MakeTurretInoperable();
	carriedObj SetTurretModeChangeWait( true );
	carriedObj SetMode( "sentry_offline" );
	carriedObj MakeUnusable();
	carriedObj SetSentryOwner( self );
	carriedObj SetSentryCarrier( self );
	
	carriedObj SetCanDamage( false );
	carriedObj SetContents( 0 );

	return carriedObj;
}