Talk:GetThreatRatio

Revision as of 18:37, 12 July 2022 by DavidJCobb (talk | contribs)

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 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.
  • b += a
  • If b is less 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)

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.

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;
}
Return to "GetThreatRatio" page.