Talk:GetThreatRatio
Active discussions
Reverse engineeringEdit
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::Subroutine006E3700Edit
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_E6Edit
Intended functionality unknown. It always returns zero.