Skip to content

Commit bbe9a6b

Browse files
committed
Add CombatFormulas::CalcWeaponDamage and the explosion damage path
CalcWeaponDamage works out the damage of one attack before armour, for combat and for the figure an item card shows. ID 2209001. For a launcher that is only half the story. A Fat Man's WEAP record is worth 18 damage, because the launcher is a tube and nearly all of the damage belongs to the shell it fires. Reaching the shell takes two more functions, and they are not interchangeable. TESObjectWEAP::GetProjectile, which is static, returns what a weapon fires, or null for one that fires nothing. The instance data's own override is preferred, then the base form's, and the ammunition's projectile stands in when neither names one, so a barrel that swaps the projectile and a change of ammunition are both followed. ID 2198934. Explosion::GetDamage returns what a blast already in the world is worth. It multiplies the explosion record's damage by that explosion's own damageMult, keeps the result in calculatedDamage and hands the same number back every time afterwards, so everything the engine asks about one blast agrees with itself. ID 2236658. Which of the two applies depends on whether anything has been fired. An item card describes a weapon that has fired nothing, so there is no Explosion to ask and CombatFormulas::GetWeaponDisplayDamage calls GetProjectile and reads the explosion record directly. A blast in the world calls Explosion::GetDamage instead. The two routes reach the same figure through different code. Explosion::GetActorOwner resolves an explosion's owner handle and returns it only when it names an actor, which is the aggressor a blast's blows are credited to. ID 2236659.
1 parent 29e7ac3 commit bbe9a6b

4 files changed

Lines changed: 65 additions & 0 deletions

File tree

‎include/RE/C/CombatFormulas.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ namespace RE
4242
return func(a_target, a_bodyPart, a_physicalDamage, a_damageTypes);
4343
}
4444

45+
// The damage one attack deals before the target's armour. Combat and the
46+
// damage an item card shows both work it out through this.
47+
//
48+
// a_avOwner is the actor the damage belongs to, and a TESNPC record is
49+
// taken as well, with the actor values read off that instead. a_condition
50+
// is the weapon's item health, and a_rangeMult the falloff for a target
51+
// past the weapon's range, 1.0 within it.
52+
[[nodiscard]] inline float CalcWeaponDamage(const TESForm* a_avOwner, const TESObjectWEAP::InstanceData* a_data, const TESAmmo* a_ammo, float a_condition, float a_rangeMult)
53+
{
54+
using func_t = decltype(&CombatFormulas::CalcWeaponDamage);
55+
static REL::Relocation<func_t> func{ ID::CombatFormulas::CalcWeaponDamage };
56+
return func(a_avOwner, a_data, a_ammo, a_condition, a_rangeMult);
57+
}
58+
4559
[[nodiscard]] inline std::int64_t GetNumCrippledAttackConditions(Actor* a_actor)
4660
{
4761
using func_t = decltype(&CombatFormulas::GetNumCrippledAttackConditions);

‎include/RE/E/Explosion.h‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
namespace RE
1616
{
17+
class Actor;
1718
class ActorCause;
1819
class NiLight;
1920
class NonActorMagicCaster;
@@ -74,6 +75,33 @@ namespace RE
7475
virtual void Update(float a_delta); // C7
7576
virtual void FindTargets(); // C8
7677

78+
// Whoever set the blast off, or null when the owner has gone away or is
79+
// not an actor. This is the aggressor the engine credits an explosion's
80+
// blows to.
81+
[[nodiscard]] Actor* GetActorOwner()
82+
{
83+
using func_t = decltype(&Explosion::GetActorOwner);
84+
static REL::Relocation<func_t> func{ ID::Explosion::GetActorOwner };
85+
return func(this);
86+
}
87+
88+
// What the blast is worth. Worked out from the explosion record's damage
89+
// and this explosion's damageMult the first time anything asks, then kept
90+
// in calculatedDamage and handed back unchanged after that.
91+
//
92+
// Every question the engine asks about a live blast is answered from
93+
// here, from whether it is worth looking for targets at all to what each
94+
// of them loses. The item card is the one thing that does not come
95+
// through it, because a weapon being looked at has set nothing off, so
96+
// CombatFormulas::GetWeaponDisplayDamage reads the explosion record
97+
// instead and the two arrive at the same number by different routes.
98+
[[nodiscard]] float GetDamage()
99+
{
100+
using func_t = decltype(&Explosion::GetDamage);
101+
static REL::Relocation<func_t> func{ ID::Explosion::GetDamage };
102+
return func(this);
103+
}
104+
77105
// members
78106
hknpClosestUniqueBodyIdHitCollector collector; // 110
79107
void* explosionDBHandle; // 520 - TODO

‎include/RE/IDs.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,12 @@ namespace RE::ID
10361036
inline constexpr REL::ID GetBuildConfirmQuestion{ 2223057 };
10371037
}
10381038

1039+
namespace Explosion
1040+
{
1041+
inline constexpr REL::ID GetActorOwner{ 2236659 };
1042+
inline constexpr REL::ID GetDamage{ 2236658 };
1043+
}
1044+
10391045
namespace ExteriorCellSingleton
10401046
{
10411047
inline constexpr REL::ID Singleton{ 4796370 };
@@ -2322,6 +2328,7 @@ namespace RE::ID
23222328

23232329
namespace TESObjectWEAP
23242330
{
2331+
inline constexpr REL::ID GetProjectile{ 2198934 };
23252332
inline constexpr REL::ID GetMeleeAttackSpeed{ 2198957 };
23262333
inline constexpr REL::ID GetMeleeAttackSpeedLabel{ 2198959 };
23272334
inline constexpr REL::ID Fire{ 2198960 };

‎include/RE/T/TESObjectWEAP.h‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,22 @@ namespace RE
140140
};
141141
static_assert(sizeof(Data) == 0x138);
142142

143+
// What this weapon fires, or null for one that fires nothing.
144+
//
145+
// The instance data's own override is preferred, then the base form's,
146+
// and the ammunition's projectile stands in when neither names one. A
147+
// muzzle or a barrel that swaps the projectile is therefore followed,
148+
// and so is a change of ammunition.
149+
//
150+
// a_instanceData may be null, in which case only the base form and the
151+
// ammunition are consulted.
152+
[[nodiscard]] static BGSProjectile* GetProjectile(const TESObjectWEAP* a_weapon, const TESAmmo* a_ammo, const InstanceData* a_instanceData)
153+
{
154+
using func_t = decltype(&TESObjectWEAP::GetProjectile);
155+
static REL::Relocation<func_t> func{ ID::TESObjectWEAP::GetProjectile };
156+
return func(a_weapon, a_ammo, a_instanceData);
157+
}
158+
143159
[[nodiscard]] MELEE_ATTACK_SPEED GetMeleeAttackSpeed()
144160
{
145161
using func_t = decltype(&TESObjectWEAP::GetMeleeAttackSpeed);

0 commit comments

Comments
 (0)