enum EActionType
{
	QuickAction = 0,
	ChargeAction = 1,
	None = 2,
}

class ProjectileDelayEvent extends Event
{
}

class ProjectileBreachEvent extends Event
{
}

class ProjectileTickEvent extends TickableEvent
{
}

class BaseProjectile extends ItemObject
{
	protected var m_projectileComponent : ProjectileComponent;
	protected var m_user : weak< GameObject >;
	protected var m_projectile : weak< GameObject >;
	protected var m_projectileSpawnPoint : Vector4;
	protected var m_projectilePosition : Vector4;
	protected var m_initialLaunchVelocity : Float;
	default m_initialLaunchVelocity = 0.f;
	protected var m_lifeTime : Float;
	default m_lifeTime = 0.f;
	editable var m_tweakDBPath : String;

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

	protected event OnTakeControl( ri : EntityResolveComponentsInterface )
	{
		super.OnTakeControl( ri );
		m_projectileComponent = ( ( ProjectileComponent )( EntityResolveComponentsInterface.GetComponent( ri, 'projectileComponent' ) ) );
	}

	protected event OnProjectileInitialize( eventData : gameprojectileSetUpEvent )
	{
		var broadcaster : StimBroadcasterComponent;
		m_user = eventData.owner;
		m_projectile = eventData.weapon;
		broadcaster = eventData.owner.GetStimBroadcasterComponent();
		if( broadcaster )
		{
			broadcaster.TriggerSingleBroadcast( this, gamedataStimType.WeaponDisplayed );
		}
	}

	protected event OnShoot( eventData : gameprojectileShootEvent )
	{
		var broadcaster : StimBroadcasterComponent;
		SetProjectileLifetime();
		SetInitialVelocityBasedOnActionType( m_user );
		LinearLaunch( eventData, m_initialLaunchVelocity );
		GameObjectEffectHelper.StartEffectEvent( m_projectile, 'trail', true );
		broadcaster = m_user.GetStimBroadcasterComponent();
		if( broadcaster )
		{
			broadcaster.TriggerSingleBroadcast( this, gamedataStimType.IllegalAction );
		}
	}

	protected final function CurvedLaunchToTarget( eventData : gameprojectileShootEvent, optional targetObject : weak< GameObject >, optional targetComponent : IPlacedComponent )
	{
		var linearTimeRatio : Float;
		var interpolationTimeRatio : Float;
		var returnTimeMargin : Float;
		var bendTimeRatio : Float;
		var bendFactor : Float;
		var halfLeanAngle : Float;
		var endLeanAngle : Float;
		var angleInterpolationDuration : Float;
		linearTimeRatio = GetProjectileTweakDBFloatParameter( "linearTimeRatio" );
		interpolationTimeRatio = GetProjectileTweakDBFloatParameter( "interpolationTimeRatio" );
		returnTimeMargin = GetProjectileTweakDBFloatParameter( "returnTimeMargin" );
		bendTimeRatio = GetProjectileTweakDBFloatParameter( "bendTimeRatio" );
		bendFactor = GetProjectileTweakDBFloatParameter( "bendFactor" );
		halfLeanAngle = GetProjectileTweakDBFloatParameter( "halfLeanAngle" );
		endLeanAngle = GetProjectileTweakDBFloatParameter( "endLeanAngle" );
		angleInterpolationDuration = GetProjectileTweakDBFloatParameter( "angleInterpolationDuration" );
		CurvedLaunch( eventData, targetObject, targetComponent, m_initialLaunchVelocity, linearTimeRatio, interpolationTimeRatio, returnTimeMargin, bendTimeRatio, bendFactor, halfLeanAngle, endLeanAngle, angleInterpolationDuration );
	}

	protected event OnShootTarget( eventData : gameprojectileShootTargetEvent )
	{
		var targetComponent : IPlacedComponent;
		var actionType : EActionType;
		GeneralLaunchSetup( eventData );
		SetInitialVelocityBasedOnActionType( m_user );
		targetComponent = eventData.params.trackedTargetComponent;
		actionType = GetLeftHandCyberwareAction( m_user );
		if( targetComponent && ( actionType == EActionType.QuickAction || actionType == EActionType.ChargeAction ) )
		{
			CurvedLaunchToTarget( eventData, , targetComponent );
		}
		else
		{
			LinearLaunch( eventData, m_initialLaunchVelocity );
		}
	}

	protected final function GeneralLaunchSetup( eventData : gameprojectileShootEvent )
	{
		m_projectileSpawnPoint = eventData.startPoint;
		ProjectileHelper.SpawnTrailVFX( m_projectileComponent );
		SetProjectileLifetime();
	}

	protected final function SetProjectileLifetime()
	{
		m_lifeTime = GetProjectileTweakDBFloatParameter( "lifetime" );
		if( m_lifeTime > 0.0 )
		{
			CreateDelayEvent( m_lifeTime );
		}
	}

	protected final function ProjectileBreachDevice( const hitInstance : ref< gameprojectileHitInstance >, value : Float )
	{
		CreateProjectileDeviceBreachEvent( hitInstance, value );
	}

	protected event OnCollision( eventData : gameprojectileHitEvent ) {}

	protected final function CreateCustomTickEventWithDuration( value : Float )
	{
		var projectileTick : ProjectileTickEvent;
		projectileTick = new ProjectileTickEvent;
		GameInstance.GetDelaySystem( GetGame() ).TickOnEvent( this, projectileTick, value );
	}

	protected final function CreateDelayEvent( value : Float )
	{
		var projectileDelayEvent : ProjectileDelayEvent;
		projectileDelayEvent = new ProjectileDelayEvent;
		GameInstance.GetDelaySystem( GetGame() ).DelayEvent( this, projectileDelayEvent, value );
	}

	protected final function CreateProjectileDeviceBreachEvent( const hitInstance : ref< gameprojectileHitInstance >, value : Float )
	{
		var projectileBreachEvent : ProjectileBreachEvent;
		projectileBreachEvent = new ProjectileBreachEvent;
		GameInstance.GetDelaySystem( GetGame() ).DelayEvent( GetObject( hitInstance ), projectileBreachEvent, value );
	}

	protected event OnMaxLifetimeReached( evt : ProjectileDelayEvent )
	{
		Release();
	}

	protected event OnUpdate( evt : ProjectileTickEvent ) {}

	protected final function SetMeshVisible( value : Bool )
	{
		var meshVisualComponent : IComponent;
		meshVisualComponent.Toggle( value );
	}

	protected final function Release()
	{
		var objectPool : ObjectPoolSystem;
		GameObject.PlaySoundEvent( this, 'Stop_Time_Dilation_Bullet_Trails_bullets_normal' );
		objectPool = GameInstance.GetObjectPoolSystem( GetGame() );
		objectPool.Release( this );
	}

	protected final function HasTrajectory() : Bool
	{
		return m_projectileComponent.IsTrajectoryEmpty();
	}

	protected final function StopProjectile()
	{
		m_projectileComponent.ClearTrajectories();
	}

	protected final function CanBounceAfterCollision( value : Bool )
	{
		if( value )
		{
			m_projectileComponent.SetOnCollisionAction( gameprojectileOnCollisionAction.Bounce );
		}
		else
		{
			m_projectileComponent.SetOnCollisionAction( gameprojectileOnCollisionAction.Stop );
		}
	}

	protected final function SpawnVisualEffect( effectName : CName, optional eventTag : CName )
	{
		var spawnEffectEvent : entSpawnEffectEvent;
		spawnEffectEvent = new entSpawnEffectEvent;
		spawnEffectEvent.effectName = effectName;
		spawnEffectEvent.effectInstanceName = eventTag;
		QueueEvent( ( ( Event )( spawnEffectEvent ) ) );
	}

	protected final function BreakVisualEffectLoop( effectName : CName )
	{
		var evt : entBreakEffectLoopEvent;
		evt = new entBreakEffectLoopEvent;
		evt.effectName = effectName;
		QueueEvent( ( ( Event )( evt ) ) );
	}

	protected final function KillVisualEffect( effectName : CName )
	{
		var evt : entKillEffectEvent;
		evt = new entKillEffectEvent;
		evt.effectName = effectName;
		QueueEvent( ( ( Event )( evt ) ) );
	}

	protected final function SpawnLandVFXs( fx : FxResource, fxposition : Vector4 ) : FxInstance
	{
		var transform : WorldTransform;
		var position : WorldPosition;
		var fxSystem : FxSystem;
		var fxInstance : FxInstance;
		if( FxResource.IsValid( fx ) )
		{
			WorldPosition.SetVector4( position, fxposition );
			WorldTransform.SetWorldPosition( transform, position );
			fxSystem = GameInstance.GetFxSystem( GetGame() );
			fxInstance = fxSystem.SpawnEffect( fx, transform );
		}
		return fxInstance;
	}

	protected final function GetObject( const hitInstance : ref< gameprojectileHitInstance > ) : weak< GameObject >
	{
		return ProjectileHitHelper.GetHitObject( hitInstance );
	}

	protected final function GetObjectWorldPosition( object : weak< GameObject > ) : Vector4
	{
		return ProjectileTargetingHelper.GetObjectCurrentPosition( object );
	}

	protected final function GetLeftHandCyberwareAction( user : weak< GameObject > ) : EActionType
	{
		var actionType : EActionType;
		var quickAction : Bool;
		var chargeAction : Bool;
		quickAction = ProjectileHelper.GetPSMBlackboardIntVariable( user, GetAllBlackboardDefs().PlayerStateMachine.LeftHandCyberware ) == ( ( Int32 )( gamePSMLeftHandCyberware.QuickAction ) );
		chargeAction = ProjectileHelper.GetPSMBlackboardIntVariable( user, GetAllBlackboardDefs().PlayerStateMachine.LeftHandCyberware ) == ( ( Int32 )( gamePSMLeftHandCyberware.ChargeAction ) );
		if( quickAction )
		{
			actionType = EActionType.QuickAction;
		}
		else if( chargeAction )
		{
			actionType = EActionType.ChargeAction;
		}
		else
		{
			actionType = EActionType.None;
		}
		return actionType;
	}

	protected final function GetProjectileTweakDBFloatParameter( const param : ref< String > ) : Float
	{
		return TDB.GetFloat( TDBID.Create( ( ( "projectile." + m_tweakDBPath ) + "." ) + param ), -1.0 );
	}

	public final function GetInstigator() : weak< GameObject >
	{
		return m_user;
	}

	protected final function SetInitialVelocityBasedOnActionType( user : weak< GameObject > )
	{
		switch( GetLeftHandCyberwareAction( user ) )
		{
			case EActionType.QuickAction:
				m_initialLaunchVelocity = GetProjectileTweakDBFloatParameter( "startVelocity" );
			break;
			case EActionType.ChargeAction:
				m_initialLaunchVelocity = GetProjectileTweakDBFloatParameter( "startVelocityCharged" );
			break;
			default:
				m_initialLaunchVelocity = -1.0;
			break;
		}
	}

	protected final function TriggerSingleStimuli( const hitInstance : ref< gameprojectileHitInstance >, stimToSend : gamedataStimType )
	{
		var investigateData : stimInvestigateData;
		var broadcaster : StimBroadcasterComponent;
		broadcaster = GetStimBroadcasterComponent();
		if( broadcaster )
		{
			investigateData.attackInstigator = ( ( Entity )( GameInstance.GetPlayerSystem( GetGame() ).GetLocalPlayerControlledGameObject() ) );
			broadcaster.TriggerSingleBroadcast( this, stimToSend, , investigateData );
		}
	}

	protected final function TriggerActiveStimuliWithLifetime( const hitInstance : ref< gameprojectileHitInstance >, stimToSend : gamedataStimType, lifetime : Float, radius : Float )
	{
		var broadcaster : StimBroadcasterComponent;
		broadcaster = GetStimBroadcasterComponent();
		if( broadcaster )
		{
			broadcaster.SetSingleActiveStimuli( this, stimToSend, lifetime, radius );
		}
	}

	protected final function ProjectileHitAoE( const hitInstance : ref< gameprojectileHitInstance >, attackRadius : Float, optional attackRecord : Attack_Record )
	{
		var aoeData : ProjectileHitAoEData;
		aoeData.source = this;
		aoeData.instigator = m_user;
		aoeData.position = hitInstance.position;
		aoeData.radius = attackRadius;
		aoeData.duration = 0.0;
		aoeData.attackRecord = attackRecord;
		ProjectileGameEffectHelper.FillProjectileHitAoEData( aoeData );
		GameInstance.GetAudioSystem( GetGame() ).PlayShockwave( 'explosion', m_projectilePosition );
	}

	protected final function ProjectileHit( eventData : gameprojectileHitEvent )
	{
		ProjectileGameEffectHelper.FillProjectileHitData( this, m_user, m_projectileComponent, eventData );
	}

	protected final function LinearLaunch( eventData : gameprojectileShootEvent, startVelocity : Float )
	{
		ProjectileLaunchHelper.SetLinearLaunchTrajectory( m_projectileComponent, startVelocity );
	}

	protected final function ParabolicLaunch( eventData : gameprojectileShootEvent, gravitySimulation : Float, startVelocity : Float, energyLossFactorAfterCollision : Float )
	{
		ProjectileLaunchHelper.SetParabolicLaunchTrajectory( m_projectileComponent, gravitySimulation, startVelocity, energyLossFactorAfterCollision );
	}

	protected final function CurvedLaunch( eventData : gameprojectileShootEvent, optional targetObject : weak< GameObject >, optional targetComponent : IPlacedComponent, startVelocity : Float, linearTimeRatio : Float, interpolationTimeRatio : Float, returnTimeMargin : Float, bendTimeRatio : Float, bendFactor : Float, halfLeanAngle : Float, endLeanAngle : Float, angleInterpolationDuration : Float )
	{
		ProjectileLaunchHelper.SetCurvedLaunchTrajectory( m_projectileComponent, targetObject, targetComponent, startVelocity, linearTimeRatio, interpolationTimeRatio, returnTimeMargin, bendTimeRatio, bendFactor, halfLeanAngle, endLeanAngle, angleInterpolationDuration );
	}

}

