import class OverrideMissShotOffset extends Event
{
	import var overrideRecord : String;
}

struct TimeBetweenHitsParameters
{
	var baseCoefficient : Float;
	var baseSourceCoefficient : Float;
	var difficultyLevelCoefficient : Float;
	var groupCoefficient : Float;
	var distanceCoefficient : Float;
	var visibilityCoefficient : Float;
	var playersCountCoefficient : Float;
	var coefficientMultiplier : Float;
	var accuracyCoefficient : Float;
	var coverCoefficient : Float;
	var visionBlockerCoefficient : Float;
}

import class TargetShootComponent extends IComponent
{
	var m_weaponRecord : WeaponItem_Record;
	var m_weaponTDBID : TweakDBID;
	var m_characterRecord : Character_Record;
	var m_characterTDBID : TweakDBID;

	private import const function IsTimeBetweenHitsEnabled() : Bool;
	private import const function IsDebugEnabled() : Bool;
	public import const function GetPackageName() : String;
	private import const function CalculateMissOffset( weaponOwner : weak< GameObject >, weapon : weak< WeaponObject >, shootAtPointWS : Vector4, maxSpread : Float, useForcedMissZOffset : Bool, forcedMissZOffset : Float, isInLowCover : Bool ) : Vector4;
	private import function SetLastHitTime( value : Float );
	private import const function GetLastHitTime() : Float;
	private import function GetBasicGroupCoefficient( weaponOwner : weak< GameObject > ) : Float;
	private import function InsertRecentHitter( weaponOwner : weak< GameObject > );

	private const function GetValueFromCurve( curveName : CName, lookupValue : Float ) : Float
	{
		var statsDataSystem : StatsDataSystem;
		statsDataSystem = GameInstance.GetStatsDataSystem( GetGameObject().GetGame() );
		return statsDataSystem.GetValueFromCurve( 'time_between_hits', lookupValue, curveName );
	}

	private const function GetDistanceCoefficientFromCurve( curveName : CName, lookupValue : Float ) : Float
	{
		var statsDataSystem : StatsDataSystem;
		statsDataSystem = GameInstance.GetStatsDataSystem( GetGameObject().GetGame() );
		return statsDataSystem.GetValueFromCurve( 'tbh_weapon_type_distance_mults', lookupValue, curveName );
	}

	private const function GetVisibilityCoefficientFromCurve( curveName : CName, lookupValue : Float ) : Float
	{
		var statsDataSystem : StatsDataSystem;
		statsDataSystem = GameInstance.GetStatsDataSystem( GetGameObject().GetGame() );
		return statsDataSystem.GetValueFromCurve( 'tbh_weapon_type_visibility_mults', lookupValue, curveName );
	}

	private const function GetGameObject() : GameObject
	{
		return ( ( GameObject )( GetEntity() ) );
	}

	[ profile = "" ]
	public function HandleBeingShot( weaponOwner : weak< GameObject >, weapon : weak< WeaponObject >, shootAtPoint : Vector4, maxSpread : Float, coefficientMultiplier : Float, out miss : Bool ) : Vector4
	{
		var gameInstance : GameInstance;
		var result : Vector4;
		var weaponTDBID : TweakDBID;
		gameInstance = GetGameObject().GetGame();
		result = Vector4( 0.0, 0.0, 0.0, 0.0 );
		if( !( IsTimeBetweenHitsEnabled() ) )
		{
			return result;
		}
		miss = false;
		weaponTDBID = ItemID.GetTDBID( weapon.GetItemID() );
		if( m_weaponTDBID != weaponTDBID )
		{
			m_weaponRecord = TDB.GetWeaponItemRecord( weaponTDBID );
			m_weaponTDBID = weaponTDBID;
		}
		if( ShouldBeHit( weaponOwner, weapon, m_weaponRecord, coefficientMultiplier ) )
		{
			SetLastHitTime( EngineTime.ToFloat( GameInstance.GetSimTime( gameInstance ) ) );
			if( GetGameObject().IsPlayer() )
			{
				InsertRecentHitter( weaponOwner );
			}
			if( IsDebugEnabled() )
			{
				GameInstance.GetDebugVisualizerSystem( gameInstance ).DrawWireSphere( shootAtPoint + result, 0.04, Color( 252, 3, 3, 255 ), 3.0 );
			}
		}
		else
		{
			result = HandleMissed( weaponOwner, weapon, shootAtPoint, maxSpread );
			miss = true;
		}
		return result;
	}

	public function HandleBeingShot( weaponOwner : weak< GameObject >, weapon : weak< WeaponObject >, shootAtPoint : Vector4, maxSpread : Float, coefficientMultiplier : Float ) : Vector4
	{
		var tmp : Bool;
		return HandleBeingShot( weaponOwner, weapon, shootAtPoint, maxSpread, coefficientMultiplier, tmp );
	}

	private function HandleMissed( weaponOwner : weak< GameObject >, weapon : weak< WeaponObject >, shootAtPoint : Vector4, maxSpread : Float ) : Vector4
	{
		var useForcedMissZOffset : Bool;
		var forcedMissZOffset : Float;
		var isInLowCover : Bool;
		var characterTDBID : TweakDBID;
		var scriptedPuppetOwner : ScriptedPuppet;
		var result : Vector4;
		useForcedMissZOffset = false;
		forcedMissZOffset = 0.0;
		isInLowCover = false;
		result = Vector4( 0.0, 0.0, 0.0, 0.0 );
		scriptedPuppetOwner = ( ( ScriptedPuppet )( weaponOwner ) );
		if( scriptedPuppetOwner )
		{
			characterTDBID = scriptedPuppetOwner.GetRecordID();
			if( m_characterTDBID != characterTDBID )
			{
				m_characterRecord = TweakDBInterface.GetCharacterRecord( characterTDBID );
				m_characterTDBID = characterTDBID;
			}
			useForcedMissZOffset = m_weaponRecord.UseForcedTBHZOffset() && m_characterRecord.UseForcedTBHZOffset();
			forcedMissZOffset = m_characterRecord.ForcedTBHZOffset();
			isInLowCover = AICoverHelper.GetCurrentCoverStance( scriptedPuppetOwner ) == 'Low';
		}
		result = CalculateMissOffset( weaponOwner, weapon, shootAtPoint, maxSpread, useForcedMissZOffset, forcedMissZOffset, isInLowCover );
		if( IsDebugEnabled() )
		{
			GameInstance.GetDebugVisualizerSystem( GetGameObject().GetGame() ).DrawWireSphere( shootAtPoint + result, 0.04, Color( 50, 168, 82, 255 ), 3.0 );
		}
		return result;
	}

	private const function GetDifficultyLevelCoefficient() : Float
	{
		var statsDataSys : StatsDataSystem;
		var difficulty : gameDifficulty;
		var fieldName : String;
		statsDataSys = GameInstance.GetStatsDataSystem( GetGameObject().GetGame() );
		difficulty = statsDataSys.GetDifficulty();
		switch( difficulty )
		{
			case gameDifficulty.Story:
				fieldName = ".storyModeMultiplier";
			break;
			case gameDifficulty.Easy:
				fieldName = ".easyModeMultiplier";
			break;
			case gameDifficulty.Hard:
				fieldName = ".normalModeMultiplier";
			break;
			case gameDifficulty.VeryHard:
				fieldName = ".hardModeMultiplier";
			break;
			default:
				fieldName = ".normalModeMultiplier";
			break;
		}
		return TDB.GetFloat( TDBID.Create( GetPackageName() + fieldName ), 1.0 );
	}

	private const function GetHMGGroupMultiplier() : Float
	{
		return TDB.GetFloat( TDBID.Create( GetPackageName() + ".HMGGroupMultiplier" ), 1.0 );
	}

	private function GetGroupCoefficient( weaponOwner : GameObject ) : Float
	{
		return GetValueFromCurve( 'rescaled_group_coefficient', GetBasicGroupCoefficient( weaponOwner ) );
	}

	private const function GetPlayersNumCoefficient( weaponOwner : GameObject ) : Float
	{
		var playersCount : Uint32;
		var outPlayerGameObjects : array< GameObject >;
		playersCount = 1;
		if( IsMultiplayer() && ( ( ( Muppet )( GetGameObject() ) ) || ( ( PlayerPuppet )( GetGameObject() ) ) ) )
		{
			playersCount = GameInstance.GetPlayerSystem( weaponOwner.GetGame() ).FindPlayerControlledObjects( weaponOwner.GetWorldPosition(), 0.0, true, true, outPlayerGameObjects );
			return GetValueFromCurve( 'players_count_coefficient', ( ( Float )( playersCount ) ) );
		}
		else
		{
			return 1.0;
		}
	}

	private const function GetDistanceCoefficient( weapon : weak< WeaponObject >, targetPosition : Vector4 ) : Float
	{
		var distance : Float;
		var heldItemType : gamedataItemType;
		distance = Vector4.Distance( weapon.GetWorldPosition(), targetPosition );
		heldItemType = RPGManager.GetItemType( weapon.GetItemID() );
		switch( heldItemType )
		{
			case gamedataItemType.Wea_AssaultRifle:
				return GetDistanceCoefficientFromCurve( 'assault_rifle_distance_coefficient', distance );
			case gamedataItemType.Wea_ShotgunDual:
				return GetDistanceCoefficientFromCurve( 'dual_shotgun_distance_coefficient', distance );
			case gamedataItemType.Wea_Handgun:
				return GetDistanceCoefficientFromCurve( 'handgun_distance_coefficient', distance );
			case gamedataItemType.Wea_HeavyMachineGun:
				return GetDistanceCoefficientFromCurve( 'hmg_distance_coefficient', distance );
			case gamedataItemType.Wea_LightMachineGun:
				return GetDistanceCoefficientFromCurve( 'lmg_distance_coefficient', distance );
			case gamedataItemType.Wea_PrecisionRifle:
				return GetDistanceCoefficientFromCurve( 'precision_rifle_distance_coefficient', distance );
			case gamedataItemType.Wea_Revolver:
				return GetDistanceCoefficientFromCurve( 'revolver_distance_coefficient', distance );
			case gamedataItemType.Wea_Shotgun:
				return GetDistanceCoefficientFromCurve( 'shotgun_distance_coefficient', distance );
			case gamedataItemType.Wea_SubmachineGun:
				return GetDistanceCoefficientFromCurve( 'smg_distance_coefficient', distance );
			case gamedataItemType.Wea_SniperRifle:
				return GetDistanceCoefficientFromCurve( 'sniper_rifle_distance_coefficient', distance );
			default:
				return GetDistanceCoefficientFromCurve( 'assault_rifle_distance_coefficient', distance );
		}
	}

	private const function GetVisibilityCoefficient( weaponOwner : GameObject, weapon : WeaponObject, target : GameObject, visibilityThresholdCoefficient : Float ) : Float
	{
		var continuousLineOfSight : Float;
		var heldItemType : gamedataItemType;
		continuousLineOfSight = 0.0;
		weaponOwner.GetSourceShootComponent().GetContinuousLineOfSightToTarget( target, continuousLineOfSight );
		continuousLineOfSight *= visibilityThresholdCoefficient;
		if( continuousLineOfSight == 0.0 )
		{
			return 0.0;
		}
		heldItemType = RPGManager.GetItemType( weapon.GetItemID() );
		switch( heldItemType )
		{
			case gamedataItemType.Wea_AssaultRifle:
				return GetVisibilityCoefficientFromCurve( 'assault_rifle_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_ShotgunDual:
				return GetVisibilityCoefficientFromCurve( 'dual_shotgun_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_Handgun:
				return GetVisibilityCoefficientFromCurve( 'handgun_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_HeavyMachineGun:
				return GetVisibilityCoefficientFromCurve( 'hmg_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_LightMachineGun:
				return GetVisibilityCoefficientFromCurve( 'lmg_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_PrecisionRifle:
				return GetVisibilityCoefficientFromCurve( 'precision_rifle_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_Revolver:
				return GetVisibilityCoefficientFromCurve( 'revolver_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_Shotgun:
				return GetVisibilityCoefficientFromCurve( 'shotgun_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_SubmachineGun:
				return GetVisibilityCoefficientFromCurve( 'smg_visibility_coefficient', continuousLineOfSight );
			case gamedataItemType.Wea_SniperRifle:
				return GetVisibilityCoefficientFromCurve( 'sniper_rifle_visibility_coefficient', continuousLineOfSight );
			default:
				return GetVisibilityCoefficientFromCurve( 'assault_rifle_visibility_coefficient', continuousLineOfSight );
		}
	}

	private const function GetCoverCoefficient( weaponOwner : GameObject, weaponRecord : WeaponItem_Record, target : GameObject ) : Float
	{
		var fieldName : String;
		if( weaponOwner.GetSourceShootComponent().CanSeeSecondaryPointOfTarget( target ) )
		{
			return 1.0;
		}
		if( weaponRecord.Evolution().Type() == gamedataWeaponEvolution.Smart )
		{
			fieldName = ".coverVsSmartWeaponsMultiplier";
		}
		else
		{
			fieldName = ".coverVsNormalWeaponsMultiplier";
		}
		return TDB.GetFloat( TDBID.Create( GetPackageName() + fieldName ), 1.0 );
	}

	private const function GetVisionBlockersCoefficient( weaponOwner : GameObject, target : GameObject ) : Float
	{
		return weaponOwner.GetSourceShootComponent().GetLineOfSightTBHModifier( target );
	}

	private function CalculateTimeBetweenHits( const params : ref< TimeBetweenHitsParameters > ) : Float
	{
		return ( ( ( ( ( ( ( ( params.difficultyLevelCoefficient * params.baseCoefficient ) * params.baseSourceCoefficient ) * params.accuracyCoefficient ) * params.distanceCoefficient ) * params.visibilityCoefficient ) * params.playersCountCoefficient ) * params.groupCoefficient ) * params.coverCoefficient ) * params.visionBlockerCoefficient;
	}

	private function ShouldBeHit( weaponOwner : GameObject, weapon : weak< WeaponObject >, weaponRecord : WeaponItem_Record, visibilityThresholdCoefficient : Float ) : Bool
	{
		var timeSinceLastHit : Float;
		var timeBetweenHits : Float;
		var visibilityCollisionToTargetDist : Float;
		var accuracy : Float;
		var params : TimeBetweenHitsParameters;
		var shouldBeHit : Bool;
		var target : GameObject;
		var gameInstance : GameInstance;
		var statsSystem : StatsSystem;
		var heldItemType : gamedataItemType;
		target = GetGameObject();
		gameInstance = target.GetGame();
		statsSystem = GameInstance.GetStatsSystem( gameInstance );
		if( !( weaponOwner.GetSourceShootComponent() ) )
		{
			return true;
		}
		if( target.IsPlayer() && RPGManager.IsTechPierceEnabled( weaponOwner.GetGame(), weaponOwner, weapon.GetItemID() ) )
		{
			visibilityCollisionToTargetDist = ( ( ScriptedPuppet )( weaponOwner ) ).GetSenses().GetVisibilityTraceEndToAgentDist( target );
			if( ( visibilityCollisionToTargetDist > 0.0 ) && ( visibilityCollisionToTargetDist < 1000000000.0 ) )
			{
				return false;
			}
		}
		params.visibilityCoefficient = GetVisibilityCoefficient( weaponOwner, weapon, target, visibilityThresholdCoefficient );
		if( ( visibilityThresholdCoefficient > 0.0 ) && ( params.visibilityCoefficient <= 0.0 ) )
		{
			if( IsDebugEnabled() )
			{
				GameInstance.GetDebugVisualizerSystem( gameInstance ).DrawText3D( weapon.GetWorldPosition(), FloatToString( -1.0 ), Color( 41, 191, 31, 255 ), 0.69999999 );
			}
			return false;
		}
		accuracy = statsSystem.GetStatValue( weaponOwner.GetEntityID(), gamedataStatType.Accuracy );
		if( accuracy == 0.0 )
		{
			return false;
		}
		params.accuracyCoefficient = 1.0 / accuracy;
		params.baseCoefficient = statsSystem.GetStatValue( target.GetEntityID(), gamedataStatType.TBHsBaseCoefficient );
		params.baseSourceCoefficient = statsSystem.GetStatValue( weaponOwner.GetEntityID(), gamedataStatType.TBHsBaseSourceMultiplierCoefficient );
		params.difficultyLevelCoefficient = GetDifficultyLevelCoefficient();
		params.distanceCoefficient = GetDistanceCoefficient( weapon, target.GetWorldPosition() );
		if( params.distanceCoefficient == 0.0 )
		{
			return false;
		}
		params.playersCountCoefficient = GetPlayersNumCoefficient( weaponOwner );
		params.groupCoefficient = ( ( target.IsPlayer() ) ? ( GetGroupCoefficient( weaponOwner ) ) : ( 1.0 ) );
		params.coverCoefficient = ( ( target.IsPlayer() ) ? ( GetCoverCoefficient( weaponOwner, weaponRecord, target ) ) : ( 1.0 ) );
		params.visionBlockerCoefficient = GetVisionBlockersCoefficient( weaponOwner, target );
		heldItemType = RPGManager.GetItemType( weapon.GetItemID() );
		if( heldItemType == gamedataItemType.Wea_HeavyMachineGun )
		{
			params.groupCoefficient = LerpF( GetHMGGroupMultiplier(), 1.0, params.groupCoefficient );
		}
		timeBetweenHits = CalculateTimeBetweenHits( params );
		if( !( IsFinal() ) )
		{
			weaponOwner.GetSourceShootComponent().SetDebugParameters( params );
		}
		timeSinceLastHit = EngineTime.ToFloat( GameInstance.GetSimTime( gameInstance ) ) - GetLastHitTime();
		shouldBeHit = timeSinceLastHit >= timeBetweenHits;
		if( IsDebugEnabled() )
		{
			if( shouldBeHit )
			{
				GameInstance.GetDebugVisualizerSystem( gameInstance ).DrawText3D( weapon.GetWorldPosition(), FloatToString( timeBetweenHits ), Color( 245, 22, 49, 255 ), 0.69999999 );
				GameInstance.GetDebugVisualizerSystem( gameInstance ).DrawText3D( target.GetWorldPosition() + Vector4( 0.0, 0.0, 1.79999995, 0.0 ), FloatToString( timeSinceLastHit ), Color( 245, 22, 49, 255 ), MinF( 0.69999999, timeSinceLastHit ) );
			}
			else
			{
				GameInstance.GetDebugVisualizerSystem( gameInstance ).DrawText3D( weapon.GetWorldPosition(), FloatToString( timeBetweenHits ), Color( 41, 191, 31, 255 ), 0.69999999 );
			}
		}
		return shouldBeHit;
	}

}

