Difference between revisions of "Talk:GetThreatRatio"

From the CreationKit Wiki
Jump to navigation Jump to search
(typo)
(→‎Reverse engineering: more info; summary)
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
The "threat ratio" between two actors is one actor's "threat level" divided by the other actor's threat level. In Skyrim Classic, the threat level is calculated by ''float Actor::GetThreatLevel(float Arg1)'', at 0x006E0DE0. The calculation works as follows:
== Reverse engineering ==
The "threat ratio" between two actors is one actor's "threat level" divided by the other actor's threat level. In Skyrim Classic, the threat level is calculated by ''float Actor::GetThreatLevel(float estimated_dps)'', at 0x006E0DE0. The calculation works as follows:


* If Arg1 is less than or equal to zero, then it is set to the result of calling ''float Actor::Subroutine006A93D0()''.
* If ''estimated_dps'' is less than or equal to zero, then it is set to the result of calling ''float Actor::Subroutine006A93D0()'', used to estimate the DPS (damage per second) the actor is capable of inflicting.
* Let ''a = GMST::fArmorScalingFactor'' * the actor's current ''Damage Resist'' actor value / 100.
* Let ''a = GMST::fArmorScalingFactor'' * the actor's current ''Damage Resist'' actor value / 100.
* Let ''b'' equal the result of calling ''float Actor::Unk_E6()'' on the actor.
* Let ''b'' equal the result of calling ''float Actor::Unk_E6()'' on the actor. This function appears to just return zero immediately, so ''b = 0''.
* ''b += a''
* ''b += a''
* If ''b'' is less than 0.99, set it to 0.99.
* If ''b'' is greater than 0.99, set it to 0.99.
* Let ''c'' equal the actor's current ''Health'' actor value divided by ''(1.0 - b)''.
* Let ''c'' equal the actor's current ''Health'' actor value divided by ''(1.0 - b)''.
* The threat level is ''Arg1 * c''.
* The threat level is ''estimated_dps * c''.


