Jump to content
  • 0

Possible bug(s) with damage calculation / weapon damage


Question

Posted (edited)

After the discussion on the forums about critical hit damage being too high, and after seeing some questionable results from various things in the game I decided to ask Josh Sawyer about how total damage was calculated in Pillars of Eternity.

His reply was
 
5n4Emzs.png
 
Now much of the time, I'm not seeing results in this range on weapons. So I decided to look at the source code to see how damage is actually calculated in the game. The method which I think is most relevant is below.

This is the AdjustDamageDealt method from the class CharacterStats, which is called in the AttackBase.calcDamage() method
 

public void AdjustDamageDealt(GameObject enemy, ref DamageInfo damage)
  {
    damage.DamageAmount *= this.StatDamageHealMultiplier;
    if (this.OnPreDamageDealt != null)
      this.OnPreDamageDealt(((Component) this).get_gameObject(), new CombatEventArgs(damage, ((Component) this).get_gameObject(), enemy));
    if (this.OnAddDamage != null)
      this.OnAddDamage(((Component) this).get_gameObject(), new CombatEventArgs(damage, ((Component) this).get_gameObject(), enemy));
    int roll = Random.Range(1, 101);
    CharacterStats enemyStats = (CharacterStats) enemy.GetComponent<CharacterStats>();
    if (Object.op_Equality((Object) enemyStats, (Object) null))
      return;
    int toHitRollOverride = enemyStats.GetAttackerToHitRollOverride(roll);
    if (this.OnAttackRollCalculated != null)
      this.OnAttackRollCalculated(((Component) this).get_gameObject(), new CombatEventArgs(damage, ((Component) this).get_gameObject(), enemy));
    int num1 = this.CalculateAccuracy(damage.Attack, enemy);
    int num2 = enemyStats.CalculateDefense(damage.DefendedBy, damage.Attack, ((Component) this).get_gameObject());
    if (damage.DefendedBy != CharacterStats.DefenseType.None)
    {
      this.ComputeHitAdjustment(toHitRollOverride + num1 - num2, enemyStats, ref damage);
      if (damage.IsCriticalHit)
        damage.DamageAmount *= this.CriticalHitMultiplier;
      else if (damage.IsGraze)
        damage.DamageAmount *= CharacterStats.GrazeMultiplier;
      else if (damage.IsMiss)
        damage.DamageAmount = 0.0f;
    }
    damage.AccuracyRating = num1;
    damage.DefenseRating = num2;
    damage.RawRoll = toHitRollOverride;
    if (this.OnAdjustCritGrazeMiss != null)
      this.OnAdjustCritGrazeMiss(((Component) this).get_gameObject(), new CombatEventArgs(damage, ((Component) this).get_gameObject(), enemy));
    if (!damage.IsMiss)
    {
      if (damage.Attack.IsDisengagementAttack)
        damage.DamageAmount += this.DisengagementDamageBonus;
      if (damage.Attack is AttackMelee)
      {
        damage.DamageAmount += this.BonusMeleeDamage;
        if ((damage.Attack as AttackMelee).Unarmed)
          damage.DamageAmount += this.BonusUnarmedDamage;
      }
      for (int index = 0; index < this.BonusDamage.Length; ++index)
      {
        if ((double) this.BonusDamage[index] != 0.0)
        {
          DamagePacket.DamageProcType damageProcType = new DamagePacket.DamageProcType((DamagePacket.DamageType) index, this.BonusDamage[index]);
          damage.Damage.DamageProc.Add(damageProcType);
        }
      }
      this.AddBonusDamagePerType(damage);
      this.AddBonusDamagePerRace(damage, enemyStats);
      if (Object.op_Inequality((Object) damage.Attack, (Object) null))
      {
        Equippable equippable = (Equippable) ((Component) damage.Attack).GetComponent<Equippable>();
        if (Object.op_Inequality((Object) equippable, (Object) null))
        {
          if (equippable is Weapon)
          {
            if (damage.Attack is AttackMelee)
            {
              damage.DamageAmount *= this.BonusMeleeWeaponDamageMult;
            }
            else
            {
              damage.DamageAmount *= this.BonusRangedWeaponDamageMult;
              if (Object.op_Inequality((Object) enemy, (Object) null) && !this.IsEnemyDistant(enemy))
                damage.DamageAmount *= this.BonusRangedWeaponCloseEnemyDamageMult;
            }
          }
          equippable.ApplyItemModDamageProcs(ref damage);
        }
      }
    }
    this.ComputeInterrupt(enemyStats, ref damage);
    if (this.m_isPartyMember)
    {
      if (Object.op_Implicit((Object) enemyStats))
      {
        enemyStats.RevealDefense(damage.DefendedBy);
        enemyStats.RevealDT(damage.Damage.Type);
        using (List<DamagePacket.DamageProcType>.Enumerator enumerator = damage.Damage.DamageProc.GetEnumerator())
        {
          while (enumerator.MoveNext())
          {
            DamagePacket.DamageProcType current = enumerator.Current;
            enemyStats.RevealDT(current.Type);
          }
        }
      }
      if (damage.DefenseRating >= damage.AccuracyRating + 50)
      {
        GameState.AutoPause(AutoPauseOptions.PauseEvent.ExtraordinaryDefence, ((Component) this).get_gameObject(), enemy);
        TutorialManager.STriggerTutorialsOfTypeFast(TutorialManager.ExclusiveTriggerType.PARTYMEM_GETS_DEFENSE_TOO_HIGH);
      }
      if ((double) damage.MaxDamage - (double) damage.DTRating < (double) damage.MinDamage && Object.op_Inequality((Object) ((Component) damage.Attack).GetComponent<Weapon>(), (Object) null))
        GameState.AutoPause(AutoPauseOptions.PauseEvent.WeaponIneffective, ((Component) this).get_gameObject(), enemy);
    }
    if (this.OnPostDamageDealt == null)
      return;
    this.OnPostDamageDealt(((Component) this).get_gameObject(), new CombatEventArgs(damage, ((Component) this).get_gameObject(), enemy));
  }
  private void AddBonusDamagePerRace(DamageInfo damage, CharacterStats enemyStats)
  {
    if (enemyStats.CharacterRace >= (CharacterStats.Race) this.BonusDamagePerRace.Length || (double) this.BonusDamagePerRace[(int) enemyStats.CharacterRace] == 0.0)
      return;
    damage.DamageAmount += this.GetBonusDamagePerRace(enemyStats.CharacterRace, damage.DamageAmount);
  }

 
