Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class X2GetBasicHitChance extends X2AbilityToHitCalc_StandardAim;
- // Mostly trimmed-down version of GetHitChance. I have no idea how this works.
- // But I need the info for dynamic aim effects so let's just see what happens.
- function int GetBasicHitChance (XComGameState_Ability kAbility, XComGameState_Unit Attacker, XComGameState_Unit kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = true)
- {
- 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 CoverValue, AngleToCoverModifier, Alpha;
- // local float FinalAdjust;
- local bool bShouldAddAngleToCoverBonus;
- local TTile UnitTileLocation, TargetTileLocation;
- local ECoverType NextTileOverCoverType;
- local int TileDistance;
- // local ShotBreakdown m_ShotBreakdown;
- `log("=" $ GetFuncName() $ "=", bDebugLog, 'XV_ViperSkills GetStandardHitChance');
- // @TODO gameplay handle non-unit targets
- History = `XCOMHISTORY;
- //UnitState = XComGameState_Unit(History.GetGameStateForObjectID( kAbility.OwnerStateObject.ObjectID ));
- //TargetState = XComGameState_Unit(History.GetGameStateForObjectID( kTarget.PrimaryTarget.ObjectID ));
- UnitState = Attacker;
- TargetState = kTarget;
- // AbilityTemplate = kAbility.GetMyTemplate();
- SourceWeapon = kAbility.GetSourceWeapon();
- //AddModifier(BuiltInHitMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
- //AddModifier(BuiltInCritMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
- m_ShotBreakdown = EmptyShotBreakdown;
- // Standard shot check
- // 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);
- }
- // 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: // full 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 )
- {
- bShouldAddAngleToCoverBonus = (UnitState.GetTeam() == eTeam_XCom);
- // 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) )
- {
- 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 )
- {
- bShouldAddAngleToCoverBonus = false;
- }
- else if( SHOULD_ENABLE_PENALTY_ON_ANGLE_TO_EXTENDED_HIGH_COVER && 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);
- AngleToCoverModifier = Lerp(MAX_ANGLE_BONUS_MOD,
- MIN_ANGLE_BONUS_MOD,
- Alpha);
- AddModifier(Round(CoverValue * AngleToCoverModifier), class'XLocalizedData'.default.AngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
- }
- }
- }
- }
- // Add height advantage
- if (UnitState.HasHeightAdvantageOver(TargetState, true))
- {
- AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightAdvantageBonus, class'XLocalizedData'.default.HeightAdvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
- }
- // Check for height disadvantage
- if (TargetState.HasHeightAdvantageOver(UnitState, false))
- {
- AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightDisadvantagePenalty, class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
- }
- }
- }
- 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');
- }
- }
- }
- // For all effects?
- 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);
- 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);
- 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);
- }
- }
- }
- }
- `log("Created shot breakdown, finalizing...", bDebugLog, 'XCom_HitRolls');
- FinalizeBasicHitChance(m_ShotBreakdown, bDebugLog);
- `log ("Final hit chance determined to be" $ m_ShotBreakdown.FinalHitChance, bDebugLog, 'XCom_HitRolls');
- return m_ShotBreakdown.FinalHitChance;
- }
- function FinalizeBasicHitChance(out ShotBreakdown m_ShotBreakdown, bool bDebugLog = false)
- {
- local int i;
- local EAbilityHitResult HitResult;
- local float GrazeScale;
- local int FinalGraze;
- `log("==" $ GetFuncName() $ "==\n", bDebugLog, 'XCom_HitRolls');
- `log("Starting values...", bDebugLog, 'XCom_HitRolls');
- for (i = 0; i < eHit_MAX; ++i)
- {
- HitResult = EAbilityHitResult(i);
- `log(HitResult $ ":" @ m_ShotBreakdown.ResultTable[i], bDebugLog, 'XCom_HitRolls');
- }
- m_ShotBreakdown.FinalHitChance = m_ShotBreakdown.ResultTable[eHit_Success];
- // if crit goes negative, hit would get a boost, so restrict it to 0
- if (m_ShotBreakdown.ResultTable[eHit_Crit] < 0)
- m_ShotBreakdown.ResultTable[eHit_Crit] = 0;
- // cap success at 100 so it can be fully overridden by crit
- m_ShotBreakdown.ResultTable[eHit_Success] = min(m_ShotBreakdown.ResultTable[eHit_Success], 100);
- // Crit is folded into the chance to hit, so lower accordingly
- m_ShotBreakdown.ResultTable[eHit_Success] -= m_ShotBreakdown.ResultTable[eHit_Crit];
- // Graze is scaled against Success - but ignored if success is 100%
- if (m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
- {
- if (m_ShotBreakdown.FinalHitChance < 100)
- {
- GrazeScale = float(m_ShotBreakdown.ResultTable[eHit_Graze]) / 100.0f;
- GrazeScale *= float(m_ShotBreakdown.FinalHitChance);
- FinalGraze = Round(GrazeScale);
- m_ShotBreakdown.ResultTable[eHit_Success] -= FinalGraze;
- m_ShotBreakdown.ResultTable[eHit_Graze] = FinalGraze;
- }
- else
- {
- m_ShotBreakdown.ResultTable[eHit_Graze] = 0;
- }
- }
- if (m_ShotBreakdown.FinalHitChance >= 100)
- {
- m_ShotBreakdown.ResultTable[eHit_Miss] = 0;
- }
- else
- {
- m_ShotBreakdown.ResultTable[eHit_Miss] = 100 - m_ShotBreakdown.FinalHitChance;
- }
- `log("Calculated values...", bDebugLog, 'XCom_HitRolls');
- for (i = 0; i < eHit_MAX; ++i)
- {
- HitResult = EAbilityHitResult(i);
- `log(HitResult $ ":" @ m_ShotBreakdown.ResultTable[i], bDebugLog, 'XCom_HitRolls');
- }
- `log("Final hit chance (success + crit + graze) =" @ m_ShotBreakdown.FinalHitChance, bDebugLog, 'XCom_HitRolls');
- //"Negative chance to hit" is used as a token in UI code - don't ever report that.
- if (m_ShotBreakdown.FinalHitChance < 0)
- {
- `log("FinalHitChance was less than 0 (" $ m_ShotBreakdown.FinalHitChance $ ") and was clamped to avoid confusing the UI (@btopp).", bDebugLog, 'XCom_HitRolls');
- m_ShotBreakdown.FinalHitChance = 0;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement