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

class ProjectileLauncherRoundDetonationDelayEvent extends Event
{
}

class ProjectileLauncherRoundCollisionEvaluator extends gameprojectileScriptCollisionEvaluator
{
	private var m_collisionAction : gamedataProjectileOnCollisionAction;
	private var m_projectileStopped : Bool;
	default m_projectileStopped = false;
	private var m_maxBounceCount : Int32;
	default m_maxBounceCount = 0;
	private var m_projectileBounced : Bool;
	default m_projectileBounced = false;
	private var m_projectileStopAndStick : Bool;
	default m_projectileStopAndStick = false;
	private var m_projectilePierced : Bool;
	default m_projectilePierced = false;

	public function SetCollisionAction( collisionAction : gamedataProjectileOnCollisionAction )
	{
		m_collisionAction = collisionAction;
	}

	public function SetNumberOfBounces( maxBounceCount : Int32 )
	{
		m_maxBounceCount = maxBounceCount;
	}

	public function ProjectileStopped() : Bool
	{
		return m_projectileStopped;
	}

	public function ProjectileStopAndStick() : Bool
	{
		return m_projectileStopAndStick;
	}

	public function ProjectileBounced() : Bool
	{
		return m_projectileBounced;
	}

	public function ProjectilePierced() : Bool
	{
		return m_projectilePierced;
	}

	protected override function EvaluateCollision( defaultOnCollisionAction : gameprojectileOnCollisionAction, params : CollisionEvaluatorParams ) : gameprojectileOnCollisionAction
	{
		var validBounces : Bool;
		validBounces = false;
		if( params.isWaterSurface )
		{
			return gameprojectileOnCollisionAction.Stop;
		}
		validBounces = params.numBounces < ( ( Uint32 )( m_maxBounceCount ) );
		if( m_collisionAction == gamedataProjectileOnCollisionAction.Stop || ( !( validBounces ) && m_collisionAction == gamedataProjectileOnCollisionAction.Bounce ) )
		{
			m_projectileStopped = true;
			return gameprojectileOnCollisionAction.Stop;
		}
		else if( m_collisionAction == gamedataProjectileOnCollisionAction.Bounce )
		{
			m_projectileBounced = true;
			return gameprojectileOnCollisionAction.Bounce;
		}
		else if( m_collisionAction == gamedataProjectileOnCollisionAction.Pierce )
		{
			m_projectilePierced = true;
			return gameprojectileOnCollisionAction.Pierce;
		}
		else if( m_collisionAction == gamedataProjectileOnCollisionAction.StopAndStick )
		{
			m_projectileStopAndStick = true;
			return gameprojectileOnCollisionAction.StopAndStick;
		}
		else if( m_collisionAction == gamedataProjectileOnCollisionAction.StopAndStickPerpendicular )
		{
			m_projectileStopAndStick = true;
			return gameprojectileOnCollisionAction.StopAndStickPerpendicular;
		}
		return gameprojectileOnCollisionAction.None;
	}

}

class ProjectileLauncherRound extends ItemObject
{
	protected var m_projectileComponent : ProjectileComponent;
	protected var m_resourceLibraryComponent : ResourceLibraryComponent;
	protected var m_user : weak< GameObject >;
	protected var m_projectile : weak< GameObject >;
	protected var m_weapon : weak< WeaponObject >;
	protected var m_projectileSpawnPoint : Vector4;
	protected var m_launchMode : gamedataProjectileLaunchMode;
	protected var m_initialLaunchVelocity : Float;
	protected var m_installedProjectile : ItemID;
	protected var m_actionType : ELauncherActionType;
	protected var m_attackRecord : Attack_Record;
	protected var m_releaseRequestDelayID : DelayID;
	protected var m_detonateRequestDelayID : DelayID;
	protected var m_projectileTrailName : CName;
	protected var m_projectileCollisionEvaluator : ProjectileLauncherRoundCollisionEvaluator;
	protected var m_isAlive : Bool;
	protected var m_isSinking : Bool;
	protected var m_waterHeight : Float;
	protected var m_deepWaterDepth : Float;
	protected var m_sinkingDetonationDelay : Float;
	protected var m_waterSurfaceImpactImpulseRadius : Float;
	protected var m_waterSurfaceImpactImpulseStrength : Float;
	protected var m_waterDetonationImpulseRadius : Float;
	protected var m_waterDetonationImpulseStrength : Float;

