Talk:GetThreatRatio
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. 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)
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;
}
Actor::Unk_E6
Intended functionality unknown. It always returns zero.