diff --git a/include/RE/A/ATTACK_STATE_ENUM.h b/include/RE/A/ATTACK_STATE_ENUM.h new file mode 100644 index 00000000..9e0ee3f3 --- /dev/null +++ b/include/RE/A/ATTACK_STATE_ENUM.h @@ -0,0 +1,15 @@ +#pragma once + +namespace RE +{ + enum class ATTACK_STATE_ENUM : std::int32_t + { + kNone = 0x0, + kDraw = 0x1, + kSwing = 0x2, + kHit = 0x3, + kNextAttack = 0x4, + kFollowThrough = 0x5, + kBash = 0x6 + }; +} diff --git a/include/RE/A/Actor.h b/include/RE/A/Actor.h index 661e59f2..cb22f411 100644 --- a/include/RE/A/Actor.h +++ b/include/RE/A/Actor.h @@ -307,6 +307,13 @@ namespace RE return currentProcess ? currentProcess->GetCurrentAmmo(a_equipIndex) : nullptr; } + [[nodiscard]] std::uint32_t GetCurrentAmmoCount(BGSEquipIndex a_equipIndex) const + { + using func_t = decltype(&Actor::GetCurrentAmmoCount); + static REL::Relocation func{ ID::Actor::GetCurrentAmmoCount }; + return func(this, a_equipIndex); + } + std::uint32_t GetCurrentCollisionGroup() { using func_t = decltype(&Actor::GetCurrentCollisionGroup); diff --git a/include/RE/A/ActorEquipManager.h b/include/RE/A/ActorEquipManager.h index 7d82f343..7d211c52 100644 --- a/include/RE/A/ActorEquipManager.h +++ b/include/RE/A/ActorEquipManager.h @@ -37,6 +37,22 @@ namespace RE return *singleton; } + // Whether the actor is allowed to equip the item the handle names, and + // what stops it when it is not. Equipping through the Pip-Boy, the + // favourites bar or a quick key asks this first and turns anything but + // kSuccess into the matching HUD message. + // + // kItemBroken is the one answer an unmodified game never gives. It comes + // from ExtraDataList::IsItemBroken, so it needs an item whose health has + // been written down as zero or less, and nothing in the base game writes + // an item's health at all. + [[nodiscard]] CanEquipResult CanEquip(Actor* a_actor, const std::uint32_t& a_handleID, std::uint32_t a_stackID) + { + using func_t = decltype(&ActorEquipManager::CanEquip); + static REL::Relocation func{ ID::ActorEquipManager::CanEquip }; + return func(this, a_actor, a_handleID, a_stackID); + } + bool EquipObject( Actor* a_actor, const BGSObjectInstance& a_object, diff --git a/include/RE/B/BGSConstructibleObject.h b/include/RE/B/BGSConstructibleObject.h index 4fba545c..0cb549ef 100644 --- a/include/RE/B/BGSConstructibleObject.h +++ b/include/RE/B/BGSConstructibleObject.h @@ -33,6 +33,16 @@ namespace RE [[nodiscard]] TESForm* GetCreatedItem() const noexcept { return createdItem; } [[nodiscard]] std::uint16_t GetWorkshopPriority() const noexcept { return data.workshopPriority; } + // The recipe that builds a form, or null when nothing builds it. Matches + // a recipe that creates the form itself, and one that creates a form list + // the form belongs to. + [[nodiscard]] static BGSConstructibleObject* FindRecipeForCreatedForm(const TESForm* a_form) + { + using func_t = decltype(&BGSConstructibleObject::FindRecipeForCreatedForm); + static REL::Relocation func{ ID::BGSConstructibleObject::FindRecipeForCreatedForm }; + return func(a_form); + } + bool PlayerPassesConditions() { using func_t = decltype(&BGSConstructibleObject::PlayerPassesConditions); diff --git a/include/RE/C/CombatFormulas.h b/include/RE/C/CombatFormulas.h index 7b5657eb..743663ca 100644 --- a/include/RE/C/CombatFormulas.h +++ b/include/RE/C/CombatFormulas.h @@ -42,6 +42,20 @@ namespace RE return func(a_target, a_bodyPart, a_physicalDamage, a_damageTypes); } + // The damage one attack deals before the target's armour. Combat and the + // damage an item card shows both work it out through this. + // + // a_avOwner is the actor the damage belongs to, and a TESNPC record is + // taken as well, with the actor values read off that instead. a_condition + // is the weapon's item health, and a_rangeMult the falloff for a target + // past the weapon's range, 1.0 within it. + [[nodiscard]] inline float CalcWeaponDamage(const TESForm* a_avOwner, const TESObjectWEAP::InstanceData* a_data, const TESAmmo* a_ammo, float a_condition, float a_rangeMult) + { + using func_t = decltype(&CombatFormulas::CalcWeaponDamage); + static REL::Relocation func{ ID::CombatFormulas::CalcWeaponDamage }; + return func(a_avOwner, a_data, a_ammo, a_condition, a_rangeMult); + } + [[nodiscard]] inline std::int64_t GetNumCrippledAttackConditions(Actor* a_actor) { using func_t = decltype(&CombatFormulas::GetNumCrippledAttackConditions); diff --git a/include/RE/E/EquippedWeaponData.h b/include/RE/E/EquippedWeaponData.h index 8ae19936..e98d37a7 100644 --- a/include/RE/E/EquippedWeaponData.h +++ b/include/RE/E/EquippedWeaponData.h @@ -1,5 +1,6 @@ #pragma once +#include "RE/A/ATTACK_STATE_ENUM.h" #include "RE/B/BGSObjectInstance.h" #include "RE/B/BSSoundHandle.h" #include "RE/B/BSTArray.h" @@ -9,7 +10,6 @@ namespace RE { - enum class ATTACK_STATE_ENUM; class AimModel; class BSCloneReserver; class MuzzleFlash; diff --git a/include/RE/E/Explosion.h b/include/RE/E/Explosion.h index 332bdcfe..3a9cb55b 100644 --- a/include/RE/E/Explosion.h +++ b/include/RE/E/Explosion.h @@ -14,6 +14,7 @@ namespace RE { + class Actor; class ActorCause; class NiLight; class NonActorMagicCaster; @@ -74,6 +75,33 @@ namespace RE virtual void Update(float a_delta); // C7 virtual void FindTargets(); // C8 + // Whoever set the blast off, or null when the owner has gone away or is + // not an actor. This is the aggressor the engine credits an explosion's + // blows to. + [[nodiscard]] Actor* GetActorOwner() + { + using func_t = decltype(&Explosion::GetActorOwner); + static REL::Relocation func{ ID::Explosion::GetActorOwner }; + return func(this); + } + + // What the blast is worth. Worked out from the explosion record's damage + // and this explosion's damageMult the first time anything asks, then kept + // in calculatedDamage and handed back unchanged after that. + // + // Every question the engine asks about a live blast is answered from + // here, from whether it is worth looking for targets at all to what each + // of them loses. The item card is the one thing that does not come + // through it, because a weapon being looked at has set nothing off, so + // CombatFormulas::GetWeaponDisplayDamage reads the explosion record + // instead and the two arrive at the same number by different routes. + [[nodiscard]] float GetDamage() + { + using func_t = decltype(&Explosion::GetDamage); + static REL::Relocation func{ ID::Explosion::GetDamage }; + return func(this); + } + // members hknpClosestUniqueBodyIdHitCollector collector; // 110 void* explosionDBHandle; // 520 - TODO diff --git a/include/RE/E/ExtraDataList.h b/include/RE/E/ExtraDataList.h index 5c67a9b9..af6fa03f 100644 --- a/include/RE/E/ExtraDataList.h +++ b/include/RE/E/ExtraDataList.h @@ -178,6 +178,17 @@ namespace RE return func(this); } + // Whether the health in this list is zero or less, which is what the game + // calls a broken item. The counterpart of IsDamaged above, which asks the + // weaker question of whether the health is anything below full. Both + // answer false for a list carrying no health at all. + inline bool IsItemBroken() + { + using func_t = decltype(&ExtraDataList::IsItemBroken); + static REL::Relocation func{ ID::ExtraDataList::IsItemBroken }; + return func(this); + } + inline bool CompareList(const ExtraDataList* a_compare, ComparisonQualifier a_qualifier) { using func_t = decltype(&ExtraDataList::CompareList); diff --git a/include/RE/E/ExtraLeveledItem.h b/include/RE/E/ExtraLeveledItem.h new file mode 100644 index 00000000..3149e919 --- /dev/null +++ b/include/RE/E/ExtraLeveledItem.h @@ -0,0 +1,26 @@ +#pragma once + +#include "RE/B/BSExtraData.h" + +namespace RE +{ + class __declspec(novtable) ExtraLeveledItem : + public BSExtraData // 00 + { + public: + static constexpr auto RTTI{ RTTI::ExtraLeveledItem }; + static constexpr auto VTABLE{ VTABLE::ExtraLeveledItem }; + static constexpr auto TYPE{ EXTRA_DATA_TYPE::kLevelItem }; + + ExtraLeveledItem(std::uint32_t a_levItem) : + BSExtraData(TYPE), + levItem(a_levItem) + { + REX::EMPLACE_VTABLE(this); + } + + // members + std::uint32_t levItem; // 18 + }; + static_assert(sizeof(ExtraLeveledItem) == 0x20); +} diff --git a/include/RE/E/ExtraOutfitItem.h b/include/RE/E/ExtraOutfitItem.h new file mode 100644 index 00000000..0367acd8 --- /dev/null +++ b/include/RE/E/ExtraOutfitItem.h @@ -0,0 +1,26 @@ +#pragma once + +#include "RE/B/BSExtraData.h" + +namespace RE +{ + class __declspec(novtable) ExtraOutfitItem : + public BSExtraData // 00 + { + public: + static constexpr auto RTTI{ RTTI::ExtraOutfitItem }; + static constexpr auto VTABLE{ VTABLE::ExtraOutfitItem }; + static constexpr auto TYPE{ EXTRA_DATA_TYPE::kOutfitItem }; + + ExtraOutfitItem(std::uint32_t a_outfit) : + BSExtraData(TYPE), + outfit(a_outfit) + { + REX::EMPLACE_VTABLE(this); + } + + // members + std::uint32_t outfit; // 18 + }; + static_assert(sizeof(ExtraOutfitItem) == 0x20); +} diff --git a/include/RE/Fallout.h b/include/RE/Fallout.h index 81fc7203..2e09d03b 100644 --- a/include/RE/Fallout.h +++ b/include/RE/Fallout.h @@ -23,6 +23,7 @@ #include "RE/A/AITimeStamp.h" #include "RE/A/AITimer.h" #include "RE/A/AMMO_DATA.h" +#include "RE/A/ATTACK_STATE_ENUM.h" #include "RE/A/ActionInput.h" #include "RE/A/ActionOutput.h" #include "RE/A/ActionPoints.h" @@ -691,6 +692,7 @@ #include "RE/E/ExtraHealth.h" #include "RE/E/ExtraInstanceData.h" #include "RE/E/ExtraLeveledCreature.h" +#include "RE/E/ExtraLeveledItem.h" #include "RE/E/ExtraLight.h" #include "RE/E/ExtraLinkedRef.h" #include "RE/E/ExtraLinkedRefChildren.h" @@ -698,6 +700,7 @@ #include "RE/E/ExtraLock.h" #include "RE/E/ExtraMapMarker.h" #include "RE/E/ExtraMaterialSwap.h" +#include "RE/E/ExtraOutfitItem.h" #include "RE/E/ExtraPoison.h" #include "RE/E/ExtraPowerLinks.h" #include "RE/E/ExtraRadioData.h" diff --git a/include/RE/IDs.h b/include/RE/IDs.h index d04ca216..c7f2cf7c 100644 --- a/include/RE/IDs.h +++ b/include/RE/IDs.h @@ -18,6 +18,7 @@ namespace RE::ID inline constexpr REL::ID GetClosestBone{ 2230051 }; inline constexpr REL::ID GetCollisionFilter{ 2277949 }; inline constexpr REL::ID GetCombatStyle{ 2231053 }; + inline constexpr REL::ID GetCurrentAmmoCount{ 2229950 }; inline constexpr REL::ID SetCurrentAmmoCount{ 2229952 }; inline constexpr REL::ID GetCurrentCollisionGroup{ 2229993 }; inline constexpr REL::ID GetCurrentFireLocation{ 2231167 }; @@ -77,6 +78,7 @@ namespace RE::ID inline constexpr REL::ID EquipObject{ 2231392 }; inline constexpr REL::ID UnequipObject{ 2231395 }; inline constexpr REL::ID UnequipItem{ 2231399 }; + inline constexpr REL::ID CanEquip{ 2231405 }; } namespace ActorUtils @@ -170,6 +172,7 @@ namespace RE::ID namespace BGSConstructibleObject { + inline constexpr REL::ID FindRecipeForCreatedForm{ 2197324 }; inline constexpr REL::ID PlayerPassesConditions{ 2197318 }; } @@ -1033,6 +1036,12 @@ namespace RE::ID inline constexpr REL::ID GetBuildConfirmQuestion{ 2223057 }; } + namespace Explosion + { + inline constexpr REL::ID GetActorOwner{ 2236659 }; + inline constexpr REL::ID GetDamage{ 2236658 }; + } + namespace ExteriorCellSingleton { inline constexpr REL::ID Singleton{ 4796370 }; @@ -1054,6 +1063,7 @@ namespace RE::ID inline constexpr REL::ID ClearFavorite{ 2190191 }; inline constexpr REL::ID IsFavorite{ 2190189 }; inline constexpr REL::ID IsDamaged{ 2190224 }; + inline constexpr REL::ID IsItemBroken{ 2190223 }; inline constexpr REL::ID CompareList{ 2190098 }; inline constexpr REL::ID SetFavorite{ 2190188 }; inline constexpr REL::ID GetPrimitive{ 2190427 }; @@ -2318,6 +2328,7 @@ namespace RE::ID namespace TESObjectWEAP { + inline constexpr REL::ID GetProjectile{ 2198934 }; inline constexpr REL::ID GetMeleeAttackSpeed{ 2198957 }; inline constexpr REL::ID GetMeleeAttackSpeedLabel{ 2198959 }; inline constexpr REL::ID Fire{ 2198960 }; diff --git a/include/RE/T/TESObjectWEAP.h b/include/RE/T/TESObjectWEAP.h index b34a703d..2d85d0e5 100644 --- a/include/RE/T/TESObjectWEAP.h +++ b/include/RE/T/TESObjectWEAP.h @@ -140,6 +140,22 @@ namespace RE }; static_assert(sizeof(Data) == 0x138); + // What this 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. A + // muzzle or a barrel that swaps the projectile is therefore followed, + // and so is a change of ammunition. + // + // a_instanceData may be null, in which case only the base form and the + // ammunition are consulted. + [[nodiscard]] static BGSProjectile* GetProjectile(const TESObjectWEAP* a_weapon, const TESAmmo* a_ammo, const InstanceData* a_instanceData) + { + using func_t = decltype(&TESObjectWEAP::GetProjectile); + static REL::Relocation func{ ID::TESObjectWEAP::GetProjectile }; + return func(a_weapon, a_ammo, a_instanceData); + } + [[nodiscard]] MELEE_ATTACK_SPEED GetMeleeAttackSpeed() { using func_t = decltype(&TESObjectWEAP::GetMeleeAttackSpeed);