Difference between revisions of "Talk:GetThreatRatio"

From the CreationKit Wiki
Jump to navigation Jump to search
(typo)
(more RE)
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.
Line 7: Line 8:
* If ''b'' is less than 0.99, set it to 0.99.
* 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)''.
* 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)
=== 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 effective_health = percentage_upgraded;
  effective_health *= (GMST::fSmithingWeaponMax - 1.0F);
  effective_health += 1.0F;
  if (effective_health > 0.0) {
      return effective_health;
  }
  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>

Revision as of 17:37, 12 July 2022

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 effective_health = percentage_upgraded;
   effective_health *= (GMST::fSmithingWeaponMax - 1.0F);
   effective_health += 1.0F;
   if (effective_health > 0.0) {
      return effective_health;
   }
   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;
}