enum EMissileRainPhase
{
	Init = 0,
	Phase1 = 1,
	Phase2 = 2,
}

class RainMissileProjectile extends BaseProjectile
{
	private var m_meshComponent : IComponent;
	private editable var m_effect : EffectRef;
	private var m_damage : EffectInstance;
	private var m_owner : weak< GameObject >;
	private var m_weapon : weak< WeaponObject >;
	private var m_countTime : Float;
	default m_countTime = 0;
	private editable var m_startVelocity : Float;
	private editable var m_lifetime : Float;
	private var m_alive : Bool;
	default m_alive = true;
	private var m_arrived : Bool;
	default m_arrived = false;
	private var m_spawnPosition : Vector4;
	private var m_phase1Duration : Float;
	private editable var m_landIndicatorFX : FxResource;
	private var m_fxInstance : FxInstance;
	private var m_armingDistance : Float;
	private var m_armed : Bool;
	private var m_hasExploded : Bool;
	default m_hasExploded = false;
	private var m_missileDBID : TweakDBID;
	private var m_missileAttackRecord : weak< Attack_Record >;
	private var m_timeToDestory : Float;
	default m_timeToDestory = -1.f;
	private var m_initialTargetPosition : Vector4;
	private var m_initialTargetOffset : Vector4;
	private var m_finalTargetPosition : Vector4;
	private var m_finalTargetOffset : Vector4;
	private var m_finalTargetPositionCalculationDelay : Float;
	private var m_targetComponent : weak< IPlacedComponent >;
	private var m_followTargetInPhase2 : Bool;
	private var m_puppetBroadphaseHitRadiusSquared : Float;
	private var m_phase : EMissileRainPhase;
	private var m_spiralParams : SpiralControllerParams;
	private var m_useSpiralParams : Bool;
	private var m_randStartVelocity : Float;

	protected event OnRequestComponents( ri : EntityRequestComponentsInterface )
	{
		super.OnRequestComponents( ri );
		EntityRequestComponentsInterface.RequestComponent( ri, 'MeshComponent', 'IComponent', true );
	}

	protected event OnTakeControl( ri : EntityResolveComponentsInterface )
	{
		super.OnTakeControl( ri );
		m_meshComponent = EntityResolveComponentsInterface.GetComponent( ri, 'MeshComponent' );
		m_spiralParams = new SpiralControllerParams;
		m_spiralParams.rampUpDistanceStart = 0.40000001;
		m_spiralParams.rampUpDistanceEnd = 1.0;
		m_spiralParams.rampDownDistanceStart = 7.5;
		m_spiralParams.rampDownDistanceEnd = 5.0;
		m_spiralParams.rampDownFactor = 1.0;
		m_spiralParams.randomizePhase = true;
	}

