class Override_X2AbilityToHitCalc_StandardAim extends X2AbilityToHitCalc_StandardAim config (RWRealisticAimingAnglesHL);
//WATCHOUT!!! CHECK FOR FUNCTION PASSESWEAPONCHECK IN X2EVENTLISTENER_ABILITYPOINTS CLASS WHICH "SPECIFICALLY"
//CHECKS FOR SPECIAL HITS FROM THE "UNOVERRIDDEN" X2ABILITYTOHITCALC_STANDARDAIM!!!
//WHICH MEANS THAT THIS OVERRIDE COULD PREVENT WINNING SPECIAL ABILITYPOINTS...!!!
//NO... IT SEEMS THAT ABILITYPOINTS ARE WON EVEN WITH THIS MOD... ;)

//ADDED THIS PARAGRAPH!!!
var config float MinimumGoodAngleBonusCoverReductionPercentage;
var config float MaximumGoodAngleBonusCoverReductionPercentage;
var config float MinimumGoodAngleBonusStartsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface;
var config float MaximumGoodAngleBonusEndsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface;

var config float MinimumHeightAdvantageBonusCoverReductionPercentage;
var config float MaximumHeightAdvantageBonusCoverReductionPercentage;
var config float MinimumHeightAdvantageBonusStartsAtThisVerticalAngleBetweenLineOfFireAndCoverSurface;
var config float MaximumHeightAdvantageBonusEndsAtThisVerticalAngleBetweenLineOfFireAndCoverSurface;

var config bool  ApplyGoodAngleBonusNotOnlyForXComSoldiersButAlsoForAnyoneShooting;
var config bool  ApplyGoodAngleBonusEvenFartherThan11TilesAway;
var config float TargetingObjectsRealisticallyAllowsToMissThemAndUsesConventionalHitChanceStatsPlusThisAimingBonus;

var config bool  ApplyVerticalGoodAngleAgainstLowerTargetAsProportionalHeightAdvantage;
var config bool  ApplyVerticalBadAngleAgainstHigherTargetAsProportionalHeightDisadvantage;
var config bool  AllowFarCoverToGiveSomeCoverProtection;
var config bool  GiveHitChanceZeroWhenTargetableEnemiesAreInsteadNotTrulyInLineOfSight;
var config bool  OnlyAlertedTargetsCanHaveTheExtraProtectionOfFarCoverAndHeightDisadvantage;

var config float NO_LINE_OF_SIGHT_PENALTY;

var float FarCoverBonus;

var bool ShowChances;
var bool ShowCriticalAndDodgeChances;
var bool ShowDiceRolls;
var bool ShowOnlyForAllies;

var bool UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance;
var bool PrioritizeCriticalChanceOverDodgeChance;
var bool AlwaysRollForBothDodgeAndCriticalEvenWhenOneUnsuccesful;
var bool GuaranteedHitsCannotDodge;
var bool IndirectFireGuaranteesHit;
var bool EnableGamblerFallacySystemRoll;
//END OF ADDED PARAGRAPH!!!

var config bool OVERWATCH_BYPASS_COVER;
var config array<name> OVERWATCH_ABILITIES;
var config array<name> MOVEMENT_ABILITY;

function InternalRollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, bool bIsPrimaryTarget, const out AbilityResultContext ResultContext, out EAbilityHitResult Result, out ArmorMitigationResults ArmorMitigated, out int HitChance)
{
	local int i, RandRoll, Current, ModifiedHitChance;
	local EAbilityHitResult DebugResult, ChangeResult;
	local ArmorMitigationResults Armor;
	local XComGameState_Unit TargetState, UnitState;
	local XComGameState_Player PlayerState;
	local XComGameStateHistory History;
	local StateObjectReference EffectRef;
	local XComGameState_Effect EffectState;
	local bool bRolledResultIsAMiss, bModHitRoll;
	local bool HitsAreCrits;
	local string LogMsg;
	local ETeam CurrentPlayerTeam;
	local ShotBreakdown m_ShotBreakdown;

	//ADDED THIS PARAGRAPH!!!
	local int OriginalHitRoll;
	local float DodgeChance;
	local String AbilityName;
	local int RealHitChance;
	local int TotalHitChance;
	local string DebugRollOutput;
	//END OF ADDED PARAGRAPH!!!

	History = `XCOMHISTORY;

	`log("===" $ GetFuncName() $ "===", true, 'XCom_HitRolls');
	`log("Attacker ID:" @ kAbility.OwnerStateObject.ObjectID, true, 'XCom_HitRolls');
	`log("Target ID:" @ kTarget.PrimaryTarget.ObjectID, true, 'XCom_HitRolls');
	`log("Ability:" @ kAbility.GetMyTemplate().LocFriendlyName @ "(" $ kAbility.GetMyTemplateName() $ ")", true, 'XCom_HitRolls');

	ArmorMitigated = Armor;     //  clear out fields just in case
	HitsAreCrits = bHitsAreCrits;
	if (`CHEATMGR != none)
	{
		if (`CHEATMGR.bForceCritHits)
			HitsAreCrits = true;

		if (`CHEATMGR.bNoLuck)
		{
			`log("NoLuck cheat forcing a miss.", true, 'XCom_HitRolls');
			Result = eHit_Miss;
			return;
		}
		if (`CHEATMGR.bDeadEye)
		{
			UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
			if( !`CHEATMGR.bXComOnlyDeadEye || !UnitState.ControllingPlayerIsAI() || default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == true)
			{
				`log("DeadEye cheat forcing a hit.", true, 'XCom_HitRolls');
				Result = eHit_Success;
				if( HitsAreCrits )
					Result = eHit_Crit;
				return;
			}
		}
	}

	HitChance = GetHitChance(kAbility, kTarget, m_ShotBreakdown, true);
	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false) RandRoll = `SYNC_RAND_TYPED(100, ESyncRandType_Generic);
	Result = eHit_Miss;

	`log("=" $ GetFuncName() $ "=", true, 'XCom_HitRolls');
	`log("Final hit chance:" @ HitChance, true, 'XCom_HitRolls');

	//ADDED THIS PARAGRAPH!!!
	//HEART OF EUROLLS INJECTION FROM XCOM1
	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance)
	{
		UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
		TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));
		// Revert real hit chance manipulated by FinalizeHitChance()
		RealHitChance = Clamp(HitChance, 0, 100);
		// Roll for hit
		if (default.EnableGamblerFallacySystemRoll)
		{
		//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(RealHitChance, false, false, IsAlien);
		}
		else
		{
			RandRoll = class'Engine'.static.GetEngine().SyncRand(100, string(Name)@string(GetStateName())@string(GetFuncName()));
		}
		OriginalHitRoll = RandRoll;
		DebugResult = EAbilityHitResult(eHit_Success);
		`log("Checking table" @ DebugResult @ "(" $ RealHitChance $ ")...", true, 'XCom_HitRolls');
		`log("Random roll:" @ RandRoll, true, 'XCom_HitRolls');
		DebugRollOutput = "Hit roll:"@RandRoll;
	}
	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance)
	{
		TotalHitChance = RealHitChance;
		// EU aim rolls
		if (RandRoll < RealHitChance)
		{
			Result = eHit_Success;
			`log("MATCH!", true, 'XCom_HitRolls');
			// Hits, now roll for crits (Super priority)
			if (default.PrioritizeCriticalChanceOverDodgeChance)
			{
				if (default.EnableGamblerFallacySystemRoll)
				{
				//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Crit], true, false, IsAlien);
				}
				else
				{
					RandRoll = class'Engine'.static.GetEngine().SyncRand(100, string(Name)@string(GetStateName())@string(GetFuncName()));
				}
				DebugResult = EAbilityHitResult(eHit_Crit);
				if (default.ShowCriticalAndDodgeChances)
				{
					DebugRollOutput = DebugRollOutput@", Crit roll:"@RandRoll;
				}
				`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Crit] $ ")...", true, 'XCom_HitRolls');
				`log("Random roll:" @ RandRoll, true, 'XCom_HitRolls');
				if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Crit])
				{
					Result = eHit_Crit;
					`log("MATCH!", true, 'XCom_HitRolls');
					if (default.AlwaysRollForBothDodgeAndCriticalEvenWhenOneUnsuccesful && m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
					{
						if (default.EnableGamblerFallacySystemRoll)
						{
						//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Graze] * 100 / RealHitChance, false, true, IsAlien) * 100 / RealHitChance;
						}
						else
						{
							RandRoll = class'Engine'.static.GetEngine().SyncRand(RealHitChance, string(Name)@string(GetStateName())@string(GetFuncName()));
						}
						DebugResult = EAbilityHitResult(eHit_Graze);
						if (default.ShowCriticalAndDodgeChances && m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
						{
							DebugRollOutput = DebugRollOutput@", Dodge roll:"@RandRoll;
						}
						`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Graze] $ ")...", true, 'XCom_HitRolls');
						`log("Random roll over hit chance:" @ RandRoll, true, 'XCom_HitRolls');
						// Roll dodge over hit chance (Because of a function earlier multiplied dodge chance by actual hit chance)
						if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Graze])
						{
							Result = eHit_Success; // Dodge cancels crit and becomes a standard hit.
							`log("MATCH!", true, 'XCom_HitRolls');
						}
					}
				}
				else if (m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
				{
					if (default.EnableGamblerFallacySystemRoll)
					{
					//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Graze] * 100 / RealHitChance, false, true, IsAlien) * 100 / RealHitChance;
					}
					else
					{
						RandRoll = class'Engine'.static.GetEngine().SyncRand(RealHitChance, string(Name)@string(GetStateName())@string(GetFuncName()));
					}
					DebugResult = EAbilityHitResult(eHit_Graze);
					if (default.ShowCriticalAndDodgeChances && m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
					{
						DebugRollOutput = DebugRollOutput@", Dodge roll:"@RandRoll;
					}
					`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Graze] $ ")...", true, 'XCom_HitRolls');
					`log("Random roll over hit chance:" @ RandRoll, true, 'XCom_HitRolls');
					// Roll dodge over hit chance (Because of a function earlier multiplied dodge chance by actual hit chance)
					if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Graze])
					{
						Result = eHit_Graze;
						`log("MATCH!", true, 'XCom_HitRolls');
					}
				}
			}
			else
			{
				if (default.EnableGamblerFallacySystemRoll && m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
				{
				//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Graze] * 100 / RealHitChance, false, true, IsAlien) * 100 / RealHitChance;
				}
				else
				{
					RandRoll = class'Engine'.static.GetEngine().SyncRand(RealHitChance, string(Name)@string(GetStateName())@string(GetFuncName()));
				}
				DebugResult = EAbilityHitResult(eHit_Graze);
				if (default.ShowCriticalAndDodgeChances && m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
				{
					DebugRollOutput = DebugRollOutput@", Dodge roll:"@RandRoll;
				}
				`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Graze] $ ")...", true, 'XCom_HitRolls');
				`log("Random roll over hit chance:" @ RandRoll, true, 'XCom_HitRolls');
				// Roll dodge over hit chance (Because of a function earlier multiplied dodge chance by actual hit chance)
				if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Graze])
				{
					Result = eHit_Graze;
					`log("MATCH!", true, 'XCom_HitRolls');
					if (default.AlwaysRollForBothDodgeAndCriticalEvenWhenOneUnsuccesful)
					{
						if (default.EnableGamblerFallacySystemRoll)
						{
						//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Crit], true, false, IsAlien);
						}
						else
						{
							RandRoll = class'Engine'.static.GetEngine().SyncRand(100, string(Name)@string(GetStateName())@string(GetFuncName()));
						}
						DebugResult = EAbilityHitResult(eHit_Crit);
						if (default.ShowCriticalAndDodgeChances)
						{
							DebugRollOutput = DebugRollOutput@", Crit roll:"@RandRoll;
						}
						`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Crit] $ ")...", true, 'XCom_HitRolls');
						`log("Random roll:" @ RandRoll, true, 'XCom_HitRolls');
						if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Crit])
						{
							Result = eHit_Success; // Crit cancels dodge and becomes a standard hit.
							`log("MATCH!", true, 'XCom_HitRolls');
						}
					}

				}
				else
				{
					if (default.EnableGamblerFallacySystemRoll)
					{
					//	RandRoll = class'XComGameState_GamblerRolls'.static.StaticRoll(m_ShotBreakdown.ResultTable[eHit_Crit], true, false, IsAlien);
					}
					else
					{
						RandRoll = class'Engine'.static.GetEngine().SyncRand(100, string(Name)@string(GetStateName())@string(GetFuncName()));
					}
					DebugResult = EAbilityHitResult(eHit_Crit);
					if (default.ShowCriticalAndDodgeChances)
					{
						DebugRollOutput = DebugRollOutput@", Crit roll:"@RandRoll;
					}
					`log("Checking table" @ DebugResult @ "(" $ m_ShotBreakdown.ResultTable[eHit_Crit] $ ")...", true, 'XCom_HitRolls');
					`log("Random roll:" @ RandRoll, true, 'XCom_HitRolls');
					if (RandRoll < m_ShotBreakdown.ResultTable[eHit_Crit])
					{
						Result = eHit_Crit;
						`log("MATCH!", true, 'XCom_HitRolls');
					}
				}
			}
		}
	}
	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false)
	{
		TotalHitChance = m_ShotBreakdown.ResultTable[eHit_Success] + m_ShotBreakdown.ResultTable[eHit_Crit] + m_ShotBreakdown.ResultTable[eHit_Graze];
		TotalHitChance = Clamp(TotalHitChance, 0, 100);
		// Vanilla aim rolls
		Result = eHit_Miss;
		//THIS ABOVE WAS THE HEART OF EUROLLS INJECTION...
		//END OF ADDED PARAGRAPH!!


		`log("Random roll:" @ RandRoll, true, 'XCom_HitRolls');
		//  GetHitChance fills out m_ShotBreakdown and its ResultTable
		for (i = 0; i < eHit_Miss; ++i)     //  If we don't match a result before miss, then it's a miss.
		{
			Current += m_ShotBreakdown.ResultTable[i];
			DebugResult = EAbilityHitResult(i);
			`log("Checking table" @ DebugResult @ "(" $ Current $ ")...", true, 'XCom_HitRolls');
			if (RandRoll < Current)
			{
				Result = EAbilityHitResult(i);
				`log("MATCH!", true, 'XCom_HitRolls');
				break;
			}
		}
	}
	// Start Issue #1300
	/// HL-Docs: ref:Bugfixes; issue:1300
	/// Code block moved to be right after aim assist logic, so that if a miss is converted to a hit by aim assist, the ability will still crit if it is set up to always crit on hit.
	//if (HitsAreCrits && Result == eHit_Success)
	//	Result = eHit_Crit;
	// End Issue #1300

	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false) UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
	if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false) TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));


	//ADDED THIS PARAGRAPH!!!
	if (default.GuaranteedHitsCannotDodge && (bIndirectFire || bGuaranteedHit || TargetState == none)) // No target states are supposed to be guarantee hits
	{
		if (Result == eHit_Graze)
			Result = eHit_Success;
	}
	if (default.IndirectFireGuaranteesHit && bIndirectFire)
	{
		if (Result == eHit_Miss) // Countering the unintended effect of graze band for LW2
			Result = eHit_Success;
	}

	if (default.ShowChances && !bIndirectFire && !bGuaranteedHit && TargetState != none)
	{
		AbilityName = kAbility.GetMyFriendlyName();
		if (AbilityName == "")
		{
			AbilityName = string(kAbility.GetMyTemplateName());
		}
		if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance)
		{
			DodgeChance = (m_ShotBreakdown.ResultTable[eHit_Graze] * 100.00) / RealHitChance;
			DodgeChance = Clamp(DodgeChance, 0, 100);
		}
	}
	//END OF ADDED PARAGRAPH!!!


	// Issue #426: ChangeHitResultForX() code block moved to later in method.
	/// HL-Docs: ref:Bugfixes; issue:426
	/// Fix `X2AbilityToHitCalc_StandardAim` discarding unfavorable (for XCOM) changes to hit results from effects
	// Due to how GetModifiedHitChanceForCurrentDifficulty() is implemented, it reverts attempts to change
	// XCom Hits to Misses, or enemy misses to hits.
	// The LW2 graze band issues are related to this phenomenon, since the graze band has the effect
	// of changing some what "should" be enemy misses to hits (specifically graze result)

	// Aim Assist (miss streak prevention)
	bRolledResultIsAMiss = class'XComGameStateContext_Ability'.static.IsHitResultMiss(Result);

	//  reaction  fire shots and guaranteed hits do not get adjusted for difficulty
	if( UnitState != None &&
		!bReactionFire &&
		!bGuaranteedHit &&
		m_ShotBreakdown.SpecialGuaranteedHit == '')
	{
		PlayerState = XComGameState_Player(History.GetGameStateForObjectID(UnitState.GetAssociatedPlayerID()));
		CurrentPlayerTeam = PlayerState.GetTeam();
		`log("CurrentPlayerTeam:"@CurrentPlayerTeam, true, 'XCom_HitRolls');

		if( bRolledResultIsAMiss && CurrentPlayerTeam == eTeam_XCom )
		{
			ModifiedHitChance = GetModifiedHitChanceForCurrentDifficulty(PlayerState, TargetState, HitChance);

			//ADDED THIS PARAGRAPH!!!
			if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == true)
			{
				if( ModifiedHitChance != HitChance && OriginalHitRoll < ModifiedHitChance )
				{
					Result = eHit_Success;
					bModHitRoll = true;
					`log("*** AIM ASSIST forcing an XCom MISS to become a HIT!", true, 'XCom_HitRolls');
				}
			}
			if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false)
			{
			//END OF ADDED PARAGRAPH!!!


				if( RandRoll < ModifiedHitChance )
				{
					Result = eHit_Success;
					bModHitRoll = true;
					`log("*** AIM ASSIST forcing an XCom MISS to become a HIT!", true, 'XCom_HitRolls');
				}

			} //ADDED THIS LINE!!!

		}
		else if( !bRolledResultIsAMiss && (CurrentPlayerTeam == eTeam_Alien || CurrentPlayerTeam == eTeam_TheLost) )
		{
			ModifiedHitChance = GetModifiedHitChanceForCurrentDifficulty(PlayerState, TargetState, HitChance);


			//ADDED THIS PARAGRAPH!!!
			if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == true)
			{
				if( ModifiedHitChance != HitChance && OriginalHitRoll >= ModifiedHitChance )
				{
					Result = eHit_Miss;
					bModHitRoll = true;
					`log("*** AIM ASSIST forcing an Alien HIT to become a MISS!", true, 'XCom_HitRolls');
				}
			}
			if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false)
			{
			//END OF ADDED PARAGRAPH!!!


				if( RandRoll >= ModifiedHitChance )
				{
					Result = eHit_Miss;
					bModHitRoll = true;
					`log("*** AIM ASSIST forcing an Alien HIT to become a MISS!", true, 'XCom_HitRolls');
				}

			} //ADDED THIS LINE!!!

		}
	}

	// Start Issue #1300
	// Code block moved from earlier.
	if (HitsAreCrits && Result == eHit_Success)
		Result = eHit_Crit;
	// End Issue #1300

	`log("***HIT" @ Result, !bRolledResultIsAMiss, 'XCom_HitRolls');
	`log("***MISS" @ Result, bRolledResultIsAMiss, 'XCom_HitRolls');

	// Start Issue #426: Block moved from earlier. Only code change is for lightning reflexes,
	// because bRolledResultIsAMiss was used for both aim assist and reflexes
	if (UnitState != none && TargetState != none)
	{
		foreach UnitState.AffectedByEffects(EffectRef)
		{
			EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
			if (EffectState != none)
			{
				if (EffectState.GetX2Effect().ChangeHitResultForAttacker(UnitState, TargetState, kAbility, Result, ChangeResult))
				{
					`log("Effect" @ EffectState.GetX2Effect().FriendlyName @ "changing hit result for attacker:" @ ChangeResult,true,'XCom_HitRolls');
					Result = ChangeResult;
                                }
			}
		}
		foreach TargetState.AffectedByEffects(EffectRef)
		{
			EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
			if (EffectState != none)
			{
				if (EffectState.GetX2Effect().ChangeHitResultForTarget(EffectState, UnitState, TargetState, kAbility, bIsPrimaryTarget, Result, ChangeResult))
				{
					`log("Effect" @ EffectState.GetX2Effect().FriendlyName @ "changing hit result for target:" @ ChangeResult, true, 'XCom_HitRolls');
					Result = ChangeResult;
                                }
			}
		}
	}

	if (TargetState != none)
	{
		//  Check for Lightning Reflexes
		if (bReactionFire && TargetState.bLightningReflexes && !class'XComGameStateContext_Ability'.static.IsHitResultMiss(Result))
		{
			Result = eHit_LightningReflexes;
			`log("Lightning Reflexes triggered! Shot will miss.", true, 'XCom_HitRolls');
		}

		//ADDED THIS LINE!!!
		if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance) class'X2AbilityArmorHitRolls'.static.RollArmorMitigation(m_ShotBreakdown.ArmorMitigation, ArmorMitigated, TargetState); //  add armor mitigation (regardless of hit/miss as some shots deal damage on a miss)

	}
	// End Issue #426

	if (UnitState != none && TargetState != none)
	{
		LogMsg = class'XLocalizedData'.default.StandardAimLogMsg;
		LogMsg = repl(LogMsg, "#Shooter", UnitState.GetName(eNameType_RankFull));
		LogMsg = repl(LogMsg, "#Target", TargetState.GetName(eNameType_RankFull));
		LogMsg = repl(LogMsg, "#Ability", kAbility.GetMyTemplate().LocFriendlyName);
		LogMsg = repl(LogMsg, "#Chance", bModHitRoll ? ModifiedHitChance : HitChance);
		LogMsg = repl(LogMsg, "#Roll", RandRoll);
		LogMsg = repl(LogMsg, "#Result", class'X2TacticalGameRulesetDataStructures'.default.m_aAbilityHitResultStrings[Result]);
		`COMBATLOG(LogMsg);
	}
}


protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
	local XComGameState_Unit UnitState, TargetState;
	local XComGameState_Item SourceWeapon;
	local GameRulesCache_VisibilityInfo VisInfo;
	local array<X2WeaponUpgradeTemplate> WeaponUpgrades;
	local int i, iWeaponMod, iRangeModifier, Tiles;
	local ShotBreakdown EmptyShotBreakdown;
	local array<ShotModifierInfo> EffectModifiers;
	local StateObjectReference EffectRef;
	local XComGameState_Effect EffectState;
	local XComGameStateHistory History;
	local bool bFlanking, bIgnoreGraze, bSquadsight;
	local string IgnoreGrazeReason;
	local X2AbilityTemplate AbilityTemplate;
	local array<XComGameState_Effect> StatMods;
	local array<float> StatModValues;
	local X2Effect_Persistent PersistentEffect;
	local array<X2Effect_Persistent> UniqueToHitEffects;
	local float FinalAdjust, CoverValue, AngleToCoverModifier, Alpha;
	local int TileDistance;

	//ADDED THIS PARAGRAPH!!!
	local TTile ShooterTile;
	local TTile TargetTile;
	local TTile ShooterHead;
	local TTile TargetHead;
	local float ShooterHeadRelativeElevation;
	local float VerticalAngle;
	local float GoodAngleBonus;
	local float HeightAdvantageBonus;	 //ALWAYS FLOATS INSTEAD OF INTS!!!!
	local ECoverType CoverTypeOfTileAboveTarget;

        local TTile ShooterPeekTile, LosTile, TargetPeekTile;
	local VoxelRaytraceCheckResult RayTrace;

	local int ExistingCoverHeight, TargetHeight, FarCover;
	//END OF ADDED PARAGRAPH!!!

	local bool NoLineOfSight;

	local vector ShooterPeekVec, LosVec;
	local int floor;

        // ========================================
        // From -bg-'s EU Aim Rolls mod
        // ========================================
	local XComGameState LastGameState;
	local bool isRunningOverwatch;
	local XComGameStateContext_Ability LastAbilityContext;

	/// HL-Docs: feature:GetHitChanceEvents; issue:1031; tags:tactical
	/// WARNING! Triggering events in `X2AbilityToHitCalc::GetHitChance()` and other functions called by this function
	/// may freeze (hard hang) the game under certain circumstances.
	///
	/// In our experiments, the game would hang when the player used a moving melee ability when an event was triggered
	/// in `UITacticalHUD_AbilityContainer::ConfirmAbility()`  right above the
	/// `XComPresentationLayer(Owner.Owner).PopTargetingStates();` line or anywhere further down the script trace,
	/// while another event was also triggered in `GetHitChance()` or anywhere further down the script trace.
	///
	/// The game hangs while executing UI code, but it is the event in the To Hit Calculation logic that induces it.
	/// The speculation is that triggering events in `GetHitChance()` somehow corrupts the event manager, or it
	/// could be a threading issue.

	`log("=" $ GetFuncName() $ "=", bDebugLog, 'XCom_HitRolls');

	//  @TODO gameplay handle non-unit targets
	History = `XCOMHISTORY;
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID( kAbility.OwnerStateObject.ObjectID ));
	TargetState = XComGameState_Unit(History.GetGameStateForObjectID( kTarget.PrimaryTarget.ObjectID ));

	//ADDED THIS PARAGRAPH!!!
	UnitState  .GetKeystoneVisibilityLocation(ShooterTile);
	TargetState.GetKeystoneVisibilityLocation(TargetTile );
	`TACTICALRULES.VisibilityMgr.GetVisibilityInfo(UnitState.ObjectID, TargetState.ObjectID, VisInfo);
	CoverTypeOfTileAboveTarget = CoverTypeOfTileAboveCover(ShooterTile, TargetTile, VisInfo.TargetCoverAngle);
	GoodAngleBonus = 0; //JUST IN CASE... WIERD BEHAVIOR
	//END OF ADDED PARAGRAPH!!!

	if (kAbility != none)
	{
		AbilityTemplate = kAbility.GetMyTemplate();
		SourceWeapon = kAbility.GetSourceWeapon();

                // ========================================
                // From -bg-'s EU Aim Rolls mod
                // ========================================
		// Check if it's overwatch
		if (default.OVERWATCH_ABILITIES.Find(kAbility.GetMyTemplateName()) != INDEX_NONE)
		{
			// Check if target is moving
			// Is interrupt state, so using same game state
			LastGameState = History.GetGameStateFromHistory(History.GetCurrentHistoryIndex());
			LastAbilityContext = XComGameStateContext_Ability(LastGameState.GetContext());
			if (LastAbilityContext != none)
				if (default.MOVEMENT_ABILITY.Find(LastAbilityContext.InputContext.AbilityTemplateName) != INDEX_NONE)
					isRunningOverwatch = true;
		}
	}

	//  reset shot breakdown
	m_ShotBreakdown = EmptyShotBreakdown;

	//  check for a special guaranteed hit
	m_ShotBreakdown.SpecialGuaranteedHit = UnitState.CheckSpecialGuaranteedHit(kAbility, SourceWeapon, TargetState);
	m_ShotBreakdown.SpecialCritLabel = UnitState.CheckSpecialCritLabel(kAbility, SourceWeapon, TargetState);

	//  add all of the built-in modifiers
	if (bGuaranteedHit || m_ShotBreakdown.SpecialGuaranteedHit != '')
	{
		//  call the super version to bypass our check to ignore success mods for guaranteed hits
		super.AddModifier(100, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog); //WATCHOUT!!! SUPER. IS NOT ANYMORE THE PARENT
		super(X2AbilitytoHitCalc).AddModifier(100, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog); //OF ABILITYTOHIT_STANDARDAIM BUT BUT THE PARENT
	}
	else if (bIndirectFire)
	{
		m_ShotBreakdown.HideShotBreakdown = true;
		AddModifier(100, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
	}

	// Start Issue #1298
	/// HL-Docs: ref:Bugfixes; issue:1298
	/// Add 100 crit chance to guaranteed crit abilities for the purposes of UI.
	if (bHitsAreCrits)
	{
		//  call the super version to bypass our check to ignore crit chance mods for guaranteed crits
		super.AddModifier(100, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
	}
	// End Issue #1298

	// Issue #346: AddModifier(BuiltIn...Mod) block moved later in method.
	/// HL-Docs: ref:Bugfixes; issue:346
	/// Prevent `X2AbilityToHitCalc_StandardAim` from applying BuiltInHitMod and BuiltInCritMod against non-units.

	if (UnitState != none && TargetState == none)
	{
	    // when targeting non-units, we have a 100% chance to hit. They can't dodge or otherwise
	    // mess up our shots
	    m_ShotBreakdown.HideShotBreakdown = true;
	    AddModifier(100, class'XLocalizedData'.default.OffenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
	}
	else if (UnitState != none && TargetState != none)
	{
		// Start Issue #346: Block moved from earlier.
		AddModifier(BuiltInHitMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
		AddModifier(BuiltInCritMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
		// End Issue #346

		if (!bIndirectFire)
		{
			// StandardAim (with direct fire) will require visibility info between source and target (to check cover).
			if (`TACTICALRULES.VisibilityMgr.GetVisibilityInfo(UnitState.ObjectID, TargetState.ObjectID, VisInfo))
			{
				if (UnitState.CanFlank() && TargetState.GetMyTemplate().bCanTakeCover && VisInfo.TargetCover == CT_None)
					bFlanking = true;
				if (VisInfo.bClearLOS && !VisInfo.bVisibleGameplay)
					bSquadsight = true;

				//  Add basic offense and defense values
				AddModifier(UnitState.GetBaseStat(eStat_Offense), class'XLocalizedData'.default.OffenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
				// Single Line Change for Issue #313
				/// HL-Docs: ref:GetStatModifiersFixed
				UnitState.GetStatModifiersFixed(eStat_Offense, StatMods, StatModValues);
				for (i = 0; i < StatMods.Length; ++i)
				{
					AddModifier(int(StatModValues[i]), StatMods[i].GetX2Effect().FriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
				}
				//  Flanking bonus (do not apply to overwatch shots)
				if (bFlanking && !bReactionFire && !bMeleeAttack)
				{
					AddModifier(UnitState.GetCurrentStat(eStat_FlankingAimBonus), class'XLocalizedData'.default.FlankingAimBonus, m_ShotBreakdown, eHit_Success, bDebugLog);
				}
				//  Squadsight penalty
				if (bSquadsight)
				{
					Tiles = UnitState.TileDistanceBetween(TargetState);
					//  remove number of tiles within visible range (which is in meters, so convert to units, and divide that by tile size)
					Tiles -= UnitState.GetVisibilityRadius() * class'XComWorldData'.const.WORLD_METERS_TO_UNITS_MULTIPLIER / class'XComWorldData'.const.WORLD_StepSize;
					if (Tiles > 0)      //  pretty much should be since a squadsight target is by definition beyond sight range. but...
						AddModifier(default.SQUADSIGHT_DISTANCE_MOD * Tiles, class'XLocalizedData'.default.SquadsightMod, m_ShotBreakdown, eHit_Success, bDebugLog);
					else if (Tiles == 0)	//	right at the boundary, but squadsight IS being used so treat it like one tile
						AddModifier(default.SQUADSIGHT_DISTANCE_MOD, class'XLocalizedData'.default.SquadsightMod, m_ShotBreakdown, eHit_Success, bDebugLog);
				}

				//  Check for modifier from weapon
				if (SourceWeapon != none)
				{
					iWeaponMod = SourceWeapon.GetItemAimModifier();
					AddModifier(iWeaponMod, class'XLocalizedData'.default.WeaponAimBonus, m_ShotBreakdown, eHit_Success, bDebugLog);

					WeaponUpgrades = SourceWeapon.GetMyWeaponUpgradeTemplates();
					for (i = 0; i < WeaponUpgrades.Length; ++i)
					{
						if (WeaponUpgrades[i].AddHitChanceModifierFn != None)
						{
							if (WeaponUpgrades[i].AddHitChanceModifierFn(WeaponUpgrades[i], VisInfo, iWeaponMod))
							{
								AddModifier(iWeaponMod, WeaponUpgrades[i].GetItemFriendlyName(), m_ShotBreakdown, eHit_Success, bDebugLog);
							}
						}
					}
				}
				//  Target defense
				// Start Issue #1295
				// Add separate entries for different sources of Defense on the target unit rather than one entry with all sources of Defense rolled into it.
				//AddModifier(-TargetState.GetCurrentStat(eStat_Defense), class'XLocalizedData'.default.DefenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
				AddModifier(-TargetState.GetBaseStat(eStat_Defense), class'XLocalizedData'.default.DefenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);

				/// HL-Docs: ref:GetStatModifiersFixed
                TargetState.GetStatModifiersFixed(eStat_Defense, StatMods, StatModValues);
                for (i = 0; i < StatMods.Length; ++i)
                {
                    AddModifier(-int(StatModValues[i]), StatMods[i].GetX2Effect().FriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
                }
				// End Issue #1295

				//  Add weapon range
				if (SourceWeapon != none)
				{
					iRangeModifier = GetWeaponRangeModifier(UnitState, TargetState, SourceWeapon);
					AddModifier(iRangeModifier, class'XLocalizedData'.default.WeaponRange, m_ShotBreakdown, eHit_Success, bDebugLog);
				}
				//  Cover modifiers
				if (bMeleeAttack)
				{
					AddModifier(MELEE_HIT_BONUS, class'XLocalizedData'.default.MeleeBonus, m_ShotBreakdown, eHit_Success, bDebugLog);
				}
				else
				{

                                        `log("TargetState.CanTakeCover():"@TargetState.CanTakeCover()@", isRunningOverwatch:"@isRunningOverwatch, true, 'XCom_HitRolls');

					//  Add cover penalties
					if (TargetState.CanTakeCover()
                                                // ========================================
                                        	// From -bg-'s EU Aim Rolls mod
                                        	// ========================================
                                        	&& (!OVERWATCH_BYPASS_COVER || !isRunningOverwatch))
					{
						// if any cover is being taken, factor in the angle to attack
						if( VisInfo.TargetCover != CT_None && !bIgnoreCoverBonus )
						{
							switch( VisInfo.TargetCover )
							{
							case CT_MidLevel:           //  half cover
								AddModifier(-LOW_COVER_BONUS, class'XLocalizedData'.default.TargetLowCover, m_ShotBreakdown, eHit_Success, bDebugLog);
								CoverValue = LOW_COVER_BONUS;
								break;
							case CT_Standing:           //  High Cover
								AddModifier(-HIGH_COVER_BONUS, class'XLocalizedData'.default.TargetHighCover, m_ShotBreakdown, eHit_Success, bDebugLog);
								CoverValue = HIGH_COVER_BONUS;
								break;
							}

							TileDistance = UnitState.TileDistanceBetween(TargetState);

							// from Angle 0 -> MIN_ANGLE_TO_COVER, receive full MAX_ANGLE_BONUS_MOD
							// As Angle increases from MIN_ANGLE_TO_COVER -> MAX_ANGLE_TO_COVER, reduce bonus received by lerping MAX_ANGLE_BONUS_MOD -> MIN_ANGLE_BONUS_MOD
							// Above MAX_ANGLE_TO_COVER, receive no bonus
							if( VisInfo.TargetCoverAngle < 90-default.MinimumGoodAngleBonusStartsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface && (TileDistance <= MAX_TILE_DISTANCE_TO_COVER || default.ApplyGoodAngleBonusEvenFartherThan11TilesAway) && (UnitState.GetTeam() == eTeam_XCom || default.ApplyGoodAngleBonusNotOnlyForXComSoldiersButAlsoForAnyoneShooting))
							{
								Alpha = FClamp((VisInfo.TargetCoverAngle - default.MaximumGoodAngleBonusEndsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface) / (default.MinimumGoodAngleBonusStartsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface - default.MaximumGoodAngleBonusEndsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface), 0.0, 1.0);
								AngleToCoverModifier = Lerp(default.MaximumGoodAngleBonusCoverReductionPercentage /100, default.MinimumGoodAngleBonusCoverReductionPercentage /100, Alpha);
								GoodAngleBonus = Round(CoverValue * AngleToCoverModifier);
								`log("GoodAngleBonus:"@GoodAngleBonus, true, 'XCom_HitRolls');
								AddModifier(GoodAngleBonus, class'XLocalizedData'.default.AngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
							}
						}
					}

					//  Add height advantage
					if (UnitState.HasHeightAdvantageOver(TargetState, true) && default.ApplyVerticalGoodAngleAgainstLowerTargetAsProportionalHeightAdvantage != true)
					{
						AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightAdvantageBonus, class'XLocalizedData'.default.HeightAdvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
					}

					//  Check for height disadvantage
					if (TargetState.HasHeightAdvantageOver(UnitState, false) && default.ApplyVerticalGoodAngleAgainstLowerTargetAsProportionalHeightAdvantage != true)
					{
						AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightDisadvantagePenalty, class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
					}

					//REPLACED 'STUPID' PARAGRAPH ABOVE
					//WITH PARAGRAPH BELOW!!!
					ShooterHead = ShooterTile; ShooterHead.Z = `XWORLD.GetFloorTileZ(ShooterHead) + UnitState.UnitHeight;
					TargetHead = TargetTile;  TargetHead.Z = `XWORLD.GetFloorTileZ(TargetHead ) + TargetState.UnitHeight;

					ShooterHeadRelativeElevation = ShooterHead.Z - TargetHead.Z; //TARGETEXTRAHEIGHT MUST NOT BE CONSIDERED BUT TEST IT FOR A WHILE TO SEE IF IT IS CONSIDERED WHILE ALIENS ARE USING THIS FUNCTION
					TileDistance = UnitState.TileDistanceBetween(TargetState);
					VerticalAngle = atan(ShooterHeadRelativeElevation / TileDistance) / (2*PI) * 360; //GOD BLESS TRIGONOMETRY

					if (default.ApplyVerticalGoodAngleAgainstLowerTargetAsProportionalHeightAdvantage && ShooterHeadRelativeElevation > 0 && ((VisInfo.TargetCover == CT_MidLevel)    //HEIGHT ADVANTAGE REDUCES ONLY LOW COVER .... OR SHOULD IT???
						|| (VisInfo.TargetCover == CT_Standing && CoverTypeOfTileAboveTarget == CT_None)))
					{ //...WELL DEPENDS ON WETHER IT'S A VERTICAL TREE OR AN ORIZONTAL HIGHCOVER FENCE... ;)
						if (GoodAngleBonus != 0) CoverValue = CoverValue - GoodAngleBonus;				  //HEIGHT ADVANTAGE APPLIED "AFTER" GOOD ANGLE IS APPLIED (NOT SEPARATELY,
																										  //OTHERWISE IT COULD SUBTRACT 2 VALUES CUMULATEVELY HIGHER THAN COVERVALUE ITSELF)
						Alpha = FClamp(((90-VerticalAngle) - default.MaximumHeightAdvantageBonusEndsAtThisVerticalAngleBetweenLineOfFireAndCoverSurface) / (default.MinimumHeightAdvantageBonusStartsAtThisVerticalAngleBetweenLineOfFireAndCoverSurface - default.MaximumHeightAdvantageBonusEndsAtThisVerticalAngleBetweenLineOfFireAndCoverSurface), 0.0, 1.0);
						AngleToCoverModifier = Lerp(default.MaximumHeightAdvantageBonusCoverReductionPercentage/100, default.MinimumHeightAdvantageBonusCoverReductionPercentage/100  , Alpha);
						HeightAdvantageBonus = round(AngleToCoverModifier * CoverValue);
						`log("HeightAdvantageBonus:"@HeightAdvantageBonus, true, 'XCom_HitRolls');
						AddModifier(HeightAdvantageBonus, class'XLocalizedData'.default.HeightAdvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
					}

					if ((default.AllowFarCoverToGiveSomeCoverProtection || default.GiveHitChanceZeroWhenTargetableEnemiesAreInsteadNotTrulyInLineOfSight)
                                                // ========================================
                                        	// From -bg-'s EU Aim Rolls mod
                                        	// ========================================
                                        	&& (!OVERWATCH_BYPASS_COVER || !isRunningOverwatch))
					{
						// Both these modifiers are based off far cover
						FarCover = 0;

						ShooterPeekTile = VisInfo.SourceTile;
						ShooterPeekTile.Z += 1;
						`log("ShooterPeekTile:"@ShooterPeekTile.X@","@ShooterPeekTile.Y@","@ShooterPeekTile.Z, true, 'XCom_HitRolls');

						TargetPeekTile = VisInfo.DestTile;
						`log("TargetTile:"@TargetTile.X@","@TargetTile.Y@","@TargetTile.Z, true, 'XCom_HitRolls');
						`log("TargetPeekTile:"@TargetPeekTile.X@","@TargetPeekTile.Y@","@TargetPeekTile.Z, true, 'XCom_HitRolls');
						`log("Target Height:"@TargetState.UnitHeight, true, 'XCom_HitRolls');

						if (VisInfo.TargetCover == CT_None)
						{
							ExistingCoverHeight = 0;
							TargetHeight = TargetState.UnitHeight;
						}
						else if (TargetState.CanTakeCover())
						{
							if (VisInfo.TargetCover == CT_MidLevel)
							{
								ExistingCoverHeight = 1;
								TargetHeight = TargetState.UnitHeight - 1;  // Crouched behind low cover
							}
							else
							{
								ExistingCoverHeight = 2;
								TargetHeight = TargetState.UnitHeight;
							}
						}

						// FarCover to a tile which may already be granting cover
						FarCover = 0;
						NoLineOfSight = true;
						ShooterPeekVec = `XWORLD.GetPositionFromTileCoordinates(ShooterPeekTile);

						LosTile = TargetTile;
						LosVec = `XWORLD.GetPositionFromTileCoordinates(LosTile);
						Floor = LosVec.Z;
						for (i = 16; i < TargetState.UnitHeight * 64; i += 16)
						{
							LosVec.Z = Floor + i;
							if (`XWORLD.VoxelRaytrace_Locations(ShooterPeekVec, LosVec, RayTrace))
							{
								// Don't apply far cover to existing cover or above the target's head
								if (i > ExistingCoverHeight*64 && i <= TargetHeight*64)
								{
									`log("LOS blocked by far cover vector:"@i@","@VisInfo.TargetCover@","@RayTrace.BlockedTile.X@","@RayTrace.BlockedTile.Y@","@RayTrace.BlockedTile.Z, true, 'XCom_HitRolls');
									FarCover += 1;
								}
								else if (i <= ExistingCoverHeight*64)
								{

									`log("Hit existing cover:"@i@RayTrace.BlockedTile.X@","@RayTrace.BlockedTile.Y@","@RayTrace.BlockedTile.Z, true, 'XCom_HitRolls');
								}
							}
							else if (i <= TargetHeight*64)
							{
								if (NoLineOfSight) `log("Seen target:"@i, true, 'XCom_HitRolls');
								NoLineOfSight = false;
							}
						}

						// FarCover for which cover hasn't been determined already
						LosTile = TargetPeekTile;
						LosTile.Z = TargetTile.Z; // not based on peek tile as that can be on a different level
						LosVec = `XWORLD.GetPositionFromTileCoordinates(LosTile);
						for (i = 16; i < TargetState.UnitHeight * 64; i += 16)
						{
							LosVec.Z = Floor + i;
							if (`XWORLD.VoxelRaytrace_Locations(ShooterPeekVec, LosVec, RayTrace))
							{
								`log("LOS blocked by far cover vector:"@i@","@RayTrace.BlockedTile.X@","@RayTrace.BlockedTile.Y@","@RayTrace.BlockedTile.Z, true, 'XCom_HitRolls');
								FarCover += 1;
							}
							else // Target is always standing when peeking
							{
								if (NoLineOfSight) `log("Seen target:"@i, true, 'XCom_HitRolls');
								NoLineOfSight = false;
							}
						}

						if (default.GiveHitChanceZeroWhenTargetableEnemiesAreInsteadNotTrulyInLineOfSight && NoLineOfSight)
						{
							`log("NoLineOfSightMalus:"@default.NO_LINE_OF_SIGHT_PENALTY, true, 'XCom_HitRolls');
							AddModifier(NO_LINE_OF_SIGHT_PENALTY, "No Line of Sight", m_ShotBreakdown, eHit_Success, bDebugLog);
						}
						else if (default.AllowFarCoverToGiveSomeCoverProtection && (TargetState.GetTeam() == eTeam_XCom || TargetState.GetCurrentStat(eStat_AlertLevel)==`ALERT_LEVEL_RED || (TargetState.GetCurrentStat(eStat_AlertLevel)!=`ALERT_LEVEL_RED && !default.OnlyAlertedTargetsCanHaveTheExtraProtectionOfFarCoverAndHeightDisadvantage)))
						{
							// Big things are easy to hit even if partially covered
							if (TargetState.UnitHeight > 2)
							{
								`log("FarCover base:"@FarCover, true, 'XCom_HitRolls');
								FarCover = Max(0, FarCover - (TargetState.UnitHeight - 2) * 8);
								`log("FarCover after big target factor:"@FarCover, true, 'XCom_HitRolls');
							}

							// Scale far cover to high cover against possible tile blocks for a human sized target
							FarCoverBonus = FarCover * HIGH_COVER_BONUS / 14;
							`log("FarCoverBonus:"@round(-FarCoverBonus), true, 'XCom_HitRolls');
							AddModifier(round(-FarCoverBonus), "Far Cover", m_ShotBreakdown, eHit_Success, bDebugLog);
						}
					}
				//END OF REPLACED
				//PARAGRAPH... ;)

				}
			}
			if (UnitState.IsConcealed())
			{
				`log("Shooter is concealed, target cannot dodge.", bDebugLog, 'XCom_HitRolls');
			}
			else
			{
				if (SourceWeapon == none || SourceWeapon.CanWeaponBeDodged())
				{
					if (TargetState.CanDodge(UnitState, kAbility))
					{
						AddModifier(TargetState.GetCurrentStat(eStat_Dodge), class'XLocalizedData'.default.DodgeStat, m_ShotBreakdown, eHit_Graze, bDebugLog);
					}
					else
					{
						`log("Target cannot dodge due to some gameplay effect.", bDebugLog, 'XCom_HitRolls');
					}
				}
			}
		}

		//  Now check for critical chances.
		if (bAllowCrit)
		{
			AddModifier(UnitState.GetBaseStat(eStat_CritChance), class'XLocalizedData'.default.CharCritChance, m_ShotBreakdown, eHit_Crit, bDebugLog);
			// Single Line Change for Issue #313
			/// HL-Docs: ref:GetStatModifiersFixed
			UnitState.GetStatModifiersFixed(eStat_CritChance, StatMods, StatModValues);
			for (i = 0; i < StatMods.Length; ++i)
			{
				AddModifier(int(StatModValues[i]), StatMods[i].GetX2Effect().FriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
			}
			if (bSquadsight)
			{
				AddModifier(default.SQUADSIGHT_CRIT_MOD, class'XLocalizedData'.default.SquadsightMod, m_ShotBreakdown, eHit_Crit, bDebugLog);
			}
			if (SourceWeapon !=  none)
			{
				AddModifier(SourceWeapon.GetItemCritChance(), class'XLocalizedData'.default.WeaponCritBonus, m_ShotBreakdown, eHit_Crit, bDebugLog);

				// Issue #237 start, let upgrades modify the crit chance of the breakdown
				WeaponUpgrades = SourceWeapon.GetMyWeaponUpgradeTemplates();
				for (i = 0; i < WeaponUpgrades.Length; ++i)
				{
					// Make sure we check to only use anything from the ini that we've specified doesn't use an Effect to modify crit chance
					// Everything that does use an Effect, e.g. base game Laser Sights, get added in about 23 lines down from here
					if (WeaponUpgrades[i].AddCritChanceModifierFn != None && default.CritUpgradesThatDontUseEffects.Find(WeaponUpgrades[i].DataName) != INDEX_NONE)
					{
						if (WeaponUpgrades[i].AddCritChanceModifierFn(WeaponUpgrades[i], iWeaponMod))
						{
							AddModifier(iWeaponMod, WeaponUpgrades[i].GetItemFriendlyName(), m_ShotBreakdown, eHit_Crit, bDebugLog);
						}
					}
				}
				// Issue #237 end
			}
			if (bFlanking && !bMeleeAttack)
			{
				if (`XENGINE.IsMultiplayerGame())
				{
					AddModifier(default.MP_FLANKING_CRIT_BONUS, class'XLocalizedData'.default.FlankingCritBonus, m_ShotBreakdown, eHit_Crit, bDebugLog);
				}
				else
				{
					AddModifier(UnitState.GetCurrentStat(eStat_FlankingCritChance), class'XLocalizedData'.default.FlankingCritBonus, m_ShotBreakdown, eHit_Crit, bDebugLog);
				}
			}
		}
		foreach UnitState.AffectedByEffects(EffectRef)
		{
			EffectModifiers.Length = 0;
			EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
			if (EffectState == none)
				continue;

			PersistentEffect = EffectState.GetX2Effect();
			if (PersistentEffect == none)
				continue;

			if (UniqueToHitEffects.Find(PersistentEffect) != INDEX_NONE)
				continue;

			PersistentEffect.GetToHitModifiers(EffectState, UnitState, TargetState, kAbility, class'X2AbilitytoHitCalc_StandardAim', bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
			if (EffectModifiers.Length > 0)
			{
				if (PersistentEffect.UniqueToHitModifiers())
					UniqueToHitEffects.AddItem(PersistentEffect);

				for (i = 0; i < EffectModifiers.Length; ++i)
				{
					if (!bAllowCrit && EffectModifiers[i].ModType == eHit_Crit)
					{
						if (!PersistentEffect.AllowCritOverride())
							continue;
					}
					AddModifier(EffectModifiers[i].Value, EffectModifiers[i].Reason, m_ShotBreakdown, EffectModifiers[i].ModType, bDebugLog);
				}
			}
			if (PersistentEffect.ShotsCannotGraze())
			{
				bIgnoreGraze = true;
				IgnoreGrazeReason = PersistentEffect.FriendlyName;
			}
		}
		UniqueToHitEffects.Length = 0;
		if (TargetState.AffectedByEffects.Length > 0)
		{
			foreach TargetState.AffectedByEffects(EffectRef)
			{
				EffectModifiers.Length = 0;
				EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
				if (EffectState == none)
					continue;

				PersistentEffect = EffectState.GetX2Effect();
				if (PersistentEffect == none)
					continue;

				if (UniqueToHitEffects.Find(PersistentEffect) != INDEX_NONE)
					continue;

				PersistentEffect.GetToHitAsTargetModifiers(EffectState, UnitState, TargetState, kAbility, class'X2AbilitytoHitCalc_StandardAim', bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
				if (EffectModifiers.Length > 0)
				{
					if (PersistentEffect.UniqueToHitAsTargetModifiers())
						UniqueToHitEffects.AddItem(PersistentEffect);

					for (i = 0; i < EffectModifiers.Length; ++i)
					{
						if (!bAllowCrit && EffectModifiers[i].ModType == eHit_Crit)
							continue;
						if (bIgnoreGraze && EffectModifiers[i].ModType == eHit_Graze)
							continue;
						AddModifier(EffectModifiers[i].Value, EffectModifiers[i].Reason, m_ShotBreakdown, EffectModifiers[i].ModType, bDebugLog);
					}
				}
			}
		}
		//  Remove graze if shooter ignores graze chance.
		if (bIgnoreGraze)
		{
			AddModifier(-m_ShotBreakdown.ResultTable[eHit_Graze], IgnoreGrazeReason, m_ShotBreakdown, eHit_Graze, bDebugLog);
		}
		//  Remove crit from reaction fire. Must be done last to remove all crit.
		if (bReactionFire)
		{
			AddReactionCritModifier(UnitState, TargetState, m_ShotBreakdown, bDebugLog);
		}
	}

	// Start Issue #1271
	/// HL-Docs: feature:GetAdditionalHitModifiers; issue:1271; tags:tactical
	/// This feature adds a method that can be used by subclasses to apply
	/// additional hit modifiers without having to override and copy paste
	/// the entire `GetHitChance()` function.
	GetAdditionalHitModifiers_CH(kAbility, kTarget, m_ShotBreakdown, bDebugLog);
	// End Issue #1271

	//  Final multiplier based on end Success chance
	if (bReactionFire && !bGuaranteedHit)
	{
		FinalAdjust = m_ShotBreakdown.ResultTable[eHit_Success] * GetReactionAdjust(UnitState, TargetState);
		AddModifier(-int(FinalAdjust), AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
		AddReactionFlatModifier(UnitState, TargetState, m_ShotBreakdown, bDebugLog);
	}
	else if (FinalMultiplier != 1.0f)
	{
		FinalAdjust = m_ShotBreakdown.ResultTable[eHit_Success] * FinalMultiplier;
		AddModifier(-int(FinalAdjust), AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
	}
	FinalizeHitChance(m_ShotBreakdown, bDebugLog);
	return m_ShotBreakdown.FinalHitChance;
}

//ADDED THIS SECTION
function ECoverType CoverTypeOfTileAboveCover(const out TTile SourceTile, const out TTile DestTile, int TargetCoverAngle) {
	local TTile TileDifference;
	local TTile TileAboveCover;

	TileDifference.X = SourceTile.X - DestTile.X;
	TileDifference.Y = SourceTile.Y - DestTile.Y;

	TileAboveCover = DestTile; TileAboveCover.Z += 2;
	if( TileDifference.X > 0) TileAboveCover.X += 1; else TileAboveCover.X -= 1;
	if(`XWORLD.IsTileFullyOccupied(TileAboveCover) || `XWORLD.IsTileOccupied(TileAboveCover)) return CT_Standing;

	TileAboveCover = DestTile; TileAboveCover.Z += 2;
	if( TileDifference.Y > 0) TileAboveCover.Y += 1; else TileAboveCover.Y -= 1;
	if(`XWORLD.IsTileFullyOccupied(TileAboveCover) || `XWORLD.IsTileOccupied(TileAboveCover)) return CT_Standing;
}