class StatPoolsManager
{

	public static function ApplyDamage( hitEvent : gameHitEvent, forReal : Bool, out valuesLost : array< SDamageDealt > )
	{
		var i, j : Int32;
		var dmgType : gamedataDamageType;
		var tempLost : array< SDamageDealt >;
		var attackValues : array< Float >;
		var poolType : gamedataStatPoolType;
		var targetHit : Bool;
		var hitShapes : array< HitShapeData >;
		var target : GameObject;
		var statsSystem : StatsSystem;
		var maxPercentDamage : Float;
		var projectilesPerShot : Float;
		var damageCeiling : Float;
		var npcTarget : weak< NPCPuppet >;
		var firstHit : Bool;
		var asHitDataBase : HitData_Base;
		var asHitShapeUserDataBase : HitShapeUserDataBase;
		var isProtectionLayer : Bool;
		var instigator : weak< GameObject >;
		hitShapes = hitEvent.hitRepresentationResult.hitShapes;
		instigator = hitEvent.attackData.GetInstigator();
		target = hitEvent.target;
		valuesLost.Clear();
		attackValues = hitEvent.attackComputed.GetAttackValues();
		for( i = 0; i < attackValues.Size(); i += 1 )
		{
			dmgType = ( ( gamedataDamageType )( i ) );
			statsSystem = GameInstance.GetStatsSystem( target.GetGame() );
			maxPercentDamage = statsSystem.GetStatValue( target.GetEntityID(), gamedataStatType.MaxPercentDamageTakenPerHit );
			npcTarget = ( ( NPCPuppet )( target ) );
			if( npcTarget && ( maxPercentDamage > 0.0 ) )
			{
				if( ( npcTarget.IsBoss() || npcTarget.GetNPCRarity() == gamedataNPCRarity.MaxTac ) && !( instigator.GetIsInFastFinisher() ) )
				{
					projectilesPerShot = statsSystem.GetStatValue( hitEvent.attackData.GetWeapon().GetEntityID(), gamedataStatType.ProjectilesPerShot );
					if( AttackData.IsDoT( hitEvent.attackData ) )
					{
						maxPercentDamage *= TweakDBInterface.GetFloat( T"Constants.DamageSystem.maxDamageDoTProportion", 1.0 );
					}
					damageCeiling = ( maxPercentDamage / 100.0 ) * statsSystem.GetStatValue( target.GetEntityID(), gamedataStatType.Health );
					if( projectilesPerShot > 0.0 )
					{
						damageCeiling /= projectilesPerShot;
					}
					attackValues[ i ] = ClampF( attackValues[ i ], 0.0, damageCeiling );
				}
			}
			if( ( attackValues[ i ] > 0.0 ) && ( attackValues[ i ] < 1.0 ) )
			{
				attackValues[ i ] = 1.0;
			}
			firstHit = false;
			for( j = 0; j < hitShapes.Size(); j += 1 )
			{
				isProtectionLayer = false;
				asHitDataBase = ( ( HitData_Base )( hitShapes[ j ].userData ) );
				asHitShapeUserDataBase = ( ( HitShapeUserDataBase )( hitShapes[ j ].userData ) );
				if( asHitDataBase )
				{
					isProtectionLayer = asHitDataBase.m_hitShapeType == HitShape_Type.ProtectionLayer;
				}
				else if( asHitShapeUserDataBase )
				{
					isProtectionLayer = ( asHitShapeUserDataBase.m_isProtectionLayer && !( ( hitEvent.attackData.GetAttackType() == gamedataAttackType.Hack && asHitShapeUserDataBase.m_quickHacksPierceProtection ) ) ) && !( instigator.GetIsInFastFinisher() );
				}
				if( !( firstHit ) && !( isProtectionLayer ) )
				{
					firstHit = true;
					targetHit = true;
				}
				if( ( !( targetHit ) && isProtectionLayer ) && ( hitShapes.Size() < 12 ) )
				{
					attackValues[ i ] = 0.0;
					hitEvent.attackData.RemoveFlag( hitFlag.Headshot );
					hitEvent.attackData.RemoveFlag( hitFlag.CriticalHit );
					hitEvent.attackData.RemoveFlag( hitFlag.BreachHit );
					hitEvent.attackData.SetHitType( gameuiHitType.Glance );
				}
				if( asHitDataBase )
				{
					if( GetBodyPartStatPool( target, asHitDataBase.m_bodyPartStatPoolName, poolType ) )
					{
						ApplyLocalizedDamageSingle( hitEvent, attackValues[ i ], dmgType, poolType, forReal, tempLost );
						MergeStatPoolsLost( valuesLost, tempLost );
					}
				}
			}
			ApplyDamageSingle( hitEvent, dmgType, attackValues[ i ], forReal, tempLost );
			MergeStatPoolsLost( valuesLost, tempLost );
		}
		hitEvent.attackComputed.SetAttackValues( attackValues );
	}

	private static function ApplyLocalizedDamageSingle( hitEvent : gameHitEvent, dmg : Float, dmgType : gamedataDamageType, poolType : gamedataStatPoolType, forReal : Bool, valuesLost : ref< array< SDamageDealt > > )
	{
		var currentPoolVal : Float;
		var valueToDrain : Float;
		var dmgVal : Float;
		var newPool : SDamageDealt;
		var targetID : StatsObjectID;
		targetID = hitEvent.target.GetEntityID();
		valuesLost.Clear();
		currentPoolVal = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() ).GetStatPoolValue( targetID, poolType, false );
		if( currentPoolVal > 0.0 )
		{
			dmgVal = dmg;
			valueToDrain = ( ( currentPoolVal <= dmgVal ) ? ( currentPoolVal ) : ( dmgVal ) );
			if( forReal )
			{
				DrainStatPool( hitEvent, poolType, valueToDrain );
			}
			newPool.affectedStatPool = poolType;
			newPool.value = valueToDrain;
			newPool.type = dmgType;
			valuesLost.PushBack( newPool );
		}
	}

	private static function MergeStatPoolsLost( out to : array< SDamageDealt >, const from : ref< array< SDamageDealt > > )
	{
		var i : Int32;
		for( i = 0; i < from.Size(); i += 1 )
		{
			AddDrain( to, from[ i ].affectedStatPool, from[ i ].value, from[ i ].type );
		}
	}

	private static function GetBodyPartStatPool( obj : GameObject, bodyPartName : CName, out poolType : gamedataStatPoolType ) : Bool
	{
		var statPoolType : gamedataStatPoolType;
		var value : Float;
		var objectID : StatsObjectID;
		objectID = obj.GetEntityID();
		statPoolType = ( ( gamedataStatPoolType )( ( ( Int32 )( EnumValueFromName( 'gamedataStatPoolType', bodyPartName ) ) ) ) );
		if( IsStatPoolValid( statPoolType ) )
		{
			value = GameInstance.GetStatPoolsSystem( obj.GetGame() ).GetStatPoolValue( objectID, statPoolType );
			if( value > 0.0 )
			{
				poolType = statPoolType;
			}
			return true;
		}
		else
		{
			poolType = gamedataStatPoolType.Invalid;
			return false;
		}
	}

	private static function AddDrain( out arr : array< SDamageDealt >, type : gamedataStatPoolType, value : Float, dmgType : gamedataDamageType )
	{
		var res : SDamageDealt;
		var i : Int32;
		for( i = 0; i < arr.Size(); i += 1 )
		{
			if( arr[ i ].affectedStatPool == type )
			{
				arr[ i ].value += value;
				return;
			}
		}
		res.type = dmgType;
		res.affectedStatPool = type;
		res.value = value;
		arr.PushBack( res );
	}

	private static function ApplyDamageSingle( hitEvent : gameHitEvent, dmgType : gamedataDamageType, initialDamageValue : Float, forReal : Bool, valuesLost : ref< array< SDamageDealt > > )
	{
		var attackData : AttackData;
		var instigator : weak< GameObject >;
		var statPoolsSystem : StatPoolsSystem;
		var currentHealthValue : Float;
		var statPoolValue : SDamageDealt;
		var npcTarget : weak< NPCPuppet >;
		var isDeadNPC : Bool;
		attackData = hitEvent.attackData;
		instigator = attackData.GetInstigator();
		statPoolsSystem = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() );
		valuesLost.Clear();
		currentHealthValue = statPoolsSystem.GetStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.Health, false );
		if( currentHealthValue > 0.0 )
		{
			ApplyDamageToOverShieldSingle( hitEvent, dmgType, initialDamageValue, forReal, valuesLost );
			ApplyDamageToArmorSingle( hitEvent, dmgType, initialDamageValue, forReal, valuesLost );
			if( initialDamageValue >= 0.0 )
			{
				if( currentHealthValue < initialDamageValue )
				{
					npcTarget = ( ( NPCPuppet )( hitEvent.target ) );
					isDeadNPC = npcTarget && ( ( npcTarget.IsDead() || ScriptedPuppet.IsDefeated( npcTarget ) ) || npcTarget.IsAboutToDieOrDefeated() );
					if( hitEvent.target.IsPlayer() && hitEvent.attackData.HasFlag( hitFlag.CannotKillPlayer ) )
					{
						initialDamageValue = currentHealthValue - 1.0;
					}
					else if( ( !( hitEvent.target.IsPlayer() ) && !( isDeadNPC ) ) && IsFinisherGrace( hitEvent ) )
					{
						initialDamageValue = currentHealthValue - 1.0;
						if( npcTarget && ( npcTarget.IsBoss() || npcTarget.GetNPCRarity() == gamedataNPCRarity.MaxTac ) )
						{
							GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() ).RequestSettingStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.Poise, 0.0, hitEvent.attackData.GetInstigator(), true );
						}
					}
					else if( ( ( !( hitEvent.target.IsPlayer() ) && !( isDeadNPC ) ) && hitEvent.attackData.HasFlag( hitFlag.BleedingDot ) ) && ( PlayerDevelopmentSystem.GetData( instigator ).IsNewPerkBought( gamedataNewPerkType.Reflexes_Master_Perk_5 ) > 0 ) )
					{
						initialDamageValue = currentHealthValue - 1.0;
						if( npcTarget && ( npcTarget.IsBoss() || npcTarget.GetNPCRarity() == gamedataNPCRarity.MaxTac ) )
						{
							GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() ).RequestSettingStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.Poise, 0.0, hitEvent.attackData.GetInstigator(), true );
						}
					}
					else
					{
						attackData.AddFlag( hitFlag.WasKillingBlow, 'Killing Blow' );
						initialDamageValue = currentHealthValue;
					}
				}
				if( forReal )
				{
					statPoolsSystem.RequestSettingStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.CPO_Armor, 0.0, instigator );
					DrainStatPool( hitEvent, gamedataStatPoolType.Health, initialDamageValue );
				}
				statPoolValue.type = dmgType;
				statPoolValue.affectedStatPool = gamedataStatPoolType.Health;
				statPoolValue.value = initialDamageValue;
				valuesLost.PushBack( statPoolValue );
				attackData.AddFlag( hitFlag.SuccessfulAttack, 'DealtDamage' );
			}
		}
	}

	private static function IsFinisherGrace( hitEvent : gameHitEvent ) : Bool
	{
		var player : weak< PlayerPuppet >;
		var currentPercValue, healthThreshold : Float;
		var chance : Float;
		var weapon : WeaponObject;
		chance = 0.0;
		if( !( hitEvent.attackData.GetInstigator().IsPlayer() ) )
		{
			return false;
		}
		if( hitEvent.attackData.HasFlag( hitFlag.QuickHack ) )
		{
			return false;
		}
		player = ( ( PlayerPuppet )( hitEvent.attackData.GetInstigator() ) );
		if( player.GetIsInFastFinisher() )
		{
			return false;
		}
		currentPercValue = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() ).GetStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.Health );
		healthThreshold = TweakDBInterface.GetFloat( T"NewPerks.Reflexes_Right_Milestone_3.graceChanceHealthThreshold", 0.0 );
		if( currentPercValue < healthThreshold )
		{
			return false;
		}
		weapon = GameObject.GetActiveWeapon( player );
		if( ( weapon.IsThrowable() || weapon.IsMelee() ) || weapon.IsMonowire() )
		{
			if( weapon.IsMelee() )
			{
				chance = TweakDBInterface.GetFloat( T"NewPerks.Reflexes_Right_Milestone_3.graceChance", 0.0 );
			}
			if( weapon.IsThrowable() )
			{
				chance = TweakDBInterface.GetFloat( T"NewPerks.Reflexes_Right_Milestone_3.graceChanceThrowable", 0.0 );
			}
			if( player && player.HasFinisherAvailable() )
			{
				return RandF() < chance;
			}
		}
		return false;
	}

	private static function ApplyDamageToArmorSingle( hitEvent : gameHitEvent, dmgType : gamedataDamageType, out initialDamageValue : Float, forReal : Bool, valuesLost : ref< array< SDamageDealt > > )
	{
		var currentArmorValue : Float;
		var statPoolsSystem : StatPoolsSystem;
		var statPoolValue : SDamageDealt;
		statPoolsSystem = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() );
		currentArmorValue = statPoolsSystem.GetStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.CPO_Armor, false );
		if( currentArmorValue > 0.0 )
		{
			if( forReal )
			{
				DrainStatPool( hitEvent, gamedataStatPoolType.CPO_Armor, initialDamageValue );
			}
			statPoolValue.type = dmgType;
			statPoolValue.affectedStatPool = gamedataStatPoolType.CPO_Armor;
			statPoolValue.value = MinF( initialDamageValue, currentArmorValue );
			valuesLost.PushBack( statPoolValue );
			initialDamageValue = initialDamageValue - currentArmorValue;
		}
	}

	private static function ApplyDamageToOverShieldSingle( hitEvent : gameHitEvent, dmgType : gamedataDamageType, out initialDamageValue : Float, forReal : Bool, valuesLost : ref< array< SDamageDealt > > )
	{
		var currentOvershieldValue : Float;
		var statPoolsSystem : StatPoolsSystem;
		var statPoolValue : SDamageDealt;
		statPoolsSystem = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() );
		currentOvershieldValue = statPoolsSystem.GetStatPoolValue( hitEvent.target.GetEntityID(), gamedataStatPoolType.Overshield, false );
		if( currentOvershieldValue <= 0.0 )
		{
			return;
		}
		if( forReal )
		{
			DrainStatPool( hitEvent, gamedataStatPoolType.Overshield, initialDamageValue );
		}
		statPoolValue.type = dmgType;
		statPoolValue.affectedStatPool = gamedataStatPoolType.Overshield;
		statPoolValue.value = MinF( initialDamageValue, currentOvershieldValue );
		valuesLost.PushBack( statPoolValue );
		initialDamageValue = initialDamageValue - currentOvershieldValue;
		RPGManager.AwardExperienceFromResourceSpent( ( ( PlayerPuppet )( hitEvent.target ) ), statPoolValue.value, gamedataStatPoolType.Overshield, hitEvent );
	}

	public static function ApplyStatusEffectDamage( hitEvent : gameHitEvent, resistPoolRecord : StatPool_Record, statusEffectID : TweakDBID )
	{
		var target : GameObject;
		var instigator : weak< GameObject >;
		var statPoolsSystem : StatPoolsSystem;
		var targetID : EntityID;
		var statusEffectListener : StatusEffectTriggerListener;
		var resistPool : gamedataStatPoolType;
		var baseDamage : Float;
		var finalDamage : Float;
		var resistanceFactor : Float;
		target = hitEvent.target;
		instigator = hitEvent.attackData.GetInstigator();
		targetID = target.GetEntityID();
		statPoolsSystem = GameInstance.GetStatPoolsSystem( target.GetGame() );
		resistPool = resistPoolRecord.StatPoolType();
		baseDamage = hitEvent.attackComputed.GetTotalAttackValue( gamedataStatPoolType.Health );
		if( !( statPoolsSystem.IsStatPoolAdded( targetID, resistPool ) ) )
		{
			statusEffectListener = new StatusEffectTriggerListener;
			statusEffectListener.m_owner = target;
			statusEffectListener.m_statusEffect = statusEffectID;
			statusEffectListener.m_statPoolType = resistPool;
			statusEffectListener.m_instigator = instigator;
			statPoolsSystem.RequestAddingStatPool( targetID, resistPoolRecord.GetID() );
			statPoolsSystem.RequestRegisteringListener( targetID, resistPool, statusEffectListener );
			GameObject.AddStatusEffectTriggerListener( target, statusEffectListener );
		}
		resistanceFactor = 0.5;
		finalDamage = baseDamage * resistanceFactor;
		StatPoolsManager.DrainStatPool( hitEvent, resistPool, finalDamage );
	}

	public static function DrainStatPool( hitEvent : gameHitEvent, statPoolType : gamedataStatPoolType, value : Float )
	{
		var percValueToDrain, currentPercValue, currentPointValue, minHealthPercentValue, dmgExpPercent : Float;
		var statPoolsSystem : StatPoolsSystem;
		var targetID : StatsObjectID;
		var godModeSystem : GodModeSystem;
		var isTargetImmortal : Bool;
		var processVendettaEvent : ProcessVendettaAchievementEvent;
		targetID = hitEvent.target.GetEntityID();
		godModeSystem = GameInstance.GetGodModeSystem( GetGameInstance() );
		statPoolsSystem = GameInstance.GetStatPoolsSystem( hitEvent.target.GetGame() );
		currentPercValue = statPoolsSystem.GetStatPoolValue( targetID, statPoolType );
		currentPointValue = statPoolsSystem.ToPoints( targetID, statPoolType, currentPercValue );
		if( currentPointValue <= 0.0 )
		{
			currentPointValue = 1.0;
		}
		percValueToDrain = ( value * currentPercValue ) / currentPointValue;
		minHealthPercentValue = hitEvent.attackData.GetMinimumHealthPercent();
		isTargetImmortal = godModeSystem.HasGodMode( hitEvent.target.GetEntityID(), gameGodModeType.Immortal );
		if( isTargetImmortal )
		{
			minHealthPercentValue = MaxF( minHealthPercentValue, 1.0 );
		}
		if( ( gamedataStatPoolType.Health == statPoolType && ( minHealthPercentValue > 0.0 ) ) && ( ( currentPercValue - percValueToDrain ) < minHealthPercentValue ) )
		{
			percValueToDrain = MaxF( currentPercValue - minHealthPercentValue, 0.0 );
		}
		percValueToDrain = MinF( percValueToDrain, currentPercValue );
		if( percValueToDrain == 0.0 )
		{
			return;
		}
		statPoolsSystem.RequestChangingStatPoolValue( targetID, statPoolType, -( percValueToDrain ), hitEvent.attackData.GetInstigator(), true, true, hitEvent.attackData.HasFlag( hitFlag.IgnoreStatPoolCustomLimit ) );
		dmgExpPercent = percValueToDrain;
		if( ( ( ( isTargetImmortal && ( ( currentPercValue - percValueToDrain ) <= 1.0 ) ) && hitEvent.target.IsPlayer() ) && hitEvent.attackData.GetInstigator().IsNPC() ) && statPoolType == gamedataStatPoolType.Health )
		{
			processVendettaEvent = new ProcessVendettaAchievementEvent;
			processVendettaEvent.deathInstigator = hitEvent.attackData.GetInstigator();
			hitEvent.target.QueueEvent( processVendettaEvent );
		}
		if( dmgExpPercent > 0.0 )
		{
			RPGManager.AwardExperienceFromDamage( hitEvent, dmgExpPercent );
		}
	}

	public static function IsStatPoolValid( type : gamedataStatPoolType ) : Bool
	{
		var i : Int32;
		for( i = 0; i < ( ( Int32 )( gamedataStatPoolType.Count ) ); i += 1 )
		{
			if( type == ( ( gamedataStatPoolType )( i ) ) )
			{
				return true;
			}
		}
		return false;
	}

	public static function SimulateKill( hitEvent : gameHitEvent ) : Bool
	{
		var target : GameObject;
		var curStatPoolValue : Float;
		var valueToDrain : Float;
		target = hitEvent.target;
		valueToDrain = hitEvent.attackComputed.GetTotalAttackValue( gamedataStatPoolType.Health );
		if( valueToDrain > 0.0 )
		{
			curStatPoolValue = GameInstance.GetStatPoolsSystem( target.GetGame() ).GetStatPoolValue( target.GetEntityID(), gamedataStatPoolType.Health, false );
			return ( ( valueToDrain >= curStatPoolValue ) ? ( true ) : ( false ) );
		}
		return false;
	}

}