	protected event OnProjectileInitialize( eventData : gameprojectileSetUpEvent )
	{
		var missileDB : weak< Attack_Record >;
		var statsSystem : StatsSystem;
		var weaponID : EntityID;
		var velVariance : Float;
		super.OnProjectileInitialize( eventData );
		m_owner = eventData.owner;
		m_weapon = ( ( WeaponObject )( eventData.weapon ) );
		if( m_weapon )
		{
			statsSystem = GameInstance.GetStatsSystem( eventData.weapon.GetGame() );
			weaponID = eventData.weapon.GetEntityID();
			m_useSpiralParams = statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunAddSpiralTrajectory ) > 0.0;
			m_spiralParams.radius = statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunSpiralRadius );
			m_spiralParams.cycleTimeMin = statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunSpiralCycleTimeMin );
			m_spiralParams.cycleTimeMax = statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunSpiralCycleTimeMax );
			m_spiralParams.randomizeDirection = ( ( Bool )( statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunSpiralRandomizeDirection ) ) );
			velVariance = statsSystem.GetStatValue( weaponID, gamedataStatType.SmartGunProjectileVelocityVariance );
			m_randStartVelocity = RandRangeF( m_startVelocity - ( m_startVelocity * velVariance ), m_startVelocity + ( m_startVelocity * velVariance ) );
		}
		else
		{
			m_spiralParams.enabled = false;
			m_spiralParams.radius = 0.01;
			m_spiralParams.cycleTimeMin = 0.1;
			m_spiralParams.cycleTimeMax = 0.1;
			m_randStartVelocity = m_startVelocity;
			m_useSpiralParams = false;
		}
		missileDB = m_weapon.GetAttack( 'RainMissile' ).GetRecord();
		if( TDBID.IsValid( missileDB.GetID() ) )
		{
			m_missileDBID = missileDB.GetID();
			m_missileAttackRecord = missileDB;
		}
		m_meshComponent.Toggle( false );
		m_timeToDestory = -1.0;
		m_hasExploded = false;
		m_alive = true;
		m_phase = EMissileRainPhase.Init;
		m_countTime = 0.0;
		m_initialTargetPosition = Vector4.EmptyVector();
		m_initialTargetOffset = Vector4.EmptyVector();
		m_finalTargetPositionCalculationDelay = 0.0;
		m_finalTargetPosition = Vector4.EmptyVector();
		m_finalTargetOffset = Vector4.EmptyVector();
		m_followTargetInPhase2 = false;
		m_arrived = false;
		m_armingDistance = AITweakParams.GetFloatFromTweak( m_missileDBID, "armingDistance" );
		m_armed = m_armingDistance <= 0.0;
		m_puppetBroadphaseHitRadiusSquared = AITweakParams.GetFloatFromTweak( m_missileDBID, "puppetBroadphaseHitRadiusSquared" );
	}

	private function StartTrailEffect()
	{
		GameObjectEffectHelper.StartEffectEvent( this, 'trail', true );
	}

	private function StartTrailStartEffect()
	{
		GameObjectEffectHelper.StartEffectEvent( this, 'trail_start', true );
	}

	protected event OnShoot( eventData : gameprojectileShootEvent )
	{
		var linearParams : LinearTrajectoryParams;
		m_spawnPosition = eventData.startPoint;
		if( m_owner.IsPlayer() )
		{
			linearParams = new LinearTrajectoryParams;
			linearParams.startVel = m_startVelocity;
			m_projectileComponent.SetOnCollisionAction( gameprojectileOnCollisionAction.Stop );
			m_projectileComponent.AddLinear( linearParams );
			m_projectileComponent.LockOrientation( false );
			StartTrailEffect();
		}
		else
		{
			DelayDestroyBullet();
		}
	}

	protected event OnHit( evt : gameHitEvent )
	{
		Explode( evt.hitPosition );
		DelayDestroyBullet();
	}

	protected event OnShootTarget( eventData : gameprojectileShootTargetEvent )
	{
		if( !( m_weapon ) )
		{
			DelayDestroyBullet();
		}
		m_targetComponent = eventData.params.trackedTargetComponent;
		m_initialTargetPosition = eventData.params.targetPosition;
		m_initialTargetOffset = eventData.params.hitPlaneOffset;
		m_spawnPosition = eventData.startPoint;
		m_followTargetInPhase2 = AITweakParams.GetBoolFromTweak( m_missileDBID, "followTargetInPhase2" );
		m_finalTargetPositionCalculationDelay = AITweakParams.GetFloatFromTweak( m_missileDBID, "finalTargetPositionCalculationDelay" );
		if( m_finalTargetPositionCalculationDelay == 0.0 )
		{
			CalcFinalTargetPositionAndOffset();
		}
		StartPhase1( eventData.startPoint );
	}

	protected event OnAcceleratedMovement( eventData : gameprojectileAcceleratedMovementEvent ) {}

	protected event OnLinearMovement( eventData : gameprojectileLinearMovementEvent )
	{
		var distance : Vector4;
		distance = Matrix.GetTranslation( m_projectileComponent.GetLocalToWorld() ) - eventData.targetPosition;
		GameObject.SetAudioParameter( this, 'RTPC_Height_Difference', distance.Z );
		GameObject.PlaySoundEvent( this, 'Smart_21301_bullet_trail_whistle' );
	}

	protected event OnTick( eventData : gameprojectileTickEvent )
	{
		var distanceTravelled : Float;
		m_countTime += eventData.deltaTime;
		if( !( m_armed ) && m_phase != EMissileRainPhase.Init )
		{
			distanceTravelled = Vector4.Distance( m_spawnPosition, eventData.position );
			if( distanceTravelled > m_armingDistance )
			{
				m_armed = true;
			}
		}
		if( m_phase == EMissileRainPhase.Phase1 )
		{
			if( ( m_finalTargetPositionCalculationDelay > 0.0 ) && ( m_countTime >= m_finalTargetPositionCalculationDelay ) )
			{
				CalcFinalTargetPositionAndOffset();
			}
			if( m_countTime >= m_phase1Duration )
			{
				StartPhase2();
			}
		}
		if( m_countTime >= m_lifetime )
		{
			DestroyBullet();
		}
		if( m_timeToDestory > 0.0 )
		{
			m_timeToDestory -= eventData.deltaTime;
			if( m_timeToDestory <= 0.0 )
			{
				DestroyBullet();
			}
		}
		if( m_arrived )
		{
			if( m_phase == EMissileRainPhase.Phase2 )
			{
				Explode( Matrix.GetTranslation( m_projectileComponent.GetLocalToWorld() ) );
				DelayDestroyBullet();
			}
			m_arrived = false;
		}
	}

	protected event OnCollision( eventData : gameprojectileHitEvent )
	{
		super.OnCollision( eventData );
		OnCollideWithEntity( eventData.hitInstances[ 0 ].projectilePosition );
	}

	protected event OnGameprojectileBroadPhaseHitEvent( eventData : gameprojectileBroadPhaseHitEvent )
	{
		var hitObject : Entity;
		var hitPosition : Vector4;
		var owner : weak< GameObject >;
		hitObject = eventData.hitObject;
		hitPosition = eventData.position;
		if( ( m_puppetBroadphaseHitRadiusSquared >= 0.0 ) && ( ( ScriptedPuppet )( hitObject ) ) )
		{
			if( Vector4.DistanceSquared( hitPosition, hitObject.GetWorldPosition() ) > m_puppetBroadphaseHitRadiusSquared )
			{
				return false;
			}
		}
		owner = m_weapon.GetOwner();
		if( ( owner && ( owner == hitObject ) ) && !( m_armed ) )
		{
			return false;
		}
		OnCollideWithEntity( hitPosition );
	}

	private function OnCollideWithEntity( projectilePosition : Vector4 )
	{
		Explode( projectilePosition );
		DelayDestroyBullet();
	}

	protected function DelayDestroyBullet()
	{
		if( m_timeToDestory < 0.0 )
		{
			m_timeToDestory = 0.2;
		}
	}

	private function DestroyBullet()
	{
		if( m_alive )
		{
			m_fxInstance.BreakLoop();
			m_alive = false;
			m_meshComponent.Toggle( false );
			m_projectileComponent.ClearTrajectories();
			m_hasExploded = true;
			Release();
		}
	}

	private function Explode( projectilePosition : Vector4 )
	{
		var explosionAttackRecord : Attack_Record;
		var explodeDuration : Float;
		var aoeData : ProjectileHitAoEData;
		var investigateData : stimInvestigateData;
		var broadCaster : StimBroadcasterComponent;
		explodeDuration = 0.2;
		if( !( m_armed ) )
		{
			m_meshComponent.Toggle( false );
			DelayDestroyBullet();
			return;
		}
		if( !( m_hasExploded ) )
		{
			m_hasExploded = true;
			m_meshComponent.Toggle( false );
			explosionAttackRecord = IAttack.GetExplosiveHitAttack( m_missileAttackRecord );
			if( explosionAttackRecord )
			{
				aoeData.source = m_user;
				aoeData.instigator = m_user;
				aoeData.position = projectilePosition;
				aoeData.radius = AITweakParams.GetFloatFromTweak( m_missileDBID, "explosionRadius" );
				aoeData.duration = explodeDuration;
				aoeData.attackRecord = explosionAttackRecord;
				aoeData.weapon = m_weapon;
				aoeData.enableImpulseFalloff = true;
				ProjectileGameEffectHelper.FillProjectileHitAoEData( aoeData );
				broadCaster = GetStimBroadcasterComponent();
				if( broadCaster )
				{
					investigateData.attackInstigator = m_user;
					investigateData.attackInstigatorPosition = m_user.GetWorldPosition();
					investigateData.revealsInstigatorPosition = true;
					broadCaster.TriggerSingleBroadcast( this, gamedataStimType.Explosion, aoeData.radius + 5.0, investigateData );
				}
				GameInstance.GetAudioSystem( GetGame() ).PlayShockwave( 'explosion', projectilePosition );
			}
		}
	}

	protected event OnFollowSuccess( eventData : gameprojectileFollowEvent )
	{
		m_arrived = true;
	}

	protected function StartPhase1( targetPos : Vector4 )
	{
		var followCurveParams : FollowCurveTrajectoryParams;
		var targetDirection : Vector4;
		var forwardOffset : Vector4;
		var lateralOffset : Vector4;
		var orientation : Quaternion;
		followCurveParams = new FollowCurveTrajectoryParams;
		if( EMissileRainPhase.Phase1 != m_phase )
		{
			m_phase = EMissileRainPhase.Phase1;
			m_phase1Duration = RandRangeF( AITweakParams.GetFloatFromTweak( m_missileDBID, "p1DurationMin" ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p1DurationMax" ) );
			m_meshComponent.Toggle( true );
			targetDirection = m_initialTargetPosition - m_spawnPosition;
			targetDirection.Z = 0.0;
			orientation = Quaternion.BuildFromDirectionVector( targetDirection );
			forwardOffset.Y = RandRangeF( AITweakParams.GetFloatFromTweak( m_missileDBID, "p1PositionForwardOffsetMin" ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p1PositionForwardOffsetMax" ) );
			forwardOffset = Quaternion.Transform( orientation, forwardOffset );
			forwardOffset.Z = 0.0;
			lateralOffset.X = RandRangeF( -( AITweakParams.GetFloatFromTweak( m_missileDBID, "p1PositionLateralOffset" ) ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p1PositionLateralOffset" ) );
			lateralOffset = Quaternion.Transform( orientation, lateralOffset );
			lateralOffset.Z = 0.0;
			targetPos += ( forwardOffset + lateralOffset );
			targetPos.Z += AITweakParams.GetFloatFromTweak( m_missileDBID, "p1PositionZOffset" );
			m_projectileComponent.ClearTrajectories();
			followCurveParams.startVelocity = AITweakParams.GetFloatFromTweak( m_missileDBID, "p1StartVelocity" );
			followCurveParams.targetPosition = targetPos;
			followCurveParams.bendTimeRatio = AITweakParams.GetFloatFromTweak( m_missileDBID, "p1BendTimeRatio" );
			followCurveParams.bendFactor = AITweakParams.GetFloatFromTweak( m_missileDBID, "p1BendFactor" );
			followCurveParams.angleInHitPlane = RandRangeF( AITweakParams.GetFloatFromTweak( m_missileDBID, "p1AngleInHitPlaneMin" ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p1AngleInHitPlaneMax" ) );
			followCurveParams.angleInVerticalPlane = RandRangeF( -( AITweakParams.GetFloatFromTweak( m_missileDBID, "p1AngleInVerticalPlane" ) ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p1AngleInVerticalPlane" ) );
			followCurveParams.snapRadius = AITweakParams.GetFloatFromTweak( m_missileDBID, "p1SnapRadius" );
			followCurveParams.sendFollowEvent = false;
			followCurveParams.startVelocityDirectionCheck = AITweakParams.GetBoolFromTweak( m_missileDBID, "startVelocityDirectionCheck", followCurveParams.startVelocityDirectionCheck );
			m_projectileComponent.AddFollowCurve( followCurveParams );
			m_projectileComponent.SetOnCollisionAction( gameprojectileOnCollisionAction.Stop );
			m_spiralParams.enabled = m_useSpiralParams;
			m_projectileComponent.SetSpiral( m_spiralParams );
			StartTrailStartEffect();
		}
	}

	protected function StartPhase2()
	{
		var followCurveParams : FollowCurveTrajectoryParams;
		var p2HardCurve : Bool;
		var inheritCarSpeed : Bool;
		var vehicleOwner : VehicleObject;
		followCurveParams = new FollowCurveTrajectoryParams;
		m_phase = EMissileRainPhase.Phase2;
		if( m_followTargetInPhase2 && m_targetComponent )
		{
			CalcFinalTargetPositionAndOffset();
			followCurveParams.target = ( ( weak< weak< GameObject > > )( m_targetComponent.GetEntity() ) );
		}
		else if( ( m_countTime < m_finalTargetPositionCalculationDelay ) || ( m_finalTargetPositionCalculationDelay < 0.0 ) )
		{
			CalcFinalTargetPositionAndOffset();
		}
		m_projectileComponent.ClearTrajectories();
		followCurveParams.startVelocity = AITweakParams.GetFloatFromTweak( m_missileDBID, "p2StartVelocity" );
		inheritCarSpeed = AITweakParams.GetBoolFromTweak( m_missileDBID, "inheritVehicleSpeed" );
		if( inheritCarSpeed && m_weapon )
		{
			vehicleOwner = ( ( VehicleObject )( m_weapon.GetOwner() ) );
			if( vehicleOwner )
			{
				followCurveParams.startVelocity += vehicleOwner.GetCurrentSpeed();
			}
		}
		followCurveParams.targetPosition = m_finalTargetPosition;
		followCurveParams.bendTimeRatio = AITweakParams.GetFloatFromTweak( m_missileDBID, "p2BendRation" );
		followCurveParams.bendFactor = AITweakParams.GetFloatFromTweak( m_missileDBID, "p2BendFactor" );
		followCurveParams.angleInHitPlane = RandRangeF( AITweakParams.GetFloatFromTweak( m_missileDBID, "p2AngleInHitPlaneMin" ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p2AngleInHitPlaneMax" ) );
		followCurveParams.angleInVerticalPlane = RandRangeF( -( AITweakParams.GetFloatFromTweak( m_missileDBID, "p2AngleInVerticalPlane" ) ), AITweakParams.GetFloatFromTweak( m_missileDBID, "p2AngleInVerticalPlane" ) );
		followCurveParams.shouldRotate = AITweakParams.GetBoolFromTweak( m_missileDBID, "p2ShouldRotate" );
		followCurveParams.accuracy = 1.0;
		followCurveParams.offset = m_finalTargetOffset;
		p2HardCurve = AITweakParams.GetBoolFromTweak( m_missileDBID, "p2HardCurve" );
		if( p2HardCurve )
		{
			followCurveParams.interpolationTimeRatio = AITweakParams.GetFloatFromTweak( m_missileDBID, "p2HardCurveInterpolationTimeRatio" );
			if( followCurveParams.interpolationTimeRatio <= 0.0 )
			{
				followCurveParams.interpolationTimeRatio = 0.05;
			}
			followCurveParams.linearTimeRatio = 0.1;
		}
		followCurveParams.startVelocityDirectionCheck = AITweakParams.GetBoolFromTweak( m_missileDBID, "startVelocityDirectionCheck", followCurveParams.startVelocityDirectionCheck );
		m_projectileComponent.AddFollowCurve( followCurveParams );
		m_spiralParams.enabled = m_useSpiralParams;
		m_projectileComponent.SetSpiral( m_spiralParams );
		StartTrailEffect();
	}

	private function CalcFinalTargetPositionAndOffset()
	{
		var offset : Float;
		var directionOffset : Vector4;
		var useTargetOffset : Bool;
		useTargetOffset = false;
		m_finalTargetOffset = m_initialTargetOffset;
		if( m_targetComponent )
		{
			m_finalTargetPosition = m_targetComponent.GetEntity().GetWorldPosition();
			m_finalTargetOffset.Z = 0.0;
		}
		else
		{
			m_finalTargetPosition = m_initialTargetPosition;
		}
		useTargetOffset = m_targetComponent.IsA( 'gameTargetingComponent' ) || AITweakParams.GetBoolFromTweak( m_missileDBID, "usetargetOffsetWhenUntargeted" );
		if( useTargetOffset )
		{
			offset = AITweakParams.GetFloatFromTweak( m_missileDBID, "targetPositionOffset" );
			if( offset > 0.0 )
			{
				directionOffset = m_spawnPosition - m_finalTargetPosition;
				directionOffset = Vector4.Normalize( directionOffset ) * offset;
				m_finalTargetOffset.X += directionOffset.X;
				m_finalTargetOffset.Y += directionOffset.Y;
				m_finalTargetOffset.X += RandRangeF( -( AITweakParams.GetFloatFromTweak( m_missileDBID, "targetPositionXYAdditive" ) ), AITweakParams.GetFloatFromTweak( m_missileDBID, "targetPositionXYAdditive" ) );
				m_finalTargetOffset.Y += RandRangeF( -( AITweakParams.GetFloatFromTweak( m_missileDBID, "targetPositionXYAdditive" ) ), AITweakParams.GetFloatFromTweak( m_missileDBID, "targetPositionXYAdditive" ) );
			}
		}
		if( !( m_followTargetInPhase2 ) || !( m_targetComponent ) )
		{
			m_fxInstance = SpawnLandVFXs( m_landIndicatorFX, m_finalTargetPosition + m_finalTargetOffset );
		}
	}

}

