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