Difference between revisions of "Talk:GetThreatRatio"
Jump to navigation
Jump to search
DavidJCobb (talk | contribs) (typo) |
DavidJCobb (talk | contribs) (→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 | == 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 | * 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 | * 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 '' | * 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.