+ class Override_X2AbilityToHitCalc_StandardAim
+ extends X2AbilityToHitCalc_StandardAim
+ config (RWRealisticAimingAngles);
+ //extends X2AbilityToHitCalc
+ //config(GameCore);
+ //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... ;)
+
+ /*
+ ;PERFECT INFORMATION: MID-ACTION TEXT
+ ShowChances = true
+ ShowCriticalAndDodgeChances = true
+ ShowDiceRolls = true
+ ShowOnlyForAllies = false
+ */
+
+ //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 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!!!
+
+
+
+ //static function StaticInternalRollForAbilityHit(X2AbilityToHitCalc_StandardAim Aim, XComGameState_Ability kAbility, AvailableTarget kTarget, bool bIsPrimaryTarget, const out AbilityResultContext ResultContext, out EAbilityHitResult Result, out ArmorMitigationResults ArmorMitigated, out int HitChance)
+ 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 String TargetName;
+ local bool IsAlien;
+ local array<string> PINotify;
+ 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() )
+ 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);
+ // RandRoll = `SYNC_RAND_TYPED(100, ESyncRandType_Generic);
+ 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));
+ IsAlien = UnitState.GetTeam() != eTeam_XCom;
+ // 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;
+ }
+ }
+
+ } //ADDED THIS LINE!!!
+
+ if (HitsAreCrits && Result == eHit_Success)
+ Result = eHit_Crit;
+
+ // UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
+ if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance == false) UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
+ // TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.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)
+ {
+ TargetName = TargetState.GetFullName();
+ 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);
+ PINotify.AddItem(UnitState.GetFullName()@"uses"@AbilityName@"against"@TargetName@"for"@RealHitChance$"%."
+ @ default.ShowCriticalAndDodgeChances ? "Crit:" @ m_ShotBreakdown.ResultTable[eHit_Crit] $ "%, Dodge:"
+ @ Round(DodgeChance) $ "%" : "");
+ }
+ else
+ {
+ PINotify.AddItem(UnitState.GetFullName()@"uses"@AbilityName@"against"@TargetName@"for"
+ @ RealHitChance$"%." $ (RealHitChance != TotalHitChance ? "(" $ TotalHitChance $ "%)" : "")
+ @ default.ShowCriticalAndDodgeChances ? "Crit:" @ m_ShotBreakdown.ResultTable[eHit_Crit] $ "%, Dodge:"
+ @ m_ShotBreakdown.ResultTable[eHit_Graze] $ "% Roll thresold:"
+ @ m_ShotBreakdown.ResultTable[eHit_Success] $ "/"
+ $ m_ShotBreakdown.ResultTable[eHit_Success] + m_ShotBreakdown.ResultTable[eHit_Crit] $ "/"
+ $ TotalHitChance : "");
+ }
+ if (default.ShowDiceRolls)
+ {
+ PINotify.AddItem(DebugRollOutput);
+ }
+ }
+ //END OF ADDED PARAGRAPH!!!
+
+
+ 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;
+
+ //ADDED THIS LINE!!!
+ if (default.ShowChances && default.ShowDiceRolls) PINotify.AddItem(UnitState.GetFullName()@"used an effect to change hit result to"@ 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;
+
+ //ADDED THIS LINE!!!
+ if (default.ShowChances && default.ShowDiceRolls) PINotify.AddItem(TargetState.GetFullName()@"has an effect that changes hit result to"@ ChangeResult);
+
+ }
+ }
+ }
+ }
+
+ // 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();
+
+ if( bRolledResultIsAMiss && CurrentPlayerTeam == eTeam_XCom )
+ {
+ ModifiedHitChance = GetModifiedHitChanceForCurrentDifficulty(PlayerState, TargetState, HitChance);
+
+
+ //ADDED THIS PARAGRAPH!!!
+ if (default.ShowChances && ModifiedHitChance != HitChance && !bIndirectFire && !bGuaranteedHit && TargetState != none)
+ {
+ PINotify.AddItem("Aim assist modified hit chance to"@ModifiedHitChance$"%, using previous hit roll.");
+ }
+ //END OF ADDED PARAGRAPH!!!
+
+
+ //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.ShowChances && ModifiedHitChance != HitChance && !bIndirectFire && !bGuaranteedHit && TargetState != none)
+ {
+ PINotify.AddItem("Aim assist modified hit chance to"@ModifiedHitChance$"%, using previous hit roll.");
+ }
+ //END OF ADDED PARAGRAPH!!!
+
+
+ //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!!!
+
+ }
+ }
+
+ /* if (default.UseIndividualConsecutiveRollsForHitChanceCriticalChanceDodgeChance) */ NotifyEventList(kAbility, kTarget, PINotify, !IsAlien); //ADDED THIS LINE!!!
+
+ `log("***HIT" @ Result, !bRolledResultIsAMiss, 'XCom_HitRolls');
+ `log("***MISS" @ Result, bRolledResultIsAMiss, 'XCom_HitRolls');
+
+ if (TargetState != none)
+ {
+ // Check for Lightning Reflexes
+ if (bReactionFire && TargetState.bLightningReflexes && !bRolledResultIsAMiss)
+ {
+ 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)
+
+ }
+
+ 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);
+ }
+ }
+
+
+
+ //ADDED THIS PARAGRAPH!!!
+ static function NotifyEventList(XComGameState_Ability kAbility, AvailableTarget kTarget, array<String> message, optional bool IsXCOM=true)
+ {
+ local PerfectInformation_Notification Notification;
+ Notification = new class'PerfectInformation_Notification';
+ Notification.StateRef = kAbility.ObjectID;
+ Notification.Message = message;
+ Notification.IsAlly = IsXCOM;
+ Notification.RollTarget = kTarget.PrimaryTarget.ObjectID;
+ `XEVENTMGR.TriggerEvent('RequestNotifyOnVisualization', Notification);
+ }
+ //END OF ADDED PARAGRAPH!!!
+
+
+
+
+ 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 bool bShouldAddAngleToCoverBonus;
+ local TTile UnitTileLocation, TargetTileLocation;
+ local ECoverType NextTileOverCoverType;
+ local int TileDistance;
+
+ //ADDED THIS PARAGRAPH!!!
+ local TTile ShooterTile;
+ local TTile TargetTile;
+ local TTile ShooterHead;
+ local TTile TargetHead;
+ local TTile ShooterTorso;
+ // local TTile TargetTorso;
+ local TTile TargetLegs;
+ local TTile ShooterBelowFeet;
+ local float ShooterTargetDistance;
+ local float ShooterHeadRelativeElevation;
+ local float VerticalAngle;
+ local float GoodAngleBonus;
+ local float ExtendedLowCoverPreventingGoodAngleBonus;
+ local float HeightAdvantageBonus; //ALWAYS FLOATS INSTEAD OF INTS!!!!
+ local float HeightDisadvantageMalus; //OR YOU WILL LOOSE HOURS TRYING TO UNDERSTAND WHY
+ local float NoLineOfSightMalus; //THEY DIDN'T APPLY MULTIPLIERS!!! BECAUSE INT AUTO-ROUND!!! MOTHERFUCKERS!!!
+ local string FarCoverText;
+ local TTile NextTile;
+ local int FarCover;
+ local GameRulesCache_VisibilityInfo VisibilityInfo;
+ // local XComGameState NewGameStateForDestructibleObject;
+ // local XComGameState_Destructible TargetStateDestructibleObject;
+ local ECoverType CoverTypeOfTileAboveTarget;
+ //END OF ADDED PARAGRAPH!!!
+
+ `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!!!
+ // ShooterLocation = `XWORLD.GetPositionFromTileCoordinates(ShooterTile);
+ // TargetLocation = `XWORLD.GetPositionFromTileCoordinates(TargetTile);
+ UnitState .GetKeystoneVisibilityLocation(ShooterTile);
+ TargetState.GetKeystoneVisibilityLocation(TargetTile );
+ `TACTICALRULES.VisibilityMgr.GetVisibilityInfo(UnitState.ObjectID, TargetState.ObjectID, VisInfo);
+ CoverTypeOfTileAboveTarget = CoverTypeOfTileAboveCover (ShooterTile, TargetTile, VisInfo.TargetCoverAngle);
+ NextTileOverCoverType = NextTileOverCoverInSameDirection(ShooterTile, TargetTile);
+ GoodAngleBonus = 0; //JUST IN CASE... WIERD BEHAVIOR
+ //END OF ADDED PARAGRAPH!!!
+
+ if (kAbility != none)
+ {
+ AbilityTemplate = kAbility.GetMyTemplate();
+ SourceWeapon = kAbility.GetSourceWeapon();
+ }
+
+ // 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);
+ }
+
+ AddModifier(BuiltInHitMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
+ AddModifier(BuiltInCritMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
+
+ if (UnitState != none && TargetState == none)
+ {
+
+ //ADDED THIS PARAGRAPH!!! (GUARANTEED HIT MY ASS...)
+ /* TargetingObjectsRealisticallyAllowsToMissThemAndUsesConventionalHitChanceStatsPlusThisAimingBonus = 15
+ if(default.TargetingObjectsRealisticallyAllowsToMissThemAndUsesConventionalHitChanceStatsPlusThisAimingBonus != 100) {
+ TargetStateDestructibleObject = XComGameState_Destructible(History.GetGameStateForObjectID( kTarget.PrimaryTarget.ObjectID ));
+ NewGameStateForDestructibleObject = class'XComGameStateContext_ChangeContainer'.static.CreateChangeState("Conversion From Unit to Destructible for Aiming Stats");
+ TargetState = XComGameState_Unit(NewGameStateForDestructibleObject.CreateNewStateObject(class'XComGameState_Unit'));
+ TargetState.SetVisibilityLocation(TargetStateDestructibleObject.TileLocation); //KEYSTONEVISIBILITYLOCATION EVEN FOR OBJECTS??? REMEMBER HOW .TILELOCATION WAS BUGGY FOR UNITS!!!!
+ AddModifier(default.TargetingObjectsRealisticallyAllowsToMissThemAndUsesConventionalHitChanceStatsPlusThisAimingBonus, class'XLocalizedData'.default.DefenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);}
+ else {
+ */ //END OF ADDED PARAGRAPH!!!
+
+ // 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);
+
+ // } //ADDED THIS LINE!!!
+
+ }
+ else if (UnitState != none && TargetState != none)
+ {
+ 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);
+ UnitState.GetStatModifiers(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
+ AddModifier(-TargetState.GetCurrentStat(eStat_Defense), class'XLocalizedData'.default.DefenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
+
+ // 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
+ {
+ // Add cover penalties
+ if (TargetState.CanTakeCover())
+ {
+ // 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
+
+ //`assert(VisInfo.TargetCoverAngle >= 0); // if the target has cover, the target cover angle should always be greater than 0
+ // if( VisInfo.TargetCoverAngle < MAX_ANGLE_TO_COVER && TileDistance <= MAX_TILE_DISTANCE_TO_COVER )
+ if( VisInfo.TargetCoverAngle < 90-default.MinimumGoodAngleBonusStartsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface && (TileDistance <= MAX_TILE_DISTANCE_TO_COVER || default.ApplyGoodAngleBonusEvenFartherThan11TilesAway))
+ {
+ bShouldAddAngleToCoverBonus = (UnitState.GetTeam() == eTeam_XCom);
+ if (default.ApplyGoodAngleBonusNotOnlyForXComSoldiersButAlsoForAnyoneShooting) bShouldAddAngleToCoverBonus = true; //GOODANGLE FOR ENEMIES TOO!!!
+
+ // We have to avoid the weird visual situation of a unit standing behind low cover
+ // and that low cover extends at least 1 tile in the direction of the attacker.
+ // if( (SHOULD_DISABLE_BONUS_ON_ANGLE_TO_EXTENDED_LOW_COVER && VisInfo.TargetCover == CT_MidLevel) || (SHOULD_ENABLE_PENALTY_ON_ANGLE_TO_EXTENDED_HIGH_COVER && VisInfo.TargetCover == CT_Standing) )
+ if( (true && VisInfo.TargetCover == CT_MidLevel) || (false && VisInfo.TargetCover == CT_Standing) )
+ {
+ UnitState.GetKeystoneVisibilityLocation(UnitTileLocation);
+ TargetState.GetKeystoneVisibilityLocation(TargetTileLocation);
+ NextTileOverCoverType = NextTileOverCoverInSameDirection(UnitTileLocation, TargetTileLocation );
+
+ // if( SHOULD_DISABLE_BONUS_ON_ANGLE_TO_EXTENDED_LOW_COVER && VisInfo.TargetCover == CT_MidLevel && NextTileOverCoverType == CT_MidLevel ) //EXTENDED LOW-HIGH COVER HIBRID
+ // if(true && (VisInfo.TargetCover == CT_MidLevel || VisInfo.TargetCover == CT_Standing) && (NextTileOverCoverType == CT_MidLevel || NextTileOverCoverType == CT_Standing))
+ if(true && VisInfo.TargetCover == CT_MidLevel && (NextTileOverCoverType == CT_MidLevel || NextTileOverCoverType == CT_Standing))
+
+ {
+ bShouldAddAngleToCoverBonus = false;
+ }
+ // else if(SHOULD_ENABLE_PENALTY_ON_ANGLE_TO_EXTENDED_HIGH_COVER && VisInfo.TargetCover == CT_Standing && NextTileOverCoverType == CT_Standing )
+ else if(false && VisInfo.TargetCover == CT_Standing && NextTileOverCoverType == CT_Standing )
+ {
+ bShouldAddAngleToCoverBonus = false;
+ Alpha = FClamp((VisInfo.TargetCoverAngle - MIN_ANGLE_TO_COVER ) / (MAX_ANGLE_TO_COVER - MIN_ANGLE_TO_COVER ), 0.0, 1.0);
+ AngleToCoverModifier = Lerp(MAX_ANGLE_PENALTY , MIN_ANGLE_PENALTY , Alpha);
+ AddModifier(Round(-1.0 * AngleToCoverModifier), class'XLocalizedData'.default.BadAngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
+ }
+ }
+
+ if( bShouldAddAngleToCoverBonus )
+ {
+ // Alpha = FClamp((VisInfo.TargetCoverAngle - MIN_ANGLE_TO_COVER ) / (MAX_ANGLE_TO_COVER - MIN_ANGLE_TO_COVER ), 0.0, 1.0);
+ // Alpha = FClamp((VisInfo.TargetCoverAngle - 0 ) / (45 - 0 ), 0.0, 1.0);
+ Alpha = FClamp((VisInfo.TargetCoverAngle - default.MaximumGoodAngleBonusEndsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface) / (default.MinimumGoodAngleBonusStartsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface - default.MaximumGoodAngleBonusEndsAtThisHorizontalAngleBetweenLineOfFireAndCoverSurface), 0.0, 1.0);
+ // AngleToCoverModifier = Lerp(MAX_ANGLE_BONUS_MOD , MIN_ANGLE_BONUS_MOD , Alpha);
+ AngleToCoverModifier = Lerp(default.MaximumGoodAngleBonusCoverReductionPercentage /100, default.MinimumGoodAngleBonusCoverReductionPercentage /100, Alpha);
+ // AngleToCoverModifier = Lerp(1 , 0 , Alpha);
+ // AddModifier(Round(CoverValue * AngleToCoverModifier) , class'XLocalizedData'.default.AngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
+ GoodAngleBonus = Round(CoverValue * AngleToCoverModifier);
+ AddModifier(GoodAngleBonus , class'XLocalizedData'.default.AngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
+ // if (VisInfo.TargetCover == CT_Standing && NextTileOverCoverType == CT_MidLevel) AddModifier(-Round(GoodAngleBonus / 2) , "ExtendedLowCover preventing GoodAngle" , m_ShotBreakdown, eHit_Success, bDebugLog);
+ // if ((VisInfo.TargetCover == CT_MidLevel || VisInfo.TargetCover == CT_Standing) && (NextTileOverCoverType == CT_MidLevel || NextTileOverCoverType == CT_Standing)) AddModifier(-Round(GoodAngleBonus / 2) , "ExtendedLowCover preventing GoodAngle" , m_ShotBreakdown, eHit_Success, bDebugLog);
+ if ( (NextTileOverCoverType == CT_MidLevel || NextTileOverCoverType == CT_Standing)) {
+ ExtendedLowCoverPreventingGoodAngleBonus = -Round(GoodAngleBonus / 2);
+ AddModifier(ExtendedLowCoverPreventingGoodAngleBonus , "ExtendedLowCover preventing GoodAngle" , m_ShotBreakdown, eHit_Success, bDebugLog);} //WHEN ONLY "HALF OF" THE HIGHCOVER IS REDUCED BY THE GOODANGLE BECAUSE THE OTHER HALF IS COVERED BY LOWCOVER... ;)
+ }
+ }
+ }
+ }
+ // Add height advantage
+ // if (UnitState.HasHeightAdvantageOver(TargetState, true) )
+ 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) )
+ if (TargetState.HasHeightAdvantageOver(UnitState, false) && default.ApplyVerticalGoodAngleAgainstLowerTargetAsProportionalHeightAdvantage != true)
+ {
+ AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightDisadvantagePenalty, class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
+ // AddModifier(-10 , class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
+ }
+
+
+ //REPLACED 'STUPID' PARAGRAPH ABOVE
+ //WITH PARAGRAPH BELOW!!!
+ // PI = 3.141592; //ALMIGHTY UNIVERSAL CONSTANT
+ // ShooterHead = ShooterTile; ShooterHead.Z += 2;
+ // TargetHead = TargetTile; TargetHead .Z += 2;
+ // ShooterHead = ShooterTile; ShooterHead.Z += UnitState .UnitHeight -1;
+ // TargetHead = TargetTile; TargetHead .Z += TargetState.UnitHeight -1;
+ ShooterHead = ShooterTile; ShooterHead.Z = `XWORLD.GetFloorTileZ(ShooterHead) + UnitState .UnitHeight -1;
+ TargetHead = TargetTile; TargetHead .Z = `XWORLD.GetFloorTileZ(TargetHead ) + TargetState.UnitHeight -1;
+ ShooterTorso = ShooterHead; ShooterTorso.Z -= 0.5;
+ // TargetTorso = TargetHead ; TargetTorso .Z -= 0.5;
+ ShooterBelowFeet = ShooterTile; ShooterBelowFeet.Z -= 2;
+ TargetLegs = TargetTile;
+
+ 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);
+ // Alpha = FClamp((VisInfo.TargetCoverAngle - 0 ) / (45 - 0 ), 0.0, 1.0);
+ // Alpha = FClamp(((90-VerticalAngle) - 30 ) / (90 - 30 ), 0.0, 1.0);
+ AngleToCoverModifier = Lerp(default.MaximumHeightAdvantageBonusCoverReductionPercentage/100, default.MinimumHeightAdvantageBonusCoverReductionPercentage/100 , Alpha);
+ // AngleToCoverModifier = Lerp(1 , 0 , Alpha);
+ HeightAdvantageBonus = round(AngleToCoverModifier * CoverValue);
+ // HeightAdvantageBonus = round(VerticalAngle/90 * CoverValue);
+ AddModifier(HeightAdvantageBonus, class'XLocalizedData'.default.HeightAdvantage, m_ShotBreakdown, eHit_Success, bDebugLog);}
+
+ if (default.GiveHitChanceZeroWhenTargetableEnemiesAreInsteadNotTrulyInLineOfSight// &&
+ /* ShooterHeadRelativeElevation > 0 */ ){ //'FAR' COVER BLOCKING VISIBILITY CHECK...
+ if (ShooterHead.Z - TargetLegs.Z >= 0){ //SAME HEIGHT OR BELOW SHOOTER
+ NextTile = ShooterTile;
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (NextTile.Z>=ShooterTile.Z && FarCover>0 && !`XWORLD.CanSeeTileToTile(ShooterTorso, TargetHead, VisibilityInfo)) NoLineOfSightMalus = -1000;
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (NextTile.Z>=ShooterTile.Z && FarCover>0 && !`XWORLD.CanSeeTileToTile(ShooterTorso, TargetHead, VisibilityInfo)) NoLineOfSightMalus = -1000; //LOW COVER IN CALCULATING VISIBILITY.
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (NextTile.Z>=ShooterTile.Z && FarCover>0 && !`XWORLD.CanSeeTileToTile(ShooterTorso, TargetHead, VisibilityInfo)) NoLineOfSightMalus = -1000;} //THIS NONSENSE HAD TO BE MODDED AWAY...
+ if (ShooterHead.Z - TargetLegs.Z < 0){ //ABOVE SHOOTER
+ NextTile = ShooterTile;
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (FarCover == 1 && !`XWORLD.CanSeeTileToTile(ShooterBelowFeet, TargetLegs, VisibilityInfo)) NoLineOfSightMalus = -1000; //DIFFERENT CHECK FOR HIGH GROUNDS
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (FarCover == 1 && !`XWORLD.CanSeeTileToTile(ShooterBelowFeet, TargetLegs, VisibilityInfo)) NoLineOfSightMalus = -1000; //IN CALCULATING VISIBILITY.
+ NextTile = NextWalkableTile(ShooterTile, NextTile, TargetTile, FarCover); if (FarCover == 1 && !`XWORLD.CanSeeTileToTile(ShooterBelowFeet, TargetLegs, VisibilityInfo)) NoLineOfSightMalus = -1000;}
+ If (NoLineOfSightMalus != 0) AddModifier(NoLineOfSightMalus, "No Line of Sight", m_ShotBreakdown, eHit_Success, bDebugLog);}
+
+ if (default.AllowFarCoverToGiveSomeCoverProtection &&
+ (TargetState.GetCurrentStat(eStat_AlertLevel)==`ALERT_LEVEL_RED || (TargetState.GetCurrentStat(eStat_AlertLevel)!=`ALERT_LEVEL_RED && !default.OnlyAlertedTargetsCanHaveTheExtraProtectionOfFarCoverAndHeightDisadvantage)) &&
+ (VisInfo.TargetCover==CT_None || (GoodAngleBonus > 0 && ExtendedLowCoverPreventingGoodAngleBonus == 0)) && //FAR COVER ONLY IF NOT IN COVER OR IF THE COVER TAKEN IS NOT IN THE DIRECTION OF THE SHOOTER (MORE FLANKING THAN COVER)
+ TileDistance > 2 &&
+ TargetState.GetMyTemplate().bCanTakeCover == true){
+ // ShooterTargetDistance = class'Helpers'.static.DistanceBetweenTiles(TargetTile, ShooterTile, false ); //FORGETTING THE "FALSE" VARIABLE WILL GIVE "SQUARED" DISTANCE!!!! MOTHERFUCKER!!! HOW GAVE SUCH A WRONG NAME TO
+ ShooterTargetDistance = TilesDistance(TargetTile, ShooterTile ); //SUCH A DANGERIOUSLY MISLEADING FUNCTION!!!!??? HOWRS TRYING TO UNDERSTAND WHY DISTANCE WAS WRONGLY CALCULATED!!!
+ // ShooterTargetDistance = VSize(TargetLocation, ShooterLocation); // * 0.8; //NO FAR COVER 20% NEAR THE SHOOTER... NONSENSE.. ;)
+ FarCoverText = "Far Cover";
+ if (ShooterHeadRelativeElevation == 0){ NextTile = TargetTile; FarCoverBonus = 0;
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (FarCover>0) FarCoverBonus = 20 / (ShooterTargetDistance*0.7) * Max(0, TilesDistance(ShooterTile, NextTile) - ShooterTargetDistance*0.3);} }
+ if (ShooterHead.Z - TargetLegs.Z < 0){ NextTile = TargetTile; //ABOVE SHOOTER
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;} //DIFFERENT VALUE FOR FAR COVER
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;} //ON ELEVATED GROUND
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;}
+ if(FarCoverBonus==0 && NextTile.Z == TargetTile.Z) {NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z>=TargetTile.Z && FarCover>0) FarCoverBonus = 20;}}
+ if (TilesDistance(TargetTile, NextTile) >= ShooterTargetDistance) {FarCoverBonus = 0;} //DON'T CONSIDER FAR COVER "BEHIND" SHOOTER... ;)
+ if (FarCoverBonus > 0 && GoodAngleBonus > 0) {FarCoverBonus = Min(GoodAngleBonus, FarCoverBonus); FarCoverText = "FarCover Preventing GoodAngle";}
+ // AddModifier( -FarCoverBonus , FarCoverText, m_ShotBreakdown, eHit_Success, bDebugLog);}
+ AddModifier(round(-FarCoverBonus), FarCoverText, m_ShotBreakdown, eHit_Success, bDebugLog);}
+
+ if (default.ApplyVerticalBadAngleAgainstHigherTargetAsProportionalHeightDisadvantage &&
+ ShooterHead.Z - TargetLegs.Z < 0 && //TARGET STANDING ON A SURFACE HIGHER THAN THE SHOOTER'S HEAD
+ (TargetState.GetCurrentStat(eStat_AlertLevel)==`ALERT_LEVEL_RED || (TargetState.GetCurrentStat(eStat_AlertLevel)!=`ALERT_LEVEL_RED && !default.OnlyAlertedTargetsCanHaveTheExtraProtectionOfFarCoverAndHeightDisadvantage)) &&
+ TargetState.GetMyTemplate().bCanTakeCover == true //&& //HIGHER, LOWER OR SAME LEVEL, FAR COVER MUST GIVE SOME ADVANTAGE NO MATTER THE HEIGHT
+ // VisInfo.TargetCover == CT_None
+ ){ //HEIGHT DISADVANTAGE DOESN'T INCREASE TARGET'S HIGH/LOW COVER... BUT ONLY ADDS FROM 1 TO 19 COVER WHEN BUILDING/CLIFF PARTIALLY ACTS AS LOW COVER
+ // Alpha = FClamp((VisInfo.TargetCoverAngle - MIN_ANGLE_TO_COVER ) / (MAX_ANGLE_TO_COVER - MIN_ANGLE_TO_COVER ), 0.0, 1.0);
+ // Alpha = FClamp((VisInfo.TargetCoverAngle - 0 ) / (45 - 0 ), 0.0, 1.0);
+ Alpha = FClamp((90-(-VerticalAngle) - 0 ) / (85 - 0 ), 0.0, 1.0);
+ // AngleToCoverModifier = Lerp(MAX_ANGLE_BONUS_MOD , MIN_ANGLE_BONUS_MOD , Alpha);
+ AngleToCoverModifier = Lerp(1 , 0 , Alpha);
+ // HeightDisadvantageMalus = round(VerticalAngle/90*(20)); //NEGATIVE RELATIVEELEVATION --> NEGATIVE VERTICALANGLE --> NEGATIVE MALUS//SOMETIMES YOU ARE SHOOTING FROM THE "SIDE OF HIS LOWCOVER" AND THE SIDE HAS ROOFCEILING AS PROTECTION...
+ HeightDisadvantageMalus = round(-AngleToCoverModifier*(20)); NextTile = TargetTile;
+ /* NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); //EACH "STEP BACKWARDS"//THE ROOFTOP-BORDER UNTIL
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);} //NEGATIVE RELATIVEELEVATION --> NEGATIVE VERTICALANGLE --> NEGATIVE MALUS
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover);}
+ if (NextTile.Z >= TargetTile.Z) {HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); }
+ */ if (VisInfo.TargetCover == CT_MidLevel) FarCover = 1;
+ if (VisInfo.TargetCover != CT_MidLevel) {
+ for (i=1;i==20;++i){
+ NextTile = NextWalkableTile(TargetTile, NextTile, ShooterTile, FarCover); if (NextTile.Z < TargetTile.Z) break;
+ // HeightDisadvantageMalus = round(HeightDisadvantageMalus + HeightDisadvantageMalus/2); //ROUNDING SMALL PERCENTAGES NEVER
+ HeightDisadvantageMalus = HeightDisadvantageMalus + HeightDisadvantageMalus/2 ; //GIVES LARGE SUMS! ROUND AT THE END!!!
+ }}
+ // HeightDisadvantageMalus = HeightDisadvantageMalus/(TargetState.UnitHeight-1); // TALL FACELESS/SECTOPODS DON'T HIDE MUCH BEHIND HIGH BUILDINGS/CLIFFS BORDERS
+ // if (HeightDisadvantageMalus < -1000) HeightDisadvantageMalus = -1000;
+ // else if (HeightDisadvantageMalus < -20) HeightDisadvantageMalus = -20;
+ if (FarCover==1) HeightDisadvantageMalus = 2 * HeightDisadvantageMalus;
+ // AddModifier( HeightDisadvantageMalus , class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);} //POSITIVE OR NEGATIVE???
+ AddModifier(round(HeightDisadvantageMalus), class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);} //POSITIVE OR NEGATIVE???
+ //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);
+ UnitState.GetStatModifiers(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);
+ }
+ 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, self.Class , bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
+ 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 , self.Class, bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
+ 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);
+ }
+ }
+
+ // 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 TTile NextWalkableTile( TTile OriginTile , // Y
+ TTile MidTile , // ^
+ TTile DestinationTile, // |
+ out int FarCover ){ // X <--
+ local TTile TileDifference;
+ local TTile NextTile;
+ local int Direction;
+ local int OriginDestinationAngle;
+ local int MidTileDestinationAngle;
+ local TileData NextTileData;
+
+ OriginDestinationAngle = Abs(DestinationTile.X - OriginTile.X) / Abs(DestinationTile.Y - OriginTile.Y);
+ MidTileDestinationAngle = Abs(DestinationTile.X - MidTile .X) / Abs(DestinationTile.Y - MidTile .Y);
+
+ NextTile = MidTile;
+ TileDifference.X = DestinationTile.X - MidTile.X;
+ TileDifference.Y = DestinationTile.Y - MidTile.Y;
+ Direction = 0;
+ FarCover = 0;
+
+ // if (Abs(TileDifference.X) > Abs(TileDifference.Y) && OriginDestinationAngle >= MidTileDestinationAngle ||
+ // Abs(TileDifference.X) <= Abs(TileDifference.Y) && OriginDestinationAngle < MidTileDestinationAngle ){
+ if (OriginDestinationAngle <= MidTileDestinationAngle){
+ if( TileDifference.X > 0) {++NextTile.X;}
+ if( TileDifference.X <=0) {--NextTile.X;}}
+ // if (Abs(TileDifference.X) <= Abs(TileDifference.Y) && OriginDestinationAngle >= MidTileDestinationAngle ||
+ // Abs(TileDifference.X) > Abs(TileDifference.Y) && OriginDestinationAngle < MidTileDestinationAngle ){
+ if (OriginDestinationAngle > MidTileDestinationAngle){
+ if( TileDifference.Y > 0) {++NextTile.Y;}
+ if( TileDifference.Y <=0) {--NextTile.Y;}}
+
+ if (Abs(TileDifference.X) > Abs(TileDifference.Y)){
+ if( TileDifference.X > 0) {Direction = `XWORLD.COVER_WLow;}
+ if( TileDifference.X <=0) {Direction = `XWORLD.COVER_ELow;}}
+ if (Abs(TileDifference.X) <= Abs(TileDifference.Y)){
+ if( TileDifference.Y > 0) {Direction = `XWORLD.COVER_NLow;}
+ if( TileDifference.Y <=0) {Direction = `XWORLD.COVER_SLow;}}
+
+ NextTile.Z = `XWORLD.GetFloorTileZ(NextTile);
+ `XWORLD.GetTileData(NextTile, NextTileData);
+ if ((NextTileData.CoverFlags & Direction) != 0) FarCover = 1;
+ return NextTile;}
+
+
+
+ // arrEnemyInfos[EnemyInfoIndex].TargetCover = World.GetCoverTypeForTarget(vEnemyPos, vLoc, arrEnemyInfos[EnemyInfoIndex].TargetCoverAngle);
+ 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;
+
+ /* if( (Abs(TileDifference.X) > Abs(TileDifference.Y) && TargetCoverAngle>45) || //FROM FRONT OF X-AXIS COVER
+ (Abs(TileDifference.X) > Abs(TileDifference.Y) && TargetCoverAngle<45) ){ //FROM SIDE OF X-AXIS COVER
+ if( TileDifference.X > 0) {TileAboveCover.X += 1;}
+ if( TileDifference.X <=0) {TileAboveCover.X -= 1;}}
+ if( (Abs(TileDifference.X) <= Abs(TileDifference.Y) && TargetCoverAngle>45) || //FROM FRONT OF Y-AXIS COVER
+ (Abs(TileDifference.X) <= Abs(TileDifference.Y) && TargetCoverAngle<45) ){ //FROM SIDE OF Y-AXIS COVER
+ if( TileDifference.Y > 0) {TileAboveCover.Y += 1;}
+ if( TileDifference.Y <=0) {TileAboveCover.Y -= 1;}}
+ */
+ 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;
+ /*
+ TileAboveCover = DestTile; TileAboveCover.Z += 1; TileAboveCover.X += 1;
+ if(`XWORLD.IsTileFullyOccupied(TileAboveCover) ||
+ `XWORLD.IsTileOccupied (TileAboveCover) ) return CT_Standing;
+ TileAboveCover = DestTile; TileAboveCover.Z += 1; TileAboveCover.X -= 1;
+ if(`XWORLD.IsTileFullyOccupied(TileAboveCover) ||
+ `XWORLD.IsTileOccupied (TileAboveCover) ) return CT_Standing;
+ TileAboveCover = DestTile; TileAboveCover.Z += 1; TileAboveCover.Y += 1;
+ if(`XWORLD.IsTileFullyOccupied(TileAboveCover) ||
+ `XWORLD.IsTileOccupied (TileAboveCover) ) return CT_Standing;
+ TileAboveCover = DestTile; TileAboveCover.Z += 1; TileAboveCover.Y -= 1;
+ if(`XWORLD.IsTileFullyOccupied(TileAboveCover) ||
+ `XWORLD.IsTileOccupied (TileAboveCover) ) return CT_Standing;
+ */
+ }
+
+
+
+
+ function bool CoverOnBothSides( TTile ShooterTile,
+ TTile TargetTile) {
+ local TTile TileDifference;
+ local int DirectionX;
+ local int DirectionY;
+ local TileData TargetTileData;
+
+ TileDifference.X = TargetTile.X - ShooterTile.X;
+ TileDifference.Y = TargetTile.Y - ShooterTile.Y;
+ DirectionX = 0;
+ DirectionY = 0;
+
+ if( Abs(TileDifference.X) > Abs(TileDifference.Y) ) {
+ if( TileDifference.X > 0) {DirectionX = `XWORLD.COVER_West;}
+ if( TileDifference.X <=0) {DirectionX = `XWORLD.COVER_East;}}
+ if( Abs(TileDifference.X) <= Abs(TileDifference.Y) ) {
+ if( TileDifference.Y > 0) {DirectionY = `XWORLD.COVER_North;}
+ if( TileDifference.Y <=0) {DirectionY = `XWORLD.COVER_South;}}
+
+ `XWORLD.GetTileData(TargetTile, TargetTileData);
+ if ((TargetTileData.CoverFlags & DirectionX) != 0 &&
+ (TargetTileData.CoverFlags & DirectionY) != 0 ) return true;}
+
+
+
+
+ static function float TilesDistance(TTile TileA, TTile TileB)
+ {
+ local XComWorldData World;
+ local vector LocA, LocB;
+ World = `XWORLD;
+ LocA = World.GetPositionFromTileCoordinates(TileA);
+ LocB = World.GetPositionFromTileCoordinates(TileB);
+
+ return VSize(LocA - LocB);
+ }