Values are explained to the extent that I understand any of them. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2018-07-23T14:05:46 (EDT)
Values are explained to the extent that I understand any of them. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2018-07-23T14:05:46 (EDT)
:Came back and updated a few things. We can now see that the threat level is:
:<math alt="Estimated DPS times (Health / (1 - min(0.99, fArmorScalingFactor times Damage Resist divided by 100))">
Estimated\ DPS \times \dfrac{Current\ Health\ AV}{1 - \operatorname{min}\left(\dfrac{fArmorScalingFactor \times Damage\ Resist\ AV}{100} , 0.99\right)}
</math>
:[[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 19:02, 12 July 2022 (EDT)
=== Actor::Subroutine006E3700 ===
If the actor has no AI process, the ''EstimateActorAttackDPS'' function is used verbatim. Otherwise, it's still called, but a whole lot of complicated stuff is done to its output before use.
<source lang="c++">
struct HealthDataInfo {
  GameSetting* value;        // fHealthDataValue1
  GameSetting* prefix_weapon; // sHealthDataPrefixWeap1
  GameSetting* prefix_armor;  // sHealthDataPrefixArmo1
};
static std::array<HealthDataInfo, 6> HealthDataInfoList;
//
void GetSmithingHealthDifference(float* minimum_value, float* max_upgrade_delta) {
  if (!HealthDataInfoList[0].value || !HealthDataInfoList[5].value) {
      *minimum_value    = 1.0F;
      *max_upgrade_delta = 0.0F;
      return;
  }
  *minimum_value = HealthDataInfoList[0].value->f;
 
  float a;
  if (!HealthDataInfoList[5].value) {
      *(uint32_t*)(0x01B92D8C) = 0;
      a = 0;
  } else {
      a = HealthDataInfoList[5].value->f;
  }
  if (HealthDataInfoList[0].value) {
      *max_upgrade_delta = a - HealthDataInfoList[0].value->f;
  } else {
      *max_upgrade_delta = a - 0;
  }
}
float GetWeaponSmithingDamageBoost(float weapon_health) {
  float smithing_min_value = 1.0F; // esp04
  float smithing_max_delta = 0.0F; // esp00
  GetSmithingHealthDifference(&smithing_min_value, &smithing_max_delta);
  if (0.0 == smithing_max_delta)
      smithing_max_delta = 1.0F;
 
  float percentage_upgraded = (weapon_health - smithing_min_value) / smithing_max_delta;
 
  float result = percentage_upgraded;
  result *= (GMST::fSmithingWeaponMax - 1.0F);
  result += 1.0F;
  if (result > 0.0) {
      return result;
  }
  return 0;
}
float sub0059A6B0(
  ActorValueOwner* attacker, // esi
  TESObjectWEAP*  weapon,  // ebp
  TESAmmo*        ammo,    // edi
  float        Arg4,
  float        Arg5, // weapon health i.e. tempering?
  UNKNOWN_TYPE Arg6
) {
  int32_t weaponGD20 = weapon ? weapon->gameData.unk20 : -1; // weapon skill enum?
  if (!ammo) {
      if (Arg6 && attacker && attacker->Unk_08()) {
        ammo = (*g_thePlayer)->GetCurrentAmmo();
      }
  }
  float esp20 = weapon ? weapon->damage.getAttackDamage() : 0.0F;
  float esp1C = ammo ? ammo->settings.damage : 0.0F;
 
  float base_weapon_damage = (esp1C + esp20) * GMST::fDamageWeaponMult; // esp10
 
  float smithing_bonus = 0; // esp1C
  float actor_value    = 0; // esp20
  if (weapon) {
      switch (ebp->gameData.type) {
        case kType_OneHandAxe:
        case kType_OneHandMace:
        case kType_TwoHandSword:
        case kType_TwoHandAxe:
        case kType_Bow:
        case kType_Staff:
            smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
            if (attacker)
              actor_value = attacker->GetCurrent(0x22) + 0.0; // CALL; FLDZ; FADDP ST(1), ST(0); FSTP [ESP+20], ST(0)
            break;
        case kType_OneHandSword:
        case kType_OneHandDagger:
            if (attacker)
              actor_value = attacker->GetCurrent(0x23) + 0.0; // OneHanded?
            if (weapon != *ptrUnarmedForm) {
              smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
              break;
            }
            [[fallthrough]];
        case kType_CrossBow:
            smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
            break;
      }
  }
  //
  float esp18 = 100.0F;
  if (attacker) {
      if (!ammo) {
        actor_value = Arg1->GetCurrent(0x23) + 0.0;
      }
      if (attacker) { // probably an inlined "get weapon skill" non-member function
        if (weaponGD20 >= 6 || weaponGD20 <= 0x17) {
            esp18 = attacker->sub005AF470(weaponGD20);
        }
      }
  }
  if (attacker) {
      if (weaponGD20 >= 6 && weaponGD20 <= 0x17) {
        esp18 = sub00599900(Arg1, esp18);
      } else {
        esp18 = 1.0F;
      }
  } else {
      esp18 = 1.0F;
  }
  //
  float esp28 = Arg5 * esp18;
  if (weapon && weapon->gameData.type <= 6) {
      auto& ecx = Singleton012E7560::get();
      if (!ecx.unk10) {
        esp18 = *(float*)0x01B4ADE0;
      }
  }
  return (((base_weapon_damage + smithing_bonus) * esp28) + actor_value) * esp18;
}
float EstimateActorAttackDPS(
  ActorValueOwner* attacker, // esi
  TESObjectWEAP*  weapon,  // ebp
  TESAmmo*        ammo      // edi
) {
  if (!weapon)
      weapon = *ptrUnarmedForm;
 
  float result = sub0059A6B0(attacker, weapon, ammo, 1.0F, 1.0F, 0);
  result *= weapon->gameData.speed;
 
  if (weapon->gameData.type != 7 && weapon->gameData.type != 2) {
      return result * GMST::fCombatDPSMeleeSpeedMult;
  }
  return result * GMST::fCombatDPSBowSpeedMult;
}</source>
=== Actor::Unk_E6 ===
Intended functionality unknown. It always returns zero.

Latest revision as of 18:02, 12 July 2022

Reverse engineering[edit source]

The "threat ratio" between two actors is one actor's "threat level" divided by the other actor's threat level. In Skyrim Classic, the threat level is calculated by float Actor::GetThreatLevel(float estimated_dps), at 0x006E0DE0. The calculation works as follows:

  • If estimated_dps is less than or equal to zero, then it is set to the result of calling float Actor::Subroutine006A93D0(), used to estimate the DPS (damage per second) the actor is capable of inflicting.
  • Let a = GMST::fArmorScalingFactor * the actor's current Damage Resist actor value / 100.
  • Let b equal the result of calling float Actor::Unk_E6() on the actor. This function appears to just return zero immediately, so b = 0.
  • b += a
  • If b is greater than 0.99, set it to 0.99.
  • Let c equal the actor's current Health actor value divided by (1.0 - b).
  • The threat level is estimated_dps * c.

Values are explained to the extent that I understand any of them. DavidJCobb (talk) 2018-07-23T14:05:46 (EDT)

Came back and updated a few things. We can now see that the threat level is:
DavidJCobb (talk) 19:02, 12 July 2022 (EDT)

Actor::Subroutine006E3700[edit source]

If the actor has no AI process, the EstimateActorAttackDPS function is used verbatim. Otherwise, it's still called, but a whole lot of complicated stuff is done to its output before use.

struct HealthDataInfo {
   GameSetting* value;         // fHealthDataValue1
   GameSetting* prefix_weapon; // sHealthDataPrefixWeap1
   GameSetting* prefix_armor;  // sHealthDataPrefixArmo1
};
static std::array<HealthDataInfo, 6> HealthDataInfoList;

// 
void GetSmithingHealthDifference(float* minimum_value, float* max_upgrade_delta) {
   if (!HealthDataInfoList[0].value || !HealthDataInfoList[5].value) {
      *minimum_value     = 1.0F;
      *max_upgrade_delta = 0.0F;
      return;
   }
   *minimum_value = HealthDataInfoList[0].value->f;
   
   float a;
   if (!HealthDataInfoList[5].value) {
      *(uint32_t*)(0x01B92D8C) = 0;
      a = 0;
   } else {
      a = HealthDataInfoList[5].value->f;
   }
   if (HealthDataInfoList[0].value) {
      *max_upgrade_delta = a - HealthDataInfoList[0].value->f;
   } else {
      *max_upgrade_delta = a - 0;
   }
}

float GetWeaponSmithingDamageBoost(float weapon_health) {
   float smithing_min_value = 1.0F; // esp04
   float smithing_max_delta = 0.0F; // esp00
   GetSmithingHealthDifference(&smithing_min_value, &smithing_max_delta);
   if (0.0 == smithing_max_delta)
      smithing_max_delta = 1.0F;
   
   float percentage_upgraded = (weapon_health - smithing_min_value) / smithing_max_delta;
   
   float result = percentage_upgraded;
   result *= (GMST::fSmithingWeaponMax - 1.0F);
   result += 1.0F;
   if (result > 0.0) {
      return result;
   }
   return 0;
}

float sub0059A6B0(
   ActorValueOwner* attacker, // esi
   TESObjectWEAP*   weapon,   // ebp
   TESAmmo*         ammo,     // edi
   float        Arg4,
   float        Arg5, // weapon health i.e. tempering?
   UNKNOWN_TYPE Arg6
) {
   int32_t weaponGD20 = weapon ? weapon->gameData.unk20 : -1; // weapon skill enum?
   if (!ammo) {
      if (Arg6 && attacker && attacker->Unk_08()) {
         ammo = (*g_thePlayer)->GetCurrentAmmo();
      }
   }
   float esp20 = weapon ? weapon->damage.getAttackDamage() : 0.0F;
   float esp1C = ammo ? ammo->settings.damage : 0.0F;
   
   float base_weapon_damage = (esp1C + esp20) * GMST::fDamageWeaponMult; // esp10
   
   float smithing_bonus = 0; // esp1C
   float actor_value    = 0; // esp20
   if (weapon) {
      switch (ebp->gameData.type) {
         case kType_OneHandAxe:
         case kType_OneHandMace:
         case kType_TwoHandSword:
         case kType_TwoHandAxe:
         case kType_Bow:
         case kType_Staff:
            smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
            if (attacker)
               actor_value = attacker->GetCurrent(0x22) + 0.0; // CALL; FLDZ; FADDP ST(1), ST(0); FSTP [ESP+20], ST(0)
            break;
         case kType_OneHandSword:
         case kType_OneHandDagger:
            if (attacker)
               actor_value = attacker->GetCurrent(0x23) + 0.0; // OneHanded?
            if (weapon != *ptrUnarmedForm) {
               smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
               break;
            }
            [[fallthrough]];
         case kType_CrossBow:
            smithing_bonus = GetWeaponSmithingDamageBoost(Arg5);
            break;
      }
   }
   //
   float esp18 = 100.0F;
   if (attacker) {
      if (!ammo) {
         actor_value = Arg1->GetCurrent(0x23) + 0.0;
      }
      if (attacker) { // probably an inlined "get weapon skill" non-member function
         if (weaponGD20 >= 6 || weaponGD20 <= 0x17) {
            esp18 = attacker->sub005AF470(weaponGD20);
         }
      }
   }
   if (attacker) {
      if (weaponGD20 >= 6 && weaponGD20 <= 0x17) {
         esp18 = sub00599900(Arg1, esp18);
      } else {
         esp18 = 1.0F;
      }
   } else {
      esp18 = 1.0F;
   }
   //
   float esp28 = Arg5 * esp18;
   if (weapon && weapon->gameData.type <= 6) {
      auto& ecx = Singleton012E7560::get();
      if (!ecx.unk10) {
         esp18 = *(float*)0x01B4ADE0;
      }
   }
   return (((base_weapon_damage + smithing_bonus) * esp28) + actor_value) * esp18;
}

float EstimateActorAttackDPS(
   ActorValueOwner* attacker, // esi
   TESObjectWEAP*   weapon,   // ebp
   TESAmmo*         ammo      // edi
) {
   if (!weapon)
      weapon = *ptrUnarmedForm;
   
   float result = sub0059A6B0(attacker, weapon, ammo, 1.0F, 1.0F, 0);
   result *= weapon->gameData.speed;
   
   if (weapon->gameData.type != 7 && weapon->gameData.type != 2) {
      return result * GMST::fCombatDPSMeleeSpeedMult;
   }
   return result * GMST::fCombatDPSBowSpeedMult;
}

Actor::Unk_E6[edit source]

Intended functionality unknown. It always returns zero.