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