Advertisement
Guest User

Untitled

a guest
Nov 7th, 2018
147
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. class X2GetBasicHitChance extends X2AbilityToHitCalc_StandardAim;
  2.  
  3.  
  4. // Mostly trimmed-down version of GetHitChance. I have no idea how this works.
  5. // But I need the info for dynamic aim effects so let's just see what happens.
  6. function int GetBasicHitChance (XComGameState_Ability kAbility, XComGameState_Unit Attacker, XComGameState_Unit kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = true)
  7. {
  8.     local XComGameState_Unit UnitState, TargetState;
  9.     local XComGameState_Item SourceWeapon;
  10.     local GameRulesCache_VisibilityInfo VisInfo;
  11.     local array<X2WeaponUpgradeTemplate> WeaponUpgrades;
  12.     local int i, iWeaponMod, iRangeModifier, Tiles;
  13.     local ShotBreakdown EmptyShotBreakdown;
  14.     local array<ShotModifierInfo> EffectModifiers;
  15.     local StateObjectReference EffectRef;
  16.     local XComGameState_Effect EffectState;
  17.     local XComGameStateHistory History;
  18.     local bool bFlanking, bIgnoreGraze, bSquadsight;
  19.     // local string IgnoreGrazeReason;
  20.     // local X2AbilityTemplate AbilityTemplate;
  21.     local array<XComGameState_Effect> StatMods;
  22.     local array<float> StatModValues;
  23.     local X2Effect_Persistent PersistentEffect;
  24.     local array<X2Effect_Persistent> UniqueToHitEffects;
  25.     local float CoverValue, AngleToCoverModifier, Alpha;
  26.     // local float FinalAdjust;
  27.     local bool bShouldAddAngleToCoverBonus;
  28.     local TTile UnitTileLocation, TargetTileLocation;
  29.     local ECoverType NextTileOverCoverType;
  30.     local int TileDistance;
  31.     // local ShotBreakdown m_ShotBreakdown;
  32.  
  33.     `log("=" $ GetFuncName() $ "=", bDebugLog, 'XV_ViperSkills GetStandardHitChance');
  34.  
  35.     //  @TODO gameplay handle non-unit targets
  36.     History = `XCOMHISTORY;
  37.     //UnitState = XComGameState_Unit(History.GetGameStateForObjectID( kAbility.OwnerStateObject.ObjectID ));
  38.     //TargetState = XComGameState_Unit(History.GetGameStateForObjectID( kTarget.PrimaryTarget.ObjectID ));
  39.     UnitState = Attacker;
  40.     TargetState = kTarget;
  41.  
  42.     // AbilityTemplate = kAbility.GetMyTemplate();
  43.     SourceWeapon = kAbility.GetSourceWeapon();         
  44.  
  45.     //AddModifier(BuiltInHitMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
  46.     //AddModifier(BuiltInCritMod, AbilityTemplate.LocFriendlyName, m_ShotBreakdown, eHit_Crit, bDebugLog);
  47.  
  48.     m_ShotBreakdown = EmptyShotBreakdown;
  49.  
  50.  
  51.     // Standard shot check
  52.     // StandardAim (with direct fire) will require visibility info between source and target (to check cover).
  53.     if (`TACTICALRULES.VisibilityMgr.GetVisibilityInfo(UnitState.ObjectID, TargetState.ObjectID, VisInfo))
  54.     {  
  55.         if (UnitState.CanFlank() && TargetState.GetMyTemplate().bCanTakeCover && VisInfo.TargetCover == CT_None)
  56.             bFlanking = true;
  57.         if (VisInfo.bClearLOS && !VisInfo.bVisibleGameplay)
  58.             bSquadsight = true;
  59.  
  60.         //  Add basic offense and defense values
  61.         AddModifier(UnitState.GetBaseStat(eStat_Offense), class'XLocalizedData'.default.OffenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
  62.         UnitState.GetStatModifiers(eStat_Offense, StatMods, StatModValues);
  63.         for (i = 0; i < StatMods.Length; ++i)
  64.         {
  65.             AddModifier(int(StatModValues[i]), StatMods[i].GetX2Effect().FriendlyName, m_ShotBreakdown, eHit_Success, bDebugLog);
  66.         }
  67.         //  Squadsight penalty
  68.         if (bSquadsight)
  69.         {
  70.             Tiles = UnitState.TileDistanceBetween(TargetState);
  71.             //  remove number of tiles within visible range (which is in meters, so convert to units, and divide that by tile size)
  72.             Tiles -= UnitState.GetVisibilityRadius() * class'XComWorldData'.const.WORLD_METERS_TO_UNITS_MULTIPLIER / class'XComWorldData'.const.WORLD_StepSize;
  73.             if (Tiles > 0)      //  pretty much should be since a squadsight target is by definition beyond sight range. but...
  74.                 AddModifier(default.SQUADSIGHT_DISTANCE_MOD * Tiles, class'XLocalizedData'.default.SquadsightMod, m_ShotBreakdown, eHit_Success, bDebugLog);
  75.             else if (Tiles == 0)    //  right at the boundary, but squadsight IS being used so treat it like one tile
  76.                 AddModifier(default.SQUADSIGHT_DISTANCE_MOD, class'XLocalizedData'.default.SquadsightMod, m_ShotBreakdown, eHit_Success, bDebugLog);
  77.         }
  78.  
  79.         //  Check for modifier from weapon             
  80.         if (SourceWeapon != none)
  81.         {
  82.             iWeaponMod = SourceWeapon.GetItemAimModifier();
  83.             AddModifier(iWeaponMod, class'XLocalizedData'.default.WeaponAimBonus, m_ShotBreakdown, eHit_Success, bDebugLog);
  84.  
  85.             WeaponUpgrades = SourceWeapon.GetMyWeaponUpgradeTemplates();
  86.             for (i = 0; i < WeaponUpgrades.Length; ++i)
  87.             {
  88.                 if (WeaponUpgrades[i].AddHitChanceModifierFn != None)
  89.                 {
  90.                     if (WeaponUpgrades[i].AddHitChanceModifierFn(WeaponUpgrades[i], VisInfo, iWeaponMod))
  91.                     {
  92.                         AddModifier(iWeaponMod, WeaponUpgrades[i].GetItemFriendlyName(), m_ShotBreakdown, eHit_Success, bDebugLog);
  93.                     }
  94.                 }
  95.             }
  96.         }
  97.         //  Target defense
  98.         AddModifier(-TargetState.GetCurrentStat(eStat_Defense), class'XLocalizedData'.default.DefenseStat, m_ShotBreakdown, eHit_Success, bDebugLog);
  99.                
  100.         //  Add weapon range
  101.         if (SourceWeapon != none)
  102.         {
  103.             iRangeModifier = GetWeaponRangeModifier(UnitState, TargetState, SourceWeapon);
  104.             AddModifier(iRangeModifier, class'XLocalizedData'.default.WeaponRange, m_ShotBreakdown, eHit_Success, bDebugLog);
  105.         }          
  106.         //  Cover modifiers
  107.         if (bMeleeAttack)
  108.         {
  109.             AddModifier(MELEE_HIT_BONUS, class'XLocalizedData'.default.MeleeBonus, m_ShotBreakdown, eHit_Success, bDebugLog);
  110.         }
  111.         else
  112.         {
  113.             //  Add cover penalties
  114.             if (TargetState.CanTakeCover())
  115.             {
  116.                 // if any cover is being taken, factor in the angle to attack
  117.                 if( VisInfo.TargetCover != CT_None && !bIgnoreCoverBonus )
  118.                 {
  119.                     switch( VisInfo.TargetCover )
  120.                     {
  121.                     case CT_MidLevel:           //  half cover
  122.                         AddModifier(-LOW_COVER_BONUS, class'XLocalizedData'.default.TargetLowCover, m_ShotBreakdown, eHit_Success, bDebugLog);
  123.                         CoverValue = LOW_COVER_BONUS;
  124.                         break;
  125.                     case CT_Standing:           //  full cover
  126.                         AddModifier(-HIGH_COVER_BONUS, class'XLocalizedData'.default.TargetHighCover, m_ShotBreakdown, eHit_Success, bDebugLog);
  127.                         CoverValue = HIGH_COVER_BONUS;
  128.                         break;
  129.                     }
  130.  
  131.                     TileDistance = UnitState.TileDistanceBetween(TargetState);
  132.  
  133.                     // from Angle 0 -> MIN_ANGLE_TO_COVER, receive full MAX_ANGLE_BONUS_MOD
  134.                     // 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
  135.                     // Above MAX_ANGLE_TO_COVER, receive no bonus
  136.  
  137.                     //`assert(VisInfo.TargetCoverAngle >= 0); // if the target has cover, the target cover angle should always be greater than 0
  138.                     if( VisInfo.TargetCoverAngle < MAX_ANGLE_TO_COVER && TileDistance <= MAX_TILE_DISTANCE_TO_COVER )
  139.                     {
  140.                         bShouldAddAngleToCoverBonus = (UnitState.GetTeam() == eTeam_XCom);
  141.  
  142.                         // We have to avoid the weird visual situation of a unit standing behind low cover
  143.                         // and that low cover extends at least 1 tile in the direction of the attacker.
  144.                         if( (SHOULD_DISABLE_BONUS_ON_ANGLE_TO_EXTENDED_LOW_COVER && VisInfo.TargetCover == CT_MidLevel) ||
  145.                             (SHOULD_ENABLE_PENALTY_ON_ANGLE_TO_EXTENDED_HIGH_COVER && VisInfo.TargetCover == CT_Standing) )
  146.                         {
  147.                             UnitState.GetKeystoneVisibilityLocation(UnitTileLocation);
  148.                             TargetState.GetKeystoneVisibilityLocation(TargetTileLocation);
  149.                             NextTileOverCoverType = NextTileOverCoverInSameDirection(UnitTileLocation, TargetTileLocation);
  150.  
  151.                             if( SHOULD_DISABLE_BONUS_ON_ANGLE_TO_EXTENDED_LOW_COVER && VisInfo.TargetCover == CT_MidLevel && NextTileOverCoverType == CT_MidLevel )
  152.                             {
  153.                                 bShouldAddAngleToCoverBonus = false;
  154.                             }
  155.                             else if( SHOULD_ENABLE_PENALTY_ON_ANGLE_TO_EXTENDED_HIGH_COVER && VisInfo.TargetCover == CT_Standing && NextTileOverCoverType == CT_Standing )
  156.                             {
  157.                                 bShouldAddAngleToCoverBonus = false;
  158.  
  159.                                 Alpha = FClamp((VisInfo.TargetCoverAngle - MIN_ANGLE_TO_COVER) / (MAX_ANGLE_TO_COVER - MIN_ANGLE_TO_COVER), 0.0, 1.0);
  160.                                 AngleToCoverModifier = Lerp(MAX_ANGLE_PENALTY,
  161.                                     MIN_ANGLE_PENALTY,
  162.                                     Alpha);
  163.                                 AddModifier(Round(-1.0 * AngleToCoverModifier), class'XLocalizedData'.default.BadAngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
  164.                             }
  165.                         }
  166.  
  167.                         if( bShouldAddAngleToCoverBonus )
  168.                         {
  169.                             Alpha = FClamp((VisInfo.TargetCoverAngle - MIN_ANGLE_TO_COVER) / (MAX_ANGLE_TO_COVER - MIN_ANGLE_TO_COVER), 0.0, 1.0);
  170.                             AngleToCoverModifier = Lerp(MAX_ANGLE_BONUS_MOD,
  171.                                                         MIN_ANGLE_BONUS_MOD,
  172.                                                         Alpha);
  173.                             AddModifier(Round(CoverValue * AngleToCoverModifier), class'XLocalizedData'.default.AngleToTargetCover, m_ShotBreakdown, eHit_Success, bDebugLog);
  174.                         }
  175.                     }
  176.                 }
  177.             }
  178.             //  Add height advantage
  179.             if (UnitState.HasHeightAdvantageOver(TargetState, true))
  180.             {
  181.                 AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightAdvantageBonus, class'XLocalizedData'.default.HeightAdvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
  182.             }
  183.  
  184.             //  Check for height disadvantage
  185.             if (TargetState.HasHeightAdvantageOver(UnitState, false))
  186.             {
  187.                 AddModifier(class'X2TacticalGameRuleset'.default.UnitHeightDisadvantagePenalty, class'XLocalizedData'.default.HeightDisadvantage, m_ShotBreakdown, eHit_Success, bDebugLog);
  188.             }
  189.         }
  190.     }
  191.  
  192.     if (UnitState.IsConcealed())
  193.     {
  194.         `log("Shooter is concealed, target cannot dodge.", bDebugLog, 'XCom_HitRolls');
  195.     }
  196.     else
  197.     {
  198.         if (SourceWeapon == none || SourceWeapon.CanWeaponBeDodged())
  199.         {
  200.             if (TargetState.CanDodge(UnitState, kAbility))
  201.             {
  202.                 AddModifier(TargetState.GetCurrentStat(eStat_Dodge), class'XLocalizedData'.default.DodgeStat, m_ShotBreakdown, eHit_Graze, bDebugLog);
  203.             }
  204.             else
  205.             {
  206.                 `log("Target cannot dodge due to some gameplay effect.", bDebugLog, 'XCom_HitRolls');
  207.             }
  208.         }                  
  209.     }
  210.  
  211.     // For all effects?
  212.     foreach UnitState.AffectedByEffects(EffectRef)
  213.         {
  214.             EffectModifiers.Length = 0;
  215.             EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
  216.             if (EffectState == none)
  217.                 continue;
  218.  
  219.             PersistentEffect = EffectState.GetX2Effect();
  220.             if (PersistentEffect == none)
  221.                 continue;
  222.  
  223.             if (UniqueToHitEffects.Find(PersistentEffect) != INDEX_NONE)
  224.                 continue;
  225.  
  226.             PersistentEffect.GetToHitModifiers(EffectState, UnitState, TargetState, kAbility, self.Class, bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
  227.             if (EffectModifiers.Length > 0)
  228.             {
  229.                 if (PersistentEffect.UniqueToHitModifiers())
  230.                     UniqueToHitEffects.AddItem(PersistentEffect);
  231.  
  232.                 for (i = 0; i < EffectModifiers.Length; ++i)
  233.                 {
  234.                     if (!bAllowCrit && EffectModifiers[i].ModType == eHit_Crit)
  235.                     {
  236.                         if (!PersistentEffect.AllowCritOverride())
  237.                             continue;
  238.                     }
  239.                     AddModifier(EffectModifiers[i].Value, EffectModifiers[i].Reason, m_ShotBreakdown, EffectModifiers[i].ModType, bDebugLog);
  240.                 }
  241.             }
  242.             if (PersistentEffect.ShotsCannotGraze())
  243.             {
  244.                 bIgnoreGraze = true;
  245.                 // IgnoreGrazeReason = PersistentEffect.FriendlyName;
  246.             }
  247.         }
  248.         UniqueToHitEffects.Length = 0;
  249.         if (TargetState.AffectedByEffects.Length > 0)
  250.         {
  251.             foreach TargetState.AffectedByEffects(EffectRef)
  252.             {
  253.                 EffectModifiers.Length = 0;
  254.                 EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));
  255.                 if (EffectState == none)
  256.                     continue;
  257.  
  258.                 PersistentEffect = EffectState.GetX2Effect();
  259.                 if (PersistentEffect == none)
  260.                     continue;
  261.  
  262.                 if (UniqueToHitEffects.Find(PersistentEffect) != INDEX_NONE)
  263.                     continue;
  264.  
  265.                 PersistentEffect.GetToHitAsTargetModifiers(EffectState, UnitState, TargetState, kAbility, self.Class, bMeleeAttack, bFlanking, bIndirectFire, EffectModifiers);
  266.                 if (EffectModifiers.Length > 0)
  267.                 {
  268.                     if (PersistentEffect.UniqueToHitAsTargetModifiers())
  269.                         UniqueToHitEffects.AddItem(PersistentEffect);
  270.  
  271.                     for (i = 0; i < EffectModifiers.Length; ++i)
  272.                     {
  273.                         if (!bAllowCrit && EffectModifiers[i].ModType == eHit_Crit)
  274.                             continue;
  275.                         if (bIgnoreGraze && EffectModifiers[i].ModType == eHit_Graze)
  276.                             continue;
  277.                         AddModifier(EffectModifiers[i].Value, EffectModifiers[i].Reason, m_ShotBreakdown, EffectModifiers[i].ModType, bDebugLog);
  278.                     }
  279.                 }
  280.             }
  281.         }
  282.         `log("Created shot breakdown, finalizing...", bDebugLog, 'XCom_HitRolls');
  283.         FinalizeBasicHitChance(m_ShotBreakdown, bDebugLog);
  284.     `log ("Final hit chance determined to be" $ m_ShotBreakdown.FinalHitChance, bDebugLog, 'XCom_HitRolls');
  285.     return m_ShotBreakdown.FinalHitChance;
  286. }
  287.  
  288. function FinalizeBasicHitChance(out ShotBreakdown m_ShotBreakdown, bool bDebugLog = false)
  289. {
  290.     local int i;
  291.     local EAbilityHitResult HitResult;
  292.     local float GrazeScale;
  293.     local int FinalGraze;
  294.  
  295.     `log("==" $ GetFuncName() $ "==\n", bDebugLog, 'XCom_HitRolls');
  296.     `log("Starting values...", bDebugLog, 'XCom_HitRolls');
  297.     for (i = 0; i < eHit_MAX; ++i)
  298.     {
  299.         HitResult = EAbilityHitResult(i);
  300.         `log(HitResult $ ":" @ m_ShotBreakdown.ResultTable[i], bDebugLog, 'XCom_HitRolls');
  301.     }
  302.  
  303.     m_ShotBreakdown.FinalHitChance = m_ShotBreakdown.ResultTable[eHit_Success];
  304.     //  if crit goes negative, hit would get a boost, so restrict it to 0
  305.     if (m_ShotBreakdown.ResultTable[eHit_Crit] < 0)
  306.         m_ShotBreakdown.ResultTable[eHit_Crit] = 0;
  307.     //  cap success at 100 so it can be fully overridden by crit
  308.     m_ShotBreakdown.ResultTable[eHit_Success] = min(m_ShotBreakdown.ResultTable[eHit_Success], 100);
  309.     //  Crit is folded into the chance to hit, so lower accordingly
  310.     m_ShotBreakdown.ResultTable[eHit_Success] -= m_ShotBreakdown.ResultTable[eHit_Crit];
  311.     //  Graze is scaled against Success - but ignored if success is 100%
  312.     if (m_ShotBreakdown.ResultTable[eHit_Graze] > 0)
  313.     {
  314.         if (m_ShotBreakdown.FinalHitChance < 100)
  315.         {
  316.             GrazeScale = float(m_ShotBreakdown.ResultTable[eHit_Graze]) / 100.0f;
  317.             GrazeScale *= float(m_ShotBreakdown.FinalHitChance);
  318.             FinalGraze = Round(GrazeScale);
  319.             m_ShotBreakdown.ResultTable[eHit_Success] -= FinalGraze;
  320.             m_ShotBreakdown.ResultTable[eHit_Graze] = FinalGraze;
  321.         }
  322.         else
  323.         {
  324.             m_ShotBreakdown.ResultTable[eHit_Graze] = 0;
  325.         }
  326.     }
  327.  
  328.     if (m_ShotBreakdown.FinalHitChance >= 100)
  329.     {
  330.         m_ShotBreakdown.ResultTable[eHit_Miss] = 0;
  331.     }
  332.     else
  333.     {
  334.         m_ShotBreakdown.ResultTable[eHit_Miss] = 100 - m_ShotBreakdown.FinalHitChance;
  335.     }
  336.    
  337.     `log("Calculated values...", bDebugLog, 'XCom_HitRolls');
  338.     for (i = 0; i < eHit_MAX; ++i)
  339.     {
  340.         HitResult = EAbilityHitResult(i);
  341.         `log(HitResult $ ":" @ m_ShotBreakdown.ResultTable[i], bDebugLog, 'XCom_HitRolls');
  342.     }
  343.     `log("Final hit chance (success + crit + graze) =" @ m_ShotBreakdown.FinalHitChance, bDebugLog, 'XCom_HitRolls');
  344.  
  345.     //"Negative chance to hit" is used as a token in UI code - don't ever report that.
  346.     if (m_ShotBreakdown.FinalHitChance < 0)
  347.     {
  348.         `log("FinalHitChance was less than 0 (" $ m_ShotBreakdown.FinalHitChance $ ") and was clamped to avoid confusing the UI (@btopp).", bDebugLog, 'XCom_HitRolls');
  349.         m_ShotBreakdown.FinalHitChance = 0;
  350.     }
  351. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement