diff --git a/include/RE/A/ActiveEffect.h b/include/RE/A/ActiveEffect.h index 79fbf1da7..03301963d 100644 --- a/include/RE/A/ActiveEffect.h +++ b/include/RE/A/ActiveEffect.h @@ -67,7 +67,7 @@ namespace RE [[nodiscard]] Actor* GetTargetActor(); // members - ActiveEffectReferenceEffectController hitEffectController; // 0C + ActiveEffectReferenceEffectController hitEffectController; // 10 BSSoundHandle persistentSound; // 30 ActorHandle caster; // 38 NiPointer sourceNode; // 40 @@ -77,11 +77,11 @@ namespace RE TESBoundObject* source; // 60 BSSimpleList* hitEffects; // 68 MagicItem* displacementSpell; // 70 - float elapsedSeconds; // 74 - float duration; // 78 - float magnitude; // 7C - REX::TEnumSet flags; // 80 - ConditionStatus conditionStatus; // 84 + float elapsedSeconds; // 78 + float duration; // 7C + float magnitude; // 80 + REX::TEnumSet flags; // 84 + ConditionStatus conditionStatus; // 88 std::uint16_t uniqueID; // 8C MagicSystem::CastingSource castingSource; // 90 }; diff --git a/include/RE/A/Actor.h b/include/RE/A/Actor.h index cb22f411d..969ef6d9f 100644 --- a/include/RE/A/Actor.h +++ b/include/RE/A/Actor.h @@ -335,6 +335,20 @@ namespace RE return func(this); } + // The item health an attack reads for the weapon in a_equipIndex, off the + // stack's health extra data, 1.0 for a stack that has none and 0.0 when + // no equipped weapon matches. + // + // It looks up the equip slot without naming an item, which always gives + // the first slot, so it reads the first equipped weapon stack flagged + // kSlotIndex1 whatever a_equipIndex says. + [[nodiscard]] float GetEquippedItemHealth(BGSEquipIndex a_equipIndex) const + { + using func_t = decltype(&Actor::GetEquippedItemHealth); + static REL::Relocation func{ ID::Actor::GetEquippedItemHealth }; + return func(this, a_equipIndex); + } + bool GetGhost() { using func_t = decltype(&Actor::GetGhost); @@ -476,6 +490,16 @@ namespace RE return func(this, a_deltaTime, a_deltaPos, a_defer); } + // What UseAmmo calls once the magazine in a_equipIndex runs dry. It + // releases the trigger of a gun in the middle of firing, which is what + // stops an automatic weapon at the last round. + void OnMagazineEmpty(BGSEquipIndex a_equipIndex) + { + using func_t = decltype(&Actor::OnMagazineEmpty); + static REL::Relocation func{ ID::Actor::OnMagazineEmpty }; + return func(this, a_equipIndex); + } + bool PerformAction(BGSAction* a_action, TESObjectREFR* a_target) { using func_t = decltype(&Actor::PerformAction); diff --git a/include/RE/A/ActorMagicCaster.h b/include/RE/A/ActorMagicCaster.h new file mode 100644 index 000000000..1e1a3bd4e --- /dev/null +++ b/include/RE/A/ActorMagicCaster.h @@ -0,0 +1,49 @@ +#pragma once + +#include "RE/B/BSTEvent.h" +#include "RE/M/MagicCaster.h" +#include "RE/R/RefAttachTechniqueInput.h" +#include "RE/S/SimpleAnimationGraphManagerHolder.h" + +namespace RE +{ + class BSAnimationGraphEvent; + + class __declspec(novtable) ActorMagicCaster : + public MagicCaster, // 00 + public SimpleAnimationGraphManagerHolder, // 48 + public BSTEventSink // 60 + { + public: + static constexpr auto RTTI{ RTTI::ActorMagicCaster }; + static constexpr auto VTABLE{ VTABLE::ActorMagicCaster }; + + ~ActorMagicCaster() override; // 00 + + // override (MagicCaster) + bool CheckCast(MagicItem* a_spell, bool a_dualCast, float* a_effectiveness, MagicSystem::CannotCastReason* a_reason, bool a_arg5) override; // 0B + TESObjectREFR* GetCasterStatsObject() const override; // 0C + Actor* GetCasterAsActor() const override; // 0D + NiNode* GetMagicNode() override; // 0F + void ClearMagicNode() override; // 10 + MagicSystem::CastingSource GetCastingSource() const override; // 16 + bool GetIsDualCasting() const override; // 17 + void SetDualCasting(bool a_dualCasting) override; // 18 + void AdjustActiveEffect(ActiveEffect* a_effect, float a_power, bool a_hostileEffectivenessOnly) override; // 1D + + // members + RefAttachTechniqueInput unk68; // 68 + std::uint64_t unkB0; // B0 + Actor* actor; // B8 + NiNode* magicNode; // C0 + std::uint64_t unkC8; // C8 + std::uint64_t unkD0; // D0 + std::uint64_t unkD8; // D8 + std::uint64_t unkE0; // E0 + std::uint64_t unkE8; // E8 + std::uint32_t unkF0; // F0 + MagicSystem::CastingSource castingSource; // F4 + std::uint32_t flags; // F8 + }; + static_assert(sizeof(ActorMagicCaster) == 0x100); +} diff --git a/include/RE/B/BGSAudio.h b/include/RE/B/BGSAudio.h index ebd7fce13..2d14f9514 100644 --- a/include/RE/B/BGSAudio.h +++ b/include/RE/B/BGSAudio.h @@ -4,6 +4,8 @@ namespace RE { class BGSSoundDescriptorForm; class BGSSoundOutput; + class NiAVObject; + class NiPoint3; class BGSAudio { @@ -21,5 +23,19 @@ namespace RE static REL::Relocation func{ ID::BGSAudio::GetUIOutputModel }; return func(); } + + // Plays a sound once. The last two arguments say where it plays from: + // a_objectToFollow the sound moves with this object + // a_position the sound stays at this point + // neither the sound plays from nowhere, like the click of + // an empty gun + // If both are given, the object wins. The game always adds 0x10 to + // a_usageFlags. + static void PlaySoundDescriptor(BGSSoundDescriptorForm* a_descriptor, std::uint32_t a_usageFlags, const NiPoint3* a_position, NiAVObject* a_objectToFollow) + { + using func_t = decltype(&BGSAudio::PlaySoundDescriptor); + static REL::Relocation func{ ID::BGSAudio::PlaySoundDescriptor }; + return func(a_descriptor, a_usageFlags, a_position, a_objectToFollow); + } }; } diff --git a/include/RE/B/BGSComponent.h b/include/RE/B/BGSComponent.h index fddcad838..121d92497 100644 --- a/include/RE/B/BGSComponent.h +++ b/include/RE/B/BGSComponent.h @@ -21,7 +21,7 @@ namespace RE // members TESObjectMISC* scrapItem; // 98 - TESGlobal* modScrapScalar; // 90 + TESGlobal* modScrapScalar; // A0 }; static_assert(sizeof(BGSComponent) == 0xA8); } diff --git a/include/RE/B/BGSDefaultObjectManager.h b/include/RE/B/BGSDefaultObjectManager.h index 2bbbc7822..47bfec27a 100644 --- a/include/RE/B/BGSDefaultObjectManager.h +++ b/include/RE/B/BGSDefaultObjectManager.h @@ -17,9 +17,8 @@ namespace RE [[nodiscard]] static BGSDefaultObjectManager* GetSingleton() { - using func_t = decltype(&BGSDefaultObjectManager::GetSingleton); - static REL::Relocation func{ ID::BGSDefaultObjectManager::Singleton }; - return func(); + static REL::Relocation singleton{ ID::BGSDefaultObjectManager::Singleton }; + return singleton.get(); } [[nodiscard]] TESForm* GetDefaultObject(DEFAULT_OBJECT a_obj) const noexcept diff --git a/include/RE/B/BGSInventoryList.h b/include/RE/B/BGSInventoryList.h index a20de9d0e..0efe31bfd 100644 --- a/include/RE/B/BGSInventoryList.h +++ b/include/RE/B/BGSInventoryList.h @@ -45,6 +45,22 @@ namespace RE AddItem2(a_object, a_count, new ExtraDataList(), a_flags); } + // Adds a copy of a_stack, a stack of a_object, to the list. It joins a + // stack of the same object whose extra data matches, which is how + // identical items come to share one stack and a count, and goes in as a + // stack of its own otherwise. a_oldCount and a_newCount, when given, + // receive the count of the stack it went into before and after. + // + // AddItem1 and AddItem2 add through it, and so does filling a list from a + // container, for each entry whose base form has an object template. All + // three hold the list's write lock. + void AddStack(TESBoundObject* a_object, BGSInventoryItem::Stack* a_stack, std::uint32_t* a_oldCount = nullptr, std::uint32_t* a_newCount = nullptr) + { + using func_t = decltype(&BGSInventoryList::AddStack); + static REL::Relocation func{ ID::BGSInventoryList::AddStack }; + return func(this, a_object, a_stack, a_oldCount, a_newCount); + } + inline void BuildFromContainer(const TESContainer* a_container) { using func_t = decltype(&BGSInventoryList::BuildFromContainer); diff --git a/include/RE/B/BGSLocalizedString.h b/include/RE/B/BGSLocalizedString.h index 513cf4808..7c9714d08 100644 --- a/include/RE/B/BGSLocalizedString.h +++ b/include/RE/B/BGSLocalizedString.h @@ -40,6 +40,19 @@ namespace RE }; static_assert(sizeof(BGSLocalizedStringDL) == 0x4); + class BGSLocalizedStringIL + { + public: + // Sets a_text to string a_id from a_file's .ILSTRINGS file, in the + // game's current language. An a_id of 0 gives an empty string. + static void LookupByID(BSFixedStringCS& a_text, const TESFile* a_file, std::uint32_t a_id) + { + using func_t = decltype(&BGSLocalizedStringIL::LookupByID); + static REL::Relocation func{ ID::BGSLocalizedStringIL::LookupByID }; + return func(a_text, a_file, a_id); + } + }; + class BGSLocalizedString { public: diff --git a/include/RE/B/BipedAnim.h b/include/RE/B/BipedAnim.h index 969b701e5..297faefef 100644 --- a/include/RE/B/BipedAnim.h +++ b/include/RE/B/BipedAnim.h @@ -2,6 +2,7 @@ #include "RE/B/BIPED_OBJECT.h" #include "RE/B/BIPOBJECT.h" +#include "RE/B/BSFixedString.h" #include "RE/B/BSIntrusiveRefCounted.h" #include "RE/B/BSPointerHandle.h" @@ -33,6 +34,17 @@ namespace RE return root; } + // Sets a whole number variable on the animation graph of the model in one + // biped slot. False when the slot has no graph or the graph refuses it. + // UseAmmo tells a gun's own graph how many rounds are left this way, + // through iWeaponCharge on kWeaponGun. + bool SetObjectGraphVariableInt(BIPED_OBJECT a_bipedObject, const BSFixedString& a_variable, std::int32_t a_value) + { + using func_t = decltype(&BipedAnim::SetObjectGraphVariableInt); + static REL::Relocation func{ ID::BipedAnim::SetObjectGraphVariableInt }; + return func(this, a_bipedObject, a_variable, a_value); + } + // members NiNode* root; // 0008 BIPOBJECT object[std::to_underlying(BIPED_OBJECT::kTotal)]; // 0010 diff --git a/include/RE/C/Console.h b/include/RE/C/Console.h index 0c16b9d86..987aa31d3 100644 --- a/include/RE/C/Console.h +++ b/include/RE/C/Console.h @@ -21,6 +21,17 @@ namespace RE return func(a_command); } + // Runs the commands ExecuteCommand has queued. ExecuteCommand only parses + // what was typed and puts it in a queue, and the game's update drains + // the queue through this, the ForEachRef form included, so everything a + // console command does happens inside this call and on its thread. + static void RunQueuedCommands() + { + using func_t = decltype(&Console::RunQueuedCommands); + static REL::Relocation func{ ID::Console::RunQueuedCommands }; + return func(); + } + [[nodiscard]] static decltype(auto) GetCurrentPickIndex() { static REL::Relocation currentPickIndex{ ID::Console::GetCurrentPickIndex }; diff --git a/include/RE/E/Explosion.h b/include/RE/E/Explosion.h index 3a9cb55b5..0199d1b6d 100644 --- a/include/RE/E/Explosion.h +++ b/include/RE/E/Explosion.h @@ -102,6 +102,19 @@ namespace RE return func(this); } + // Deals the blast to each target FindTargets gathered that is not + // kProcessed yet, marks it so, and marks the explosion + // kTargetsProcessed. An actor is struck with a blow filled in from the + // explosion, unless the explosion is kNonHostile, and has the + // explosion's object effect cast on it. Anything else takes the blast's + // damage. + void ProcessTargets() + { + using func_t = decltype(&Explosion::ProcessTargets); + static REL::Relocation func{ ID::Explosion::ProcessTargets }; + return func(this); + } + // members hknpClosestUniqueBodyIdHitCollector collector; // 110 void* explosionDBHandle; // 520 - TODO diff --git a/include/RE/E/ExtraMagicCaster.h b/include/RE/E/ExtraMagicCaster.h new file mode 100644 index 000000000..c4b133f9c --- /dev/null +++ b/include/RE/E/ExtraMagicCaster.h @@ -0,0 +1,20 @@ +#pragma once + +#include "RE/B/BSExtraData.h" +#include "RE/M/MagicCaster.h" + +namespace RE +{ + class __declspec(novtable) ExtraMagicCaster : + public BSExtraData, // 00 + public MagicCaster // 18 + { + public: + static constexpr auto RTTI{ RTTI::ExtraMagicCaster }; + static constexpr auto VTABLE{ VTABLE::ExtraMagicCaster }; + static constexpr auto TYPE{ EXTRA_DATA_TYPE::kMagicCaster }; + + ~ExtraMagicCaster() override; // 00 + }; + static_assert(sizeof(ExtraMagicCaster) == 0x60); +} diff --git a/include/RE/Fallout.h b/include/RE/Fallout.h index aed37151b..6fc8c5390 100644 --- a/include/RE/Fallout.h +++ b/include/RE/Fallout.h @@ -35,6 +35,7 @@ #include "RE/A/ActorEquipManagerEvent.h" #include "RE/A/ActorItemEquipped.h" #include "RE/A/ActorKill.h" +#include "RE/A/ActorMagicCaster.h" #include "RE/A/ActorMotionFeedbackData.h" #include "RE/A/ActorMovementData.h" #include "RE/A/ActorPackage.h" @@ -701,6 +702,7 @@ #include "RE/E/ExtraLinkedRefChildren.h" #include "RE/E/ExtraLocation.h" #include "RE/E/ExtraLock.h" +#include "RE/E/ExtraMagicCaster.h" #include "RE/E/ExtraMapMarker.h" #include "RE/E/ExtraMaterialSwap.h" #include "RE/E/ExtraOutfitItem.h" @@ -753,6 +755,7 @@ #include "RE/G/GunStateData.h" #include "RE/H/HUDColorTypes.h" #include "RE/H/HUDCompassMarkerBase.h" +#include "RE/H/HUDComponentBase.h" #include "RE/H/HUDMarkerData.h" #include "RE/H/HUDMenu.h" #include "RE/H/HUDMenuUtils.h" @@ -764,6 +767,7 @@ #include "RE/H/HUDNotificationEvent.h" #include "RE/H/HUDPerkVaultBoyData.h" #include "RE/H/HUDPerkVaultBoySwfDisplayEvent.h" +#include "RE/H/HUDQuickContainerDataModel.h" #include "RE/H/HUDSubtitleDisplayData.h" #include "RE/H/HUDSubtitleDisplayEvent.h" #include "RE/H/Hazard.h" @@ -985,7 +989,9 @@ #include "RE/M/MENU_RENDER_CONTEXT.h" #include "RE/M/MESSAGEBOX_BUTTON.h" #include "RE/M/MOVABLE_STATIC_DATA.h" +#include "RE/M/MagicCaster.h" #include "RE/M/MagicItem.h" +#include "RE/M/MagicSystem.h" #include "RE/M/MagicTarget.h" #include "RE/M/Main.h" #include "RE/M/MainMenu.h" @@ -1089,6 +1095,7 @@ #include "RE/N/NiTransform.h" #include "RE/N/NiUpdateData.h" #include "RE/N/NiVisibleArray.h" +#include "RE/N/NonActorMagicCaster.h" #include "RE/N/NonMorphFaceManagement.h" #include "RE/N/nsHUDNotifications.h" #include "RE/N/nsHUDTypes.h" diff --git a/include/RE/G/GamePlayFormulas.h b/include/RE/G/GamePlayFormulas.h index ba8dfb710..a6d0a9188 100644 --- a/include/RE/G/GamePlayFormulas.h +++ b/include/RE/G/GamePlayFormulas.h @@ -48,6 +48,18 @@ namespace RE return func(a_itemHealth); } + // Turns an item's base value into the price the player sees. While a + // barter screen is open every price the game works out comes through + // here with the trader's markup put on, from the player's Charisma. + // a_health only counts when the fHealthDataValue6 game setting is above + // 1.0, and Fallout4.esm does not define it. + inline float CalculateItemValue(float a_baseValue, float a_health) + { + using func_t = decltype(&GamePlayFormulas::CalculateItemValue); + static REL::Relocation func{ ID::GamePlayFormulas::CalculateItemValue }; + return func(a_baseValue, a_health); + } + inline float CalcSprintingActionPoints(float a_equippedWeight, float a_elapsedTime, float a_endurance) { using func_t = decltype(&GamePlayFormulas::CalcSprintingActionPoints); diff --git a/include/RE/H/HUDComponentBase.h b/include/RE/H/HUDComponentBase.h new file mode 100644 index 000000000..4284850a1 --- /dev/null +++ b/include/RE/H/HUDComponentBase.h @@ -0,0 +1,31 @@ +#pragma once + +#include "RE/B/BSGFxShaderFXTarget.h" +#include "RE/H/HUDModes.h" + +namespace RE +{ + // The base class of every element of the gameplay HUD, such as the ammo + // counter. HUDMenu keeps them in hudObjects. + class __declspec(novtable) HUDComponentBase : + public BSGFxShaderFXTarget // 00 + { + public: + static constexpr auto RTTI{ RTTI::HUDComponentBase }; + static constexpr auto VTABLE{ VTABLE::HUDComponentBase }; + + ~HUDComponentBase() override; // 00 + + // members + std::uint64_t unkB0; // B0 + std::uint64_t unkB8; // B8 + bool unkC0; // C0 + HUDModes hudModes; // C8 + float unkE8; // E8 + float unkEC; // EC + bool unkF0; // F0 + bool unkF1; // F1 + bool unkF2; // F2 + }; + static_assert(sizeof(HUDComponentBase) == 0xF8); +} diff --git a/include/RE/H/HUDQuickContainerDataModel.h b/include/RE/H/HUDQuickContainerDataModel.h new file mode 100644 index 000000000..4c7d6488d --- /dev/null +++ b/include/RE/H/HUDQuickContainerDataModel.h @@ -0,0 +1,28 @@ +#pragma once + +namespace RE +{ + class QuickContainerStateData; + + // Builds what the quick container shows for the container under the + // crosshair, and sends the result to the HUD as a QuickContainerStateEvent. + class __declspec(novtable) HUDQuickContainerDataModel + { + public: + static constexpr auto RTTI{ RTTI::HUDQuickContainerDataModel }; + static constexpr auto VTABLE{ VTABLE::HUDQuickContainerDataModel }; + + virtual ~HUDQuickContainerDataModel(); // 00 + + // Fills a_state's itemData with one row per visible entry, each built by + // InventoryItemDisplayData's constructor. When more entries follow, a + // "..." row goes at the end. A locked container gets a single + // $LockedContainer row instead. + void AddItemRows(QuickContainerStateData& a_state) + { + using func_t = decltype(&HUDQuickContainerDataModel::AddItemRows); + static REL::Relocation func{ ID::HUDQuickContainerDataModel::AddItemRows }; + return func(this, a_state); + } + }; +} diff --git a/include/RE/H/HitData.h b/include/RE/H/HitData.h index eb3528190..075944095 100644 --- a/include/RE/H/HitData.h +++ b/include/RE/H/HitData.h @@ -45,6 +45,18 @@ namespace RE kExplosion = 1 << 20 }; + // Adds the damage types on a_weapon's instance data to the hit. Each + // value is multiplied by a_mult, then resisted against the target's + // matching resistance and passed through the perk entry points that + // change it. An ordinary hit passes the same range falloff here that it + // passes CombatFormulas::CalcWeaponDamage for the physical half. + void ApplyDamageTypes(const BGSObjectInstanceT& a_weapon, float a_mult) + { + using func_t = decltype(&HitData::ApplyDamageTypes); + static REL::Relocation func{ ID::HitData::ApplyDamageTypes }; + return func(this, a_weapon, a_mult); + } + // members DamageImpactData impactData; // 00 ActorHandle aggressor; // 40 diff --git a/include/RE/I/InventoryUserUIUtils.h b/include/RE/I/InventoryUserUIUtils.h index 96db6b99f..ed5f223f7 100644 --- a/include/RE/I/InventoryUserUIUtils.h +++ b/include/RE/I/InventoryUserUIUtils.h @@ -1,10 +1,14 @@ #pragma once #include "RE/B/BSFixedString.h" +#include "RE/B/BSTArray.h" +#include "RE/B/BSTTuple.h" #include "Scaleform/G/GFx_Value.h" namespace RE { + class BGSInventoryItem; + namespace InventoryUserUIUtils { namespace detail @@ -39,5 +43,19 @@ namespace RE Scaleform::GFx::Value entry; detail::AddItemCardInfoEntry(a_array, entry, a_name, a_value); } + + // Builds the item card for one stack of a_item into a_target. a_target + // is either the card's own array of entries, or a list entry that gets + // the array as its ItemCardInfoList member. The container, barter, + // workbench and cooking menus build their cards here, and the Pip-Boy + // has its own code. Weapon and armor rows are compared against the + // equipped items in a_compareItems. For armor, + // a_compareArmorWeightAndValue also compares weight and value. + inline void PopulateItemCardInfo_Helper(Scaleform::GFx::Value& a_target, const BGSInventoryItem& a_item, std::uint32_t a_stackID, const BSScrapArray>& a_compareItems, bool a_compareArmorWeightAndValue) + { + using func_t = decltype(&InventoryUserUIUtils::PopulateItemCardInfo_Helper); + static REL::Relocation func{ ID::InventoryUserUIUtils::PopulateItemCardInfo_Helper }; + return func(a_target, a_item, a_stackID, a_compareItems, a_compareArmorWeightAndValue); + } } } diff --git a/include/RE/IDs.h b/include/RE/IDs.h index 91bdb73bf..bb1b95657 100644 --- a/include/RE/IDs.h +++ b/include/RE/IDs.h @@ -23,6 +23,7 @@ namespace RE::ID inline constexpr REL::ID GetCurrentCollisionGroup{ 2229993 }; inline constexpr REL::ID GetCurrentFireLocation{ 2231167 }; inline constexpr REL::ID GetDesiredSpeed{ 2230410 }; + inline constexpr REL::ID GetEquippedItemHealth{ 2231099 }; inline constexpr REL::ID GetGhost{ 2229668 }; inline constexpr REL::ID GetHostileToActor{ 2229968 }; inline constexpr REL::ID GetMountHandle{ 2231230 }; @@ -42,6 +43,7 @@ namespace RE::ID inline constexpr REL::ID IsQuadruped{ 2229614 }; inline constexpr REL::ID IsSneaking{ 2207655 }; inline constexpr REL::ID Move{ 2229934 }; + inline constexpr REL::ID OnMagazineEmpty{ 2231137 }; inline constexpr REL::ID PerformAction{ 2231177 }; inline constexpr REL::ID PlaySoundByEditorName{ 2230172 }; inline constexpr REL::ID RemovePerk{ 2230122 }; @@ -168,6 +170,7 @@ namespace RE::ID { inline constexpr REL::ID GetSoundDescriptor{ 2214758 }; inline constexpr REL::ID GetUIOutputModel{ 2214800 }; + inline constexpr REL::ID PlaySoundDescriptor{ 2214755 }; } namespace BGSConstructibleObject @@ -196,7 +199,7 @@ namespace RE::ID namespace BGSDefaultObjectManager { - inline constexpr REL::ID Singleton{ 2192850 }; + inline constexpr REL::ID Singleton{ 4796209 }; } namespace BGSDynamicPersistenceManager @@ -247,6 +250,7 @@ namespace RE::ID { inline constexpr REL::ID AddItem1{ 2194159 }; inline constexpr REL::ID AddItem2{ 2194160 }; + inline constexpr REL::ID AddStack{ 2194191 }; inline constexpr REL::ID BuildFromContainer{ 2194158 }; inline constexpr REL::ID Clear{ 2194162 }; inline constexpr REL::ID ctor{ 2194153 }; @@ -283,6 +287,11 @@ namespace RE::ID inline constexpr REL::ID GetText{ 2194238 }; } + namespace BGSLocalizedStringIL + { + inline constexpr REL::ID LookupByID{ 2194243 }; + } + namespace BGSMessage { inline constexpr REL::ID AddButton{ 0 }; // inlined @@ -506,6 +515,11 @@ namespace RE::ID inline constexpr REL::ID SetMotion{ 2277723 }; } + namespace BipedAnim + { + inline constexpr REL::ID SetObjectGraphVariableInt{ 2194367 }; + } + namespace BIPOBJECT { inline constexpr REL::ID dtor{ 2194330 }; @@ -952,6 +966,7 @@ namespace RE::ID namespace Console { inline constexpr REL::ID ExecuteCommand{ 2248537 }; + inline constexpr REL::ID RunQueuedCommands{ 2248538 }; inline constexpr REL::ID GetCurrentPickIndex{ 2701382 }; inline constexpr REL::ID GetPickRef{ 2701395 }; inline constexpr REL::ID GetPickRefs{ 2701391 }; @@ -1040,6 +1055,7 @@ namespace RE::ID { inline constexpr REL::ID GetActorOwner{ 2236659 }; inline constexpr REL::ID GetDamage{ 2236658 }; + inline constexpr REL::ID ProcessTargets{ 2236664 }; } namespace ExteriorCellSingleton @@ -1154,6 +1170,11 @@ namespace RE::ID inline constexpr REL::ID Get{ 2194566 }; } + namespace HitData + { + inline constexpr REL::ID ApplyDamageTypes{ 2236861 }; + } + namespace hkContainerHeapAllocator { inline constexpr REL::ID Singleton{ 2665426 }; @@ -1316,6 +1337,11 @@ namespace RE::ID inline constexpr REL::ID GetEventSource{ 4801988 }; } + namespace HUDQuickContainerDataModel + { + inline constexpr REL::ID AddItemRows{ 2221647 }; + } + namespace IAnimationGraphManagerHolder { inline constexpr REL::ID SetGraphVariableBool{ 2214543 }; @@ -1453,6 +1479,7 @@ namespace RE::ID { inline constexpr REL::ID StartTestingLoadMenu{ 2249224 }; inline constexpr REL::ID PopulateLoadScreens{ 2249232 }; + inline constexpr REL::ID CollectLoadScreens{ 2249240 }; } namespace LockpickingMenu @@ -1467,6 +1494,11 @@ namespace RE::ID inline constexpr REL::ID GetEventSource{ 2249292 }; } + namespace MagicCaster + { + inline constexpr REL::ID Cast{ 2226296 }; + } + namespace MagicTarget { inline constexpr REL::ID IsTakingHealthDamageFromActiveEffect{ 2226397 }; @@ -1546,6 +1578,7 @@ namespace RE::ID { inline constexpr REL::ID Singleton{ 4796373 }; inline constexpr REL::ID Create{ 2249456 }; + inline constexpr REL::ID QueueMessage{ 2249457 }; } namespace Moon diff --git a/include/RE/L/LoadingMenu.h b/include/RE/L/LoadingMenu.h index a6216763c..f4548aac8 100644 --- a/include/RE/L/LoadingMenu.h +++ b/include/RE/L/LoadingMenu.h @@ -9,8 +9,11 @@ namespace RE { + enum class ENUM_FORM_ID; class BGSModelMaterialSwap; class NiAVObject; + class TESDataHandler; + class TESLoadScreen; class __declspec(novtable) LoadingMenu : public GameMenuBase // 00 @@ -34,6 +37,38 @@ namespace RE virtual void OnThumbstickEvent(const ThumbstickEvent*) override; // 04 virtual void OnButtonEvent(const ButtonEvent*) override; // 08 + // The struct PopulateLoadScreens passes to CollectLoadScreens to fill. + // numNonDefaultScreens and validScreens point at members of the menu, + // and artCandidates at a list on the caller's stack. + class LoadScreenCandidates + { + public: + // members + LoadingMenu* menu; // 00 + std::uint32_t* numNonDefaultScreens; // 08 + BSScrapArray* artCandidates; // 10 + BSTArray* validScreens; // 18 + std::uint32_t numNonDefaultArtCandidates; // 20 + }; + static_assert(sizeof(LoadScreenCandidates) == 0x28); + + // Adds every form of a_formType that may show on this load to + // a_candidates. PopulateLoadScreens passes kLSCR. A screen qualifies + // when its conditions are true for the player. On the way out to the + // main menu, it qualifies when it is flagged to show there instead. + // + // Each screen that qualifies goes on validScreens, and also on + // artCandidates when it has a model. Screens with conditions are also + // counted in numNonDefaultScreens, and in numNonDefaultArtCandidates + // when they have a model. The game sorts those ahead of the rest and + // picks from them first. + static void CollectLoadScreens(TESDataHandler* a_dataHandler, ENUM_FORM_ID a_formType, LoadScreenCandidates* a_candidates) + { + using func_t = decltype(&LoadingMenu::CollectLoadScreens); + static REL::Relocation func{ ID::LoadingMenu::CollectLoadScreens }; + return func(a_dataHandler, a_formType, a_candidates); + } + static void StartTestingLoadMenu() { using func_t = decltype(&LoadingMenu::StartTestingLoadMenu); diff --git a/include/RE/M/MagicCaster.h b/include/RE/M/MagicCaster.h new file mode 100644 index 000000000..758f997d5 --- /dev/null +++ b/include/RE/M/MagicCaster.h @@ -0,0 +1,91 @@ +#pragma once + +#include "RE/B/BSPointerHandle.h" +#include "RE/B/BSSoundHandle.h" +#include "RE/B/BSTArray.h" + +namespace RE +{ + class ActiveEffect; + class Actor; + class MagicItem; + class NiNode; + class TESBoundObject; + class TESObjectREFR; + + namespace MagicSystem + { + enum class CannotCastReason; + enum class CastingSource : std::int32_t; + } + + class __declspec(novtable) MagicCaster + { + public: + static constexpr auto RTTI{ RTTI::MagicCaster }; + static constexpr auto VTABLE{ VTABLE::MagicCaster }; + + virtual ~MagicCaster(); // 00 + + // add + virtual void Unk01(); // 01 + virtual void CastSpellImmediate(MagicItem* a_spell, bool a_noHitEffectArt, TESObjectREFR* a_target, float a_effectiveness, bool a_hostileEffectivenessOnly, float a_magnitudeOverride, Actor* a_blameActor); // 02 + virtual void Unk03(); // 03 + virtual void Unk04(); // 04 + virtual void Unk05(); // 05 + virtual void Unk06(); // 06 + virtual void Unk07(); // 07 + virtual void Unk08(); // 08 + virtual void Unk09(); // 09 + virtual void Unk0A(); // 0A + virtual bool CheckCast(MagicItem* a_spell, bool a_dualCast, float* a_effectiveness, MagicSystem::CannotCastReason* a_reason, bool a_arg5); // 0B + virtual TESObjectREFR* GetCasterStatsObject() const; // 0C + virtual Actor* GetCasterAsActor() const; // 0D + virtual TESObjectREFR* GetCasterObjectReference(Actor** a_outCaster) const; // 0E + virtual NiNode* GetMagicNode(); // 0F + virtual void ClearMagicNode(); // 10 + virtual void Unk11(); // 11 + virtual void Unk12(); // 12 + virtual void Unk13(); // 13 + virtual void Unk14(); // 14 + virtual void Unk15(); // 15 + virtual MagicSystem::CastingSource GetCastingSource() const; // 16 + virtual bool GetIsDualCasting() const; // 17 + virtual void SetDualCasting(bool a_dualCasting); // 18 + virtual void Unk19(); // 19 + virtual void Unk1A(); // 1A + virtual void Unk1B(); // 1B + virtual void Unk1C(); // 1C + virtual void AdjustActiveEffect(ActiveEffect* a_effect, float a_power, bool a_hostileEffectivenessOnly); // 1D + + // Casts currentSpell on the targets it finds. a_effectiveness is the + // power the spell lands at, a multiplier on each effect's magnitude, or + // on its duration for an effect that has no magnitude, and + // a_hostileEffectivenessOnly narrows it to the hostile effects. Some + // kinds of spell ignore it, abilities, addictions and constant effect + // enchantments among them. a_targetCount receives how many targets were + // reached, and a_source names what the spell came from, the weapon for + // the object effects of a melee blow. Returns whether anything was cast. + // + // A weapon's object effects, a blast's and anything CastSpellImmediate + // casts all end here. + bool Cast(float a_effectiveness, std::uint32_t* a_targetCount, TESBoundObject* a_source, bool a_noHitEffectArt, bool a_hostileEffectivenessOnly) + { + using func_t = decltype(&MagicCaster::Cast); + static REL::Relocation func{ ID::MagicCaster::Cast }; + return func(this, a_effectiveness, a_targetCount, a_source, a_noHitEffectArt, a_hostileEffectivenessOnly); + } + + // members + BSTArray sounds; // 08 + ObjectRefHandle desiredTarget; // 20 + MagicItem* currentSpell; // 28 + std::uint32_t state; // 30 + float unk34; // 34 + float unk38; // 38 + float magnitudeOverride; // 3C + float unk40; // 40 + float unk44; // 44 + }; + static_assert(sizeof(MagicCaster) == 0x48); +} diff --git a/include/RE/M/MagicSystem.h b/include/RE/M/MagicSystem.h new file mode 100644 index 000000000..6b32db831 --- /dev/null +++ b/include/RE/M/MagicSystem.h @@ -0,0 +1,41 @@ +#pragma once + +namespace RE +{ + namespace MagicSystem + { + enum class CastingType : std::int32_t + { + kConstantEffect = 0, + kFireAndForget = 1, + kConcentration = 2 + }; + + enum class Delivery : std::int32_t + { + kSelf = 0, + kTouch = 1, + kAimed = 2, + kTargetActor = 3, + kTargetLocation = 4 + }; + + enum class SpellType : std::int32_t + { + kSpell = 0, + kDisease = 1, + kPower = 2, + kLesserPower = 3, + kAbility = 4, + kPoison = 5, + kEnchantment = 6, + kPotion = 7, + kIngredient = 8, + kLeveledSpell = 9, + kAddiction = 10, + kVoicePower = 11, + kStaffEnchantment = 12, + kScroll = 13 + }; + } +} diff --git a/include/RE/M/MessageBoxData.h b/include/RE/M/MessageBoxData.h index be2693fa5..f1a06946a 100644 --- a/include/RE/M/MessageBoxData.h +++ b/include/RE/M/MessageBoxData.h @@ -19,14 +19,16 @@ namespace RE static constexpr auto VTABLE{ VTABLE::MessageBoxData }; // members - BSString headerText; // 18 - BSString bodyText; // 28 - BSTArray buttonText; // 38 - REX::TEnumSet warningContext; // 50 - BSTSmartPointer callback; // 58 - REX::TEnumSet menuDepth; // 60 - bool modal; // 64 - bool ensureUnique; // 65 + BSString headerText; // 18 + BSString bodyText; // 28 + BSTArray buttonText; // 38 + REX::TEnumSet warningContext; // 50 + BSTSmartPointer callback; // 58 + REX::TEnumSet menuDepth; // 60 + bool modal; // 64 + bool ensureUnique; // 65 + std::int32_t cancelButtonIndex; // 68 + bool hasCancelButton; // 6C }; - static_assert(sizeof(MessageBoxData) == 0x68); + static_assert(sizeof(MessageBoxData) == 0x70); } diff --git a/include/RE/M/MessageBoxMenu.h b/include/RE/M/MessageBoxMenu.h index c28b93b40..bc7074933 100644 --- a/include/RE/M/MessageBoxMenu.h +++ b/include/RE/M/MessageBoxMenu.h @@ -30,7 +30,9 @@ namespace RE } // members - MessageBoxData* currentMessage; // E8 + bool hasCancelButton; // E8 + MessageBoxData* currentMessage; // F0 + std::int32_t cancelButtonIndex; // F8 }; - static_assert(sizeof(MessageBoxMenu) == 0xF0); + static_assert(sizeof(MessageBoxMenu) == 0x100); } diff --git a/include/RE/M/MessageMenuManager.h b/include/RE/M/MessageMenuManager.h index 6b000d434..5932273e0 100644 --- a/include/RE/M/MessageMenuManager.h +++ b/include/RE/M/MessageMenuManager.h @@ -26,11 +26,37 @@ namespace RE const char* a_button2Text = nullptr, const char* a_button3Text = nullptr, const char* a_button4Text = nullptr, - bool a_ensureUnique = false) + bool a_ensureUnique = false, + std::int32_t a_cancelButtonIndex = -1) { using func_t = decltype(&MessageMenuManager::Create); static REL::Relocation func{ ID::MessageMenuManager::Create }; - return func(this, a_headerText, a_bodyText, a_callback, a_warningContext, a_button1Text, a_button2Text, a_button3Text, a_button4Text, a_ensureUnique); + return func(this, a_headerText, a_bodyText, a_callback, a_warningContext, a_button1Text, a_button2Text, a_button3Text, a_button4Text, a_ensureUnique, a_cancelButtonIndex); + } + + // Queues a message box with one button for each label in a_buttonText, + // with no limit on the count. The callback receives the index of the + // pressed button. a_modal sets the box's kModal menu flag. + // a_ensureUnique skips the box when the box shown just before it had + // the same body text. a_cancelButtonIndex is the button the Cancel key + // presses, or -1 for none. + // + // Create calls this with at most four labels and a_modal set. With its + // own a_ensureUnique set, Create also skips the box when one with the + // same body text is already waiting in the queue. + void QueueMessage( + const char* a_headerText, + const char* a_bodyText, + IMessageBoxCallback* a_callback, + WARNING_TYPES a_warningContext, + const BSScrapArray* a_buttonText, + bool a_modal = true, + bool a_ensureUnique = false, + std::int32_t a_cancelButtonIndex = -1) + { + using func_t = decltype(&MessageMenuManager::QueueMessage); + static REL::Relocation func{ ID::MessageMenuManager::QueueMessage }; + return func(this, a_headerText, a_bodyText, a_callback, a_warningContext, a_buttonText, a_modal, a_ensureUnique, a_cancelButtonIndex); } // members diff --git a/include/RE/N/NonActorMagicCaster.h b/include/RE/N/NonActorMagicCaster.h new file mode 100644 index 000000000..415a0fb01 --- /dev/null +++ b/include/RE/N/NonActorMagicCaster.h @@ -0,0 +1,29 @@ +#pragma once + +#include "RE/B/BSPointerHandle.h" +#include "RE/E/ExtraMagicCaster.h" + +namespace RE +{ + class __declspec(novtable) NonActorMagicCaster : + public ExtraMagicCaster // 00 + { + public: + static constexpr auto RTTI{ RTTI::NonActorMagicCaster }; + static constexpr auto VTABLE{ VTABLE::NonActorMagicCaster }; + + ~NonActorMagicCaster() override; // 00 + + // override (MagicCaster) + void CastSpellImmediate(MagicItem* a_spell, bool a_noHitEffectArt, TESObjectREFR* a_target, float a_effectiveness, bool a_hostileEffectivenessOnly, float a_magnitudeOverride, Actor* a_blameActor) override; // 02 + TESObjectREFR* GetCasterStatsObject() const override; // 0C + Actor* GetCasterAsActor() const override; // 0D + TESObjectREFR* GetCasterObjectReference(Actor** a_outCaster) const override; // 0E + NiNode* GetMagicNode() override; // 0F + + // members + TESObjectREFR* ref; // 60 + ActorHandle blameActor; // 68 + }; + static_assert(sizeof(NonActorMagicCaster) == 0x70); +} diff --git a/include/RE/T/TESDataHandler.h b/include/RE/T/TESDataHandler.h index ee54e9c60..5dca50b44 100644 --- a/include/RE/T/TESDataHandler.h +++ b/include/RE/T/TESDataHandler.h @@ -25,6 +25,12 @@ namespace RE public BSTSingletonSDM // 0058 { public: + // The first ID the game gives to forms it makes during play. Every data + // load resets nextID to it, and the counter returns to it when it runs + // out. IDs below it are never recycled, so the game never gives + // FF000001 to FF0007FF to a form it makes. + static constexpr TESFormID FIRST_DYNAMIC_FORM_ID{ 0xFF000800 }; + [[nodiscard]] static TESDataHandler* GetSingleton() { static REL::Relocation singleton{ ID::TESDataHandler::Singleton }; diff --git a/include/RE/T/TESLeveledList.h b/include/RE/T/TESLeveledList.h index 6f6d7a9e7..3a682b758 100644 --- a/include/RE/T/TESLeveledList.h +++ b/include/RE/T/TESLeveledList.h @@ -63,7 +63,7 @@ namespace RE return func(this, a_ref, a_outCont, a_legendary); } - bool GetUseAll() + [[nodiscard]] bool GetUseAll() const { using func_t = decltype(&TESLeveledList::GetUseAll); static REL::Relocation func{ ID::TESLeveledList::GetUseAll }; @@ -75,11 +75,11 @@ namespace RE BSTArray>* keywordChances; // 10 LEVELED_OBJECT* leveledLists; // 18 LEVELED_OBJECT** scriptAddedLists; // 20 - std::int8_t scriptListCount; // 28 - std::int8_t baseListCount; // 29 + std::uint8_t scriptListCount; // 28 + std::uint8_t baseListCount; // 29 std::int8_t chanceNone; // 2A std::int8_t llFlags; // 2B - std::int8_t maxUseAllCount; // 2C + std::uint8_t maxUseAllCount; // 2C }; static_assert(sizeof(TESLeveledList) == 0x30); } diff --git a/include/RE/W/WorkbenchMenuBase.h b/include/RE/W/WorkbenchMenuBase.h index 09b19d50c..f352dc425 100644 --- a/include/RE/W/WorkbenchMenuBase.h +++ b/include/RE/W/WorkbenchMenuBase.h @@ -23,6 +23,22 @@ namespace RE static constexpr auto RTTI{ RTTI::WorkbenchMenuBase }; static constexpr auto VTABLE{ VTABLE::WorkbenchMenuBase }; + // The numbers the menu gives to the functions its Flash movie can call. + // When the movie calls one, Call receives its number in params.userData. + // ExamineMenu adds its own from 9 on. + enum class CodeObjectFunction : std::uint32_t + { + kRegisterComponents = 0, + kPlaySound = 1, + kStartAnimation = 2, + kConfirmBuild = 3, + kStartBuildConfirm = 4, + kCancelConfirm = 5, + kHideMenu = 6, + kSendTutorialEvent = 7, + kOnAlternateButton = 8 + }; + enum class HighlightMode { kModMenu, diff --git a/src/F4SE/RegistrationMap.cpp b/src/F4SE/RegistrationMap.cpp index d10f2bb91..c0c322236 100644 --- a/src/F4SE/RegistrationMap.cpp +++ b/src/F4SE/RegistrationMap.cpp @@ -322,7 +322,7 @@ namespace F4SE auto vm = game->GetVM(); if (!vm) { REX::ERROR("Failed to get VM!"sv); - return; + return false; } const auto invalidHandle = vm->GetObjectHandlePolicy().EmptyHandle(); @@ -351,7 +351,7 @@ namespace F4SE auto vm = game->GetVM(); if (!vm) { REX::ERROR("Failed to get VM!"sv); - return; + return false; } const auto invalidHandle = vm->GetObjectHandlePolicy().EmptyHandle(); diff --git a/src/F4SE/RegistrationMapUnique.cpp b/src/F4SE/RegistrationMapUnique.cpp index 2add9fbb3..1f3377ad1 100644 --- a/src/F4SE/RegistrationMapUnique.cpp +++ b/src/F4SE/RegistrationMapUnique.cpp @@ -403,7 +403,7 @@ namespace F4SE auto vm = game->GetVM(); if (!vm) { REX::ERROR("Failed to get VM!"sv); - return; + return false; } const auto invalidHandle = vm->GetObjectHandlePolicy().EmptyHandle(); @@ -432,7 +432,7 @@ namespace F4SE auto vm = game->GetVM(); if (!vm) { REX::ERROR("Failed to get VM!"sv); - return; + return false; } const auto invalidHandle = vm->GetObjectHandlePolicy().EmptyHandle();