Now I do not have perfect understanding of the code, and there could be connected methods that I don't know about, but what I think I'm seeing here is that damage multipliers are individually multiplying the base damage, instead of being added together in a formula like Josh presented on tumblr. I have shown this code to four other people and they agree that this looks like how it's working.

To test damage multiplier stacking, I am using

  • The Rogue Class
  • Human or Aumaua to achieve High Might
  • Living Lands Culture
  • 18-20 Might score
  • Blinding Strike ability
  • Weapon Focus talent (whichever weapon I'm testing)
  • Dirty Fighting and Vicious Fighting talents to score easier critical hits
  • Have mostly been testing with 1H Normal style weapons, usually dual wielding

Here are some screenshots of the strange results I've been getting. 
 
I know for a fact that there is something definitely off about spears. They seem to be doing the most damage of any of the weapons currently, and it could be a separate issue. You can create a character from scratch, equip two fine spears and use Blinding Strike on a party member or any other unit and regularly score 70 damage critical hits (before DR).
 
Here is a Fine Sword crit for 70.7 damage (20 Might Aumaua Rogue). There is something strange about this one because it says Medreth only has 21 Deflection :/ I'm not sure why, it should be 40-something, unless the Bliding affliction was applied before the damage hit or something.
 
7jdZbxc.jpg
 
Fine Spear crit hit for 70.8 damage (max should be 38.24)

TkEFsLp.jpg
 
45.3 damage with a Fine Sabre crit with Crippling Strike (18 Might Orlan Rogue)

pRjF6Cj.jpg
 
47.9 damage sword HIT with Blinding Strike (Aumaua Rogue 20 Might)

VqyGpPm.jpg
 
44.8 damage sword HIT on the BB Fighter with Blinding Strike 

3t8XZe5.jpg
 
41.9 damage War Hammer crit on Crippling Strike (Human 18 Might, max should be 38.24)
 
y1oOQCg.jpg
 
Another instance - same character of 42.4
 
36xqeKV.jpg
 
Fine Sabre crit of 49.3 on a Crippling Strike (18 Might Human, max should be 38.24)
 
6gFCmyS.jpg

 

You get the picture.

I am not 100% sure what the problem is, I suspect some of it might be related to the way in which damage is calculated in the source code, but it's possible that some of these are separate bugs and not the same bug.

For instance, the spear screenshot is from a freshly launched game with a fresh character, no save/load or anything. Some of the others are from saved games.

As I learn new information, I will post it.

Edited by Sensuki
  • Like 5

10 answers to this question

Recommended Posts

  • 0
Posted (edited)

Thanks for digging this up.

 

Looking at the code, I agree that the multipliers are not applied in the right order (as I expected all along as well).

What you can see from the code is that the formula is something like

 

[(BaseDamage * (1+Might_Mult) *(1+ Crit/Miss_Mult) + Disengagement_Bonus + Melee/Unarmed/Ranged_Bonus )*(1+Melee/Unarmed/Ranged_Mult) - DT] * (1+Racial_Mult)

 

It's unclear whether the damage bonus from enhancements or support spells goes into the base damage or anywhere else.

Edited by Doppelschwert
  • Like 1
  • 0
Posted (edited)

Doppleshwert formula is accurate with the code from what I can see. I'm wondering why the attack roll is using a 1-101 range instead of 1-100 though (the function is inclusive in Unity).

 

And that explain how a rogue can get more than x3.0 instead of x2.39, heheheh.

Edited by morhilane

Azarhal, Chanter and Keeper of Truth of the Obsidian Order of Eternity.


  • 0
Posted

That's just how unity handles their .random function for integers. For floats it's the starting and ending number

  • 0
Posted

I know how the function works, that's why I'm wondering why 101 is a possible value for an attack roll. I though it was supposed to be 1-100. Not that it change anything to the maths.

Azarhal, Chanter and Keeper of Truth of the Obsidian Order of Eternity.


  • 0
Posted

I know how the function works, that's why I'm wondering why 101 is a possible value for an attack roll. I though it was supposed to be 1-100. Not that it change anything to the maths.

 

Come on,  that's an extra 1% ish bonus to Crit Chance that you're trying to quash...

  • 0
Posted

Yep,  they need to sum the some of the mult mods before *= them.   Needs an extra local variable.  And since they're going to do that.... 

Why not subtract DT before applying Crit Mods (and sneak attack? ! :), it'd just be a cut and paste :)

 

Actually,  if they do sum modifiers instead of doing a cumulative product, it'd probably be easier to balance things.  I'm just trying to get some differentiation between Might and Per as stats.

  • Like 1
  • 0
Posted

I know how the function works, that's why I'm wondering why 101 is a possible value for an attack roll. I though it was supposed to be 1-100. Not that it change anything to the maths.

It isn't. The engine for some reason requires you to state one higher than the maximum integer as the range :p. 100 is the maximum in the game.

  • 0
Posted

 

I know how the function works, that's why I'm wondering why 101 is a possible value for an attack roll. I though it was supposed to be 1-100. Not that it change anything to the maths.

It isn't. The engine for some reason requires you to state one higher than the maximum integer as the range :p. 100 is the maximum in the game.

 

 

 

OMG, you are right. What kind of morons doesn't keep his function behavior the same regardless of types? There is no point for it to work different between floats and integers.

Azarhal, Chanter and Keeper of Truth of the Obsidian Order of Eternity.


×
×
  • Create New...