	protected event OnRequestComponents( ri : EntityRequestComponentsInterface )
	{
		EntityRequestComponentsInterface.RequestComponent( ri, 'projectileComponent', 'ProjectileComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'StimBroadcaster', 'StimBroadcasterComponent', false );
		EntityRequestComponentsInterface.RequestComponent( ri, 'MeshComponent', 'IComponent', true );
		EntityRequestComponentsInterface.RequestComponent( ri, 'ResourceLibrary', 'ResourceLibraryComponent', true );
	}

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

	protected event OnProjectileInitialize( eventData : gameprojectileSetUpEvent )
	{
		var broadcaster : StimBroadcasterComponent;
		var invalidDelayID : DelayID;
		m_user = eventData.owner;
		m_projectile = eventData.weapon;
		m_weapon = ( ( WeaponObject )( m_projectile ) );
		SetCurrentlyInstalledRound();
		SetProjectileLauncherAction();
		SetCollisionAction();
		m_isAlive = true;
		m_isSinking = false;
		m_waterHeight = 0.0;
		m_deepWaterDepth = GetFloat( "deepWaterDepth" );
		m_sinkingDetonationDelay = GetFloat( "sinkingDetonationDelay" );
		m_waterSurfaceImpactImpulseRadius = GetFloat( "waterSurfaceImpactImpulseRadius" );
		m_waterSurfaceImpactImpulseStrength = GetFloat( "waterSurfaceImpactImpulseStrength" );
		m_waterDetonationImpulseRadius = GetFloat( "waterDetonationImpulseRadius" );
		m_waterDetonationImpulseStrength = GetFloat( "waterDetonationImpulseStrength" );
		invalidDelayID = GetInvalidDelayID();
		if( m_releaseRequestDelayID != invalidDelayID )
		{
			GameInstance.GetDelaySystem( GetGame() ).CancelDelay( m_releaseRequestDelayID );
			m_releaseRequestDelayID = invalidDelayID;
		}
		if( m_detonateRequestDelayID != invalidDelayID )
		{
			GameInstance.GetDelaySystem( GetGame() ).CancelDelay( m_detonateRequestDelayID );
			m_detonateRequestDelayID = invalidDelayID;
		}
		broadcaster = eventData.owner.GetStimBroadcasterComponent();
		if( broadcaster )
		{
			broadcaster.TriggerSingleBroadcast( this, gamedataStimType.WeaponDisplayed );
		}
	}

	protected final function SetCollisionAction()
	{
		var collisionAction : CName;
		m_projectileCollisionEvaluator = new ProjectileLauncherRoundCollisionEvaluator;
		m_projectileComponent.SetCollisionEvaluator( m_projectileCollisionEvaluator );
		m_projectileComponent.SetEnergyLossFactor( GetFloat( "energyLossFactor" ), GetFloat( "energyLossFactor" ) );
		if( m_actionType == ELauncherActionType.QuickAction )
		{
			collisionAction = GetCName( "collisionAction" );
		}
		else
		{
			collisionAction = GetCName( "collisionActionCharged" );
		}
		m_projectileCollisionEvaluator.SetCollisionAction( CollisionActionNameToEnum( collisionAction ) );
		m_projectileCollisionEvaluator.SetNumberOfBounces( GetInt( "maxBounceCount" ) );
	}

	protected final function SetCurrentlyInstalledRound() : Bool
	{
		var projectileLauncherRound : array< SPartSlots >;
		var partSlots : SPartSlots;
		var i : Int32;
		projectileLauncherRound = ItemModificationSystem.GetAllSlots( m_user, m_weapon.GetItemID() );
		if( projectileLauncherRound.Size() == 0 )
		{
			return false;
		}
		for( i = 0; i < projectileLauncherRound.Size(); i += 1 )
		{
			partSlots = projectileLauncherRound[ i ];
			if( partSlots.status == ESlotState.Taken && ( partSlots.slotID == T"AttachmentSlots.ProjectileLauncherRound" ) )
			{
				m_installedProjectile = partSlots.installedPart;
			}
		}
		return false;
	}

	protected event OnShoot( eventData : gameprojectileShootEvent )
	{
		GeneralLaunchSetup( eventData );
		LinearLaunch( eventData, m_initialLaunchVelocity );
	}

	protected event OnShootTarget( eventData : gameprojectileShootTargetEvent )
	{
		var targetComponent : IPlacedComponent;
		var targetEntity : weak< Entity >;
		var isFriendlyNPC : Bool;
		isFriendlyNPC = false;
		GeneralLaunchSetup( eventData );
		targetComponent = eventData.params.trackedTargetComponent;
		targetEntity = targetComponent.GetEntity();
		isFriendlyNPC = PlayerPuppet.IsTargetFriendlyNPC( ( ( PlayerPuppet )( m_user ) ), targetEntity );
		if( ( m_launchMode == gamedataProjectileLaunchMode.Tracking && targetComponent ) && !( isFriendlyNPC ) )
		{
			CurvedLaunchToTarget( eventData, , targetComponent );
		}
		else
		{
			LinearLaunch( eventData, m_initialLaunchVelocity );
		}
	}

	protected final function GeneralLaunchSetup( eventData : gameprojectileShootEvent )
	{
		var broadcaster : StimBroadcasterComponent;
		m_projectileSpawnPoint = eventData.startPoint;
		SetProjectileLifetime();
		SetLaunchModeBasedOnAction();
		SetAttackRecordBasedOnAction();
		SetLaunchVelocityBasedOnAction();
		SetProjectileTrailEffect();
		broadcaster = m_user.GetStimBroadcasterComponent();
		if( broadcaster )
		{
			broadcaster.TriggerSingleBroadcast( this, gamedataStimType.IllegalAction );
		}
		m_projectileComponent.SetCollisionCooldown( 1.0 );
	}

	protected const function WeaponIsCharged() : Bool
	{
		var weaponObject : weak< WeaponObject >;
		weaponObject = ( ( WeaponObject )( GameInstance.GetTransactionSystem( m_user.GetGame() ).GetItemInSlot( m_user, T"AttachmentSlots.WeaponLeft" ) ) );
		return weaponObject.IsCharged();
	}

	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 = GetFloat( "linearTimeRatio" );
		interpolationTimeRatio = GetFloat( "interpolationTimeRatio" );
		returnTimeMargin = GetFloat( "returnTimeMargin" );
		bendTimeRatio = GetFloat( "bendTimeRatio" );
		bendFactor = GetFloat( "bendFactor" );
		halfLeanAngle = GetFloat( "halfLeanAngle" );
		endLeanAngle = GetFloat( "endLeanAngle" );
		angleInterpolationDuration = GetFloat( "angleInterpolationDuration" );
		CurvedLaunch( eventData, targetObject, targetComponent, m_initialLaunchVelocity, linearTimeRatio, interpolationTimeRatio, returnTimeMargin, bendTimeRatio, bendFactor, halfLeanAngle, endLeanAngle, angleInterpolationDuration );
	}

