#pragma once #include "RE/B/BGSAttachParentArray.h" #include "RE/B/BGSCharacterMorph.h" #include "RE/B/BGSCharacterTint.h" #include "RE/B/BGSForcedLocRefType.h" #include "RE/B/BGSLocalizedString.h" #include "RE/B/BGSNativeTerminalForm.h" #include "RE/B/BGSOverridePackCollection.h" #include "RE/B/BSTArray.h" #include "RE/B/BSTEvent.h" #include "RE/B/BSTHashMap.h" #include "RE/N/NPC_DATA.h" #include "RE/N/NiPoint3.h" #include "RE/P/PerkRankData.h" #include "RE/S/SEX.h" #include "RE/T/TESActorBase.h" #include "RE/T/TESRaceForm.h" #include "RE/T/TESSpellList.h" namespace RE { class BGSRelationship; class CreatureSounds; class MenuOpenCloseEvent; class __declspec(novtable) TESNPC : public TESActorBase, // 000 public TESRaceForm, // 1B0 public BGSOverridePackCollection, // 1C0 public BGSForcedLocRefType, // 1F8 public BGSNativeTerminalForm, // 208 public BSTEventSink // 218 { public: static constexpr auto RTTI{ RTTI::TESActorBase }; static constexpr auto VTABLE{ VTABLE::TESActorBase }; static constexpr auto FORM_ID{ ENUM_FORM_ID::kNPC_ }; class HeadRelatedData { public: // members BGSColorForm* hairColor; // 00 BGSColorForm* facialHairColor; // 08 BGSTextureSet* faceDetails; // 10 }; static_assert(sizeof(HeadRelatedData) == 0x18); void CopyPerkRankArray(const std::vector& a_copiedData); bool AddPerk(BGSPerk* a_perk, std::int8_t a_rank) { if (!GetPerkIndex(a_perk)) { std::vector storage{ &perks[0], &perks[perkCount] }; auto perk = new PerkRankData(a_perk, a_rank); storage.push_back(*perk); AllocatePerkRankArray(static_cast(storage.size())); std::ranges::copy(storage, perks); return true; } return false; } bool AddPerks(const std::vector& a_perks, std::int8_t a_rank); [[nodiscard]] bool ContainsKeyword(std::string_view a_editorID) const; [[nodiscard]] static BSTHashMap>& GetAlternateHeadPartListMap() { static REL::Relocation>*> map{ ID::TESNPC::AlternateHeadPartListMap, -0x8 }; return *map; } [[nodiscard]] std::span GetHeadParts(bool a_alternate = true) const { if (a_alternate && UsingAlternateHeadPartList()) { auto& map = GetAlternateHeadPartListMap(); const auto it = map.find(this); if (it != map.end()) { return { it->second.data(), it->second.size() }; } else { return {}; } } else { return { headParts, static_cast(numHeadParts) }; } } [[nodiscard]] std::optional GetPerkIndex(BGSPerk* a_perk) const { if (perks) { for (std::uint32_t i = 0; i < perkCount; i++) { if (perks[i].perk == a_perk) { return i; } } } return std::nullopt; } [[nodiscard]] TESNPC* GetRootFaceNPC() noexcept { return const_cast(static_cast(this)->GetRootFaceNPC()); } [[nodiscard]] const TESNPC* GetRootFaceNPC() const noexcept { auto root = this; while (root->faceNPC) { root = root->faceNPC; } return root; } [[nodiscard]] SEX GetSex() noexcept { using func_t = decltype(&TESNPC::GetSex); static REL::Relocation func{ ID::TESNPC::GetSex }; return func(this); } [[nodiscard]] TESSpellList::SpellData* GetSpellList() { if (!spellData) { spellData = new TESSpellList::SpellData(); } return spellData; } [[nodiscard]] bool HasApplicableKeywordString(std::string_view a_editorID) const; bool RemovePerks(const std::vector& a_perks); [[nodiscard]] bool IsInFaction(const TESFaction* a_faction) { for (const auto& faction : factions) { if (faction.faction == a_faction && faction.rank > -1) { return true; } } return false; } [[nodiscard]] bool UsingAlternateHeadPartList() const; float GetFacialBoneMorphIntensity() { using func_t = decltype(&TESNPC::GetFacialBoneMorphIntensity); static REL::Relocation func{ ID::TESNPC::GetFacialBoneMorphIntensity }; return func(this); } void SetHairColor(BGSColorForm* a_col) { using func_t = decltype(&TESNPC::SetHairColor); static REL::Relocation func{ ID::TESNPC::SetHairColor }; return func(this, a_col); } [[nodiscard]] const char* GetShortName() noexcept { using func_t = decltype(&TESNPC::GetShortName); static REL::Relocation func{ ID::TESNPC::GetShortName }; return func(this); } // members BGSAttachParentArray attachParents; // 220 NPC_DATA data; // 238 TESClass* cl; // 240 HeadRelatedData* headRelatedData; // 248 BGSListForm* giftFilter; // 250 TESCombatStyle* combatStyle; // 258 std::uint32_t fileOffset; // 260 TESRace* originalRace; // 268 TESNPC* faceNPC; // 270 NiPoint3 morphWeight; // 278 float height; // 284 float heightMax; // 288 union { TESNPC* soundCreature; CreatureSounds* creatureSounds; }; // 290 BGSLocalizedString shortName; // 298 TESObjectARMO* farSkin; // 2A0 TESFurniture* powerArmorFurn; // 2A8 BGSOutfit* defOutfit; // 2B0 BGSOutfit* sleepOutfit; // 2B8 BGSListForm* defaultPackList; // 2C0 TESFaction* crimeFaction; // 2C8 BGSHeadPart** headParts; // 2D0 BSTArray* morphRegionSliderValues; // 2D8 BSTHashMap* facialBoneRegionSliderValues; // 2E0 std::int8_t numHeadParts; // 2E8 std::int8_t soundLevel; // 2E9 std::int8_t bodyTintColorR; // 2EA std::int8_t bodyTintColorG; // 2EN std::int8_t bodyTintColorB; // 2EC std::int8_t bodyTintColorA; // 2ED BSTArray* relationships; // 2F0 BSTHashMap* morphSliderValues; // 2F8 BGSCharacterTint::Entries* tintingData; // 300 }; static_assert(sizeof(TESNPC) == 0x308); }