	protected final function GetFloat( const param : ref< String > ) : Float
	{
		return TDB.GetFloat( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected final function GetInt( const param : ref< String > ) : Int32
	{
		return TDB.GetInt( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected final function GetBool( const param : ref< String > ) : Bool
	{
		return TDB.GetBool( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected final function GetCName( const param : ref< String > ) : CName
	{
		return TDB.GetCName( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected final function GetString( const param : ref< String > ) : String
	{
		return TDB.GetString( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected final function GetVector3( const param : ref< String > ) : Vector3
	{
		return TDB.GetVector3( ( ItemID.GetTDBID( m_installedProjectile ) + T"." ) + TDBID.Create( param ) );
	}

	protected event OnCollision( eventData : gameprojectileHitEvent )
	{
		var effect : FxResource;
		var effectTransform : WorldTransform;
		var hitInstance : gameprojectileHitInstance;
		var parabolicTrajectoryParams : ParabolicTrajectoryParams;
		var i : Int32;
		var chargeAction : Bool;
		var targetEntityID : EntityID;
		if( !( m_isAlive ) )
		{
			return false;
		}
		for( i = 0; i < eventData.hitInstances.Size(); i += 1 )
		{
			hitInstance = eventData.hitInstances[ i ];
			if( hitInstance.isWaterSurfaceImpact )
			{
				if( !( m_isSinking ) )
				{
					effect = m_resourceLibraryComponent.GetResource( 'splash_effect' );
					if( FxResource.IsValid( effect ) )
					{
						WorldTransform.SetPosition( effectTransform, hitInstance.position );
						GameInstance.GetFxSystem( GetGame() ).SpawnEffect( effect, effectTransform );
					}
					GameObject.PlaySound( this, 'w_bul_hit_water' );
					RenderingSystem.AddWaterImpulse( hitInstance.position, m_waterSurfaceImpactImpulseRadius, m_waterSurfaceImpactImpulseStrength );
					m_isSinking = true;
					m_waterHeight = hitInstance.position.Z;
					parabolicTrajectoryParams = ParabolicTrajectoryParams.GetAccelVelParabolicParams( Vector4( 0.0, 0.0, -0.40000001, 0.0 ), 0.60000002, 0.55000001 );
					m_projectileComponent.ClearTrajectories();
					m_projectileComponent.SetEnergyLossFactor( 0.05, 0.05 );
					m_projectileComponent.AddParabolic( parabolicTrajectoryParams );
					CreateDetonationDelayEvent( m_sinkingDetonationDelay );
				}
				continue;
			}
			if( m_isSinking )
			{
				GameInstance.GetDelaySystem( GetGame() ).CancelDelay( m_detonateRequestDelayID );
				m_detonateRequestDelayID = GetInvalidDelayID();
				ExecuteGameEffect( hitInstance.projectilePosition );
				StopProjectile();
				Release();
			}
			else
			{
				if( m_projectileCollisionEvaluator.ProjectileStopped() )
				{
					StopProjectile();
					Release();
				}
				if( m_projectileCollisionEvaluator.ProjectileStopAndStick() )
				{
					StopProjectile();
					SetProjectileDetonationTime();
					GameObjectEffectHelper.StartEffectEvent( this, 'detonation_warning', true );
				}
				if( !( m_projectileCollisionEvaluator.ProjectileStopAndStick() ) )
				{
					ExecuteGameEffect( hitInstance.projectilePosition );
					chargeAction = ProjectileHelper.GetPSMBlackboardIntVariable( m_user, GetAllBlackboardDefs().PlayerStateMachine.LeftHandCyberware ) == ( ( Int32 )( gamePSMLeftHandCyberware.ChargeAction ) );
					targetEntityID = hitInstance.hitObject.GetEntityID();
					if( ( chargeAction && EntityID.IsDefined( targetEntityID ) ) && m_user.GetAttitudeTowards( GetObject( hitInstance ) ) != EAIAttitude.AIA_Friendly )
					{
						GameInstance.GetStatusEffectSystem( GetGame() ).ApplyStatusEffect( targetEntityID, T"BaseStatusEffect.Knockdown" );
					}
				}
			}
			EvaluateStimBroadcasting( CollisionStimTypeNameToEnum( GetCName( "onCollisionStimType" ) ) );
			break;
		}
	}

	protected final function ExecuteGameEffect( projectilePosition : Vector4 )
	{
		var effect : FxResource;
		var effectPosition : Vector4;
		var effectTransform : WorldTransform;
		var isDeepUnderwater : Bool;
		var disableAoEAttackVfx : Bool;
		var radius : Float;
		var aoeData : ProjectileHitAoEData;
		isDeepUnderwater = false;
		disableAoEAttackVfx = false;
		if( m_isSinking )
		{
			isDeepUnderwater = ( projectilePosition.Z - m_waterHeight ) <= m_deepWaterDepth;
			effect = m_resourceLibraryComponent.GetResource( ( ( isDeepUnderwater ) ? ( 'underwater_explosion_deep' ) : ( 'underwater_explosion_shallow' ) ) );
			if( FxResource.IsValid( effect ) )
			{
				effectPosition = projectilePosition;
				effectPosition.Z = m_waterHeight;
				WorldTransform.SetPosition( effectTransform, effectPosition );
				GameInstance.GetFxSystem( GetGame() ).SpawnEffect( effect, effectTransform );
			}
			AddWaterImpulsesOnDetonation( effectPosition, 4 );
			disableAoEAttackVfx = isDeepUnderwater;
		}
		if( m_attackRecord.AttackType().Type() == gamedataAttackType.Explosion )
		{
			radius = m_attackRecord.Range();
			if( ( PlayerDevelopmentSystem.GetData( m_user ).IsNewPerkBought( gamedataNewPerkType.Tech_Left_Milestone_3 ) >= 2 ) && ( PlayerDevelopmentSystem.GetData( m_user ).IsNewPerkBought( gamedataNewPerkType.Tech_Inbetween_Left_3 ) == 1 ) )
			{
				radius *= 1.10000002;
			}
			aoeData.source = this;
			aoeData.instigator = m_user;
			aoeData.position = projectilePosition;
			aoeData.radius = radius;
			aoeData.duration = 0.0;
			aoeData.attackRecord = m_attackRecord;
			aoeData.disableVfx = disableAoEAttackVfx;
			ProjectileGameEffectHelper.FillProjectileHitAoEData( aoeData );
		}
		GameObjectEffectHelper.StopEffectEvent( this, 'detonation_warning' );
	}

	protected final function AddWaterImpulsesOnDetonation( position : Vector4, numImpulses : Int32 )
	{
		var impulseRadius : Float;
		var impulseStrength : Float;
		var i : Int32;
		impulseRadius = m_waterDetonationImpulseRadius * RandRangeF( 0.80000001, 1.20000005 );
		impulseStrength = m_waterDetonationImpulseStrength * RandRangeF( 0.1, 1.20000005 );
		RenderingSystem.AddWaterImpulse( position, impulseRadius, impulseStrength );
		for( i = 0; i < ( numImpulses - 1 ); i += 1 )
		{
			RenderingSystem.AddWaterImpulse( position + Vector4.RandRing( impulseRadius * 0.25, impulseRadius * 0.89999998 ), impulseRadius * RandRangeF( 0.40000001, 0.80000001 ), impulseStrength );
		}
	}

	protected function EvaluateStimBroadcasting( stimToSend : gamedataStimType )
	{
		var broadcastRadius : Float;
		var broadcastLifetime : Float;
		broadcastRadius = GetFloat( "onCollisionStimBroadcastRadius" );
		broadcastLifetime = GetFloat( "onCollisionStimBroadcastLifetime" );
		if( broadcastRadius > 0.0 )
		{
			if( broadcastLifetime > 0.0 )
			{
				TriggerActiveStimuliWithLifetime( stimToSend, broadcastLifetime, broadcastRadius );
			}
			else
			{
				TriggerSingleStimuli( broadcastRadius, stimToSend );
			}
		}
	}

	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;
		if( m_releaseRequestDelayID == GetInvalidDelayID() )
		{
			projectileDelayEvent = new ProjectileDelayEvent;
			m_releaseRequestDelayID = GameInstance.GetDelaySystem( GetGame() ).DelayEvent( this, projectileDelayEvent, value );
		}
	}

	protected final function CreateDetonationDelayEvent( value : Float )
	{
		var projectileDelayEvent : ProjectileLauncherRoundDetonationDelayEvent;
		if( m_detonateRequestDelayID == GetInvalidDelayID() )
		{
			projectileDelayEvent = new ProjectileLauncherRoundDetonationDelayEvent;
			m_detonateRequestDelayID = GameInstance.GetDelaySystem( GetGame() ).DelayEvent( this, projectileDelayEvent, value );
		}
	}

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

	protected event OnMaxDetonationTimeReached( evt : ProjectileLauncherRoundDetonationDelayEvent )
	{
		var currentPosition : Vector4;
		if( m_isAlive )
		{
			currentPosition = GetWorldPosition();
			ExecuteGameEffect( currentPosition );
			Release();
		}
	}

	protected event OnTick( eventData : gameprojectileTickEvent ) {}

	protected final function Release()
	{
		var objectPool : ObjectPoolSystem;
		var invalidDelayID : DelayID;
		if( m_isAlive )
		{
			GameObjectEffectHelper.BreakEffectLoopEvent( this, m_projectileTrailName );
			objectPool = GameInstance.GetObjectPoolSystem( GetGame() );
			objectPool.Release( this );
			invalidDelayID = GetInvalidDelayID();
			if( m_releaseRequestDelayID != invalidDelayID )
			{
				GameInstance.GetDelaySystem( GetGame() ).CancelDelay( m_releaseRequestDelayID );
			}
			if( m_detonateRequestDelayID != invalidDelayID )
			{
				GameInstance.GetDelaySystem( GetGame() ).CancelDelay( m_detonateRequestDelayID );
			}
			m_isAlive = false;
		}
	}

	protected final function SetProjectileTrailEffect()
	{
		switch( m_attackRecord.DamageType().DamageType() )
		{
			case gamedataDamageType.Physical:
				m_projectileTrailName = 'trail';
			break;
			case gamedataDamageType.Thermal:
				m_projectileTrailName = 'trail_thermal';
			break;
			case gamedataDamageType.Chemical:
				m_projectileTrailName = 'trail_chemical';
			break;
			case gamedataDamageType.Electric:
				m_projectileTrailName = 'trail_electric';
			break;
			default:
				m_projectileTrailName = 'trail';
			break;
		}
		GameObjectEffectHelper.StartEffectEvent( this, m_projectileTrailName, true );
	}

	protected final function SetProjectileLifetime()
	{
		var lifetime : Float;
		lifetime = GetFloat( "lifetime" );
		if( lifetime > 0.0 )
		{
			CreateDelayEvent( lifetime );
		}
	}

	protected final function SetProjectileDetonationTime()
	{
		var detonationDelay : Float;
		detonationDelay = GetFloat( "detonationDelay" );
		if( detonationDelay > 0.0 )
		{
			CreateDetonationDelayEvent( detonationDelay );
		}
	}

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

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

	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 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 SetProjectileLauncherAction() : ELauncherActionType
	{
		var quickAction : Bool;
		var chargeAction : Bool;
		quickAction = ProjectileHelper.GetPSMBlackboardIntVariable( m_user, GetAllBlackboardDefs().PlayerStateMachine.LeftHandCyberware ) == ( ( Int32 )( gamePSMLeftHandCyberware.QuickAction ) );
		chargeAction = ProjectileHelper.GetPSMBlackboardIntVariable( m_user, GetAllBlackboardDefs().PlayerStateMachine.LeftHandCyberware ) == ( ( Int32 )( gamePSMLeftHandCyberware.ChargeAction ) );
		if( quickAction )
		{
			m_actionType = ELauncherActionType.QuickAction;
		}
		else if( chargeAction )
		{
			m_actionType = ELauncherActionType.ChargeAction;
		}
		else
		{
			m_actionType = ELauncherActionType.None;
		}
		return m_actionType;
	}

	protected final function SetAttackRecordBasedOnAction()
	{
		switch( m_actionType )
		{
			case ELauncherActionType.QuickAction:
				m_attackRecord = TweakDBInterface.GetAttackRecord( TDBID.Create( GetString( "attack" ) ) );
			break;
			case ELauncherActionType.ChargeAction:
				if( ( PlayerDevelopmentSystem.GetData( m_user ).IsNewPerkBought( gamedataNewPerkType.Espionage_Central_Milestone_1 ) > 0 ) && WeaponIsCharged() )
				{
					m_attackRecord = TweakDBInterface.GetAttackRecord( TDBID.Create( GetString( "relicAttack" ) ) );
				}
				else
				{
					m_attackRecord = TweakDBInterface.GetAttackRecord( TDBID.Create( GetString( "secondaryAttack" ) ) );
				}
			break;
			default:
				break;
		}
	}

	protected final function SetLaunchVelocityBasedOnAction()
	{
		switch( m_actionType )
		{
			case ELauncherActionType.QuickAction:
				m_initialLaunchVelocity = GetFloat( "startVelocity" );
			break;
			case ELauncherActionType.ChargeAction:
				m_initialLaunchVelocity = GetFloat( "startVelocityCharged" );
			break;
			default:
				m_initialLaunchVelocity = -1.0;
			break;
		}
	}

	protected final function CollisionActionNameToEnum( collisionAction : CName ) : gamedataProjectileOnCollisionAction
	{
		switch( collisionAction )
		{
			case 'Bounce':
				return gamedataProjectileOnCollisionAction.Bounce;
			case 'Pierce':
				return gamedataProjectileOnCollisionAction.Pierce;
			case 'Stop':
				return gamedataProjectileOnCollisionAction.Stop;
			case 'StopAndStick':
				return gamedataProjectileOnCollisionAction.StopAndStick;
			case 'StopAndStickPerpendicular':
				return gamedataProjectileOnCollisionAction.StopAndStickPerpendicular;
			default:
				break;
		}
		return gamedataProjectileOnCollisionAction.Invalid;
	}

	protected final function CollisionStimTypeNameToEnum( onCollisionStimType : CName ) : gamedataStimType
	{
		switch( onCollisionStimType )
		{
			case 'Explosion':
				return gamedataStimType.Explosion;
			case 'ProjectileDistraction':
				return gamedataStimType.ProjectileDistraction;
			case 'SoundDistraction':
				return gamedataStimType.SoundDistraction;
			default:
				break;
		}
		return gamedataStimType.Invalid;
	}

	protected final function SetLaunchModeBasedOnAction()
	{
		switch( m_actionType )
		{
			case ELauncherActionType.QuickAction:
				m_launchMode = LaunchModeNameToEnum( GetCName( "quickActionlaunchMode" ) );
			break;
			case ELauncherActionType.ChargeAction:
				m_launchMode = LaunchModeNameToEnum( GetCName( "chargeActionlaunchMode" ) );
			break;
			default:
				break;
		}
	}

	protected final function LaunchModeNameToEnum( launchModeName : CName ) : gamedataProjectileLaunchMode
	{
		switch( launchModeName )
		{
			case 'Regular':
				return gamedataProjectileLaunchMode.Regular;
			case 'Tracking':
				return gamedataProjectileLaunchMode.Tracking;
			default:
				break;
		}
		return gamedataProjectileLaunchMode.Invalid;
	}

	protected final function TriggerSingleStimuli( radius : Float, stimToSend : gamedataStimType )
	{
		var investigationData : stimInvestigateData;
		var broadcaster : StimBroadcasterComponent;
		broadcaster = GetStimBroadcasterComponent();
		if( broadcaster )
		{
			investigationData.attackInstigator = ( ( Entity )( m_user ) );
			broadcaster.TriggerSingleBroadcast( this, stimToSend, radius, investigationData );
		}
	}

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

	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 );
	}

}

