From 61488e650bea912c2e6721bd87a15ada6ac50b60 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 29 Mar 2024 18:49:30 +0100 Subject: [PATCH] Better humanoid npc detection --- Code/client/Games/Skyrim/Actor.cpp | 41 ++++++++++++++++++- Code/client/Games/Skyrim/Actor.h | 2 +- Code/client/Games/Skyrim/Forms/BGSOutfit.h | 1 + Code/client/Games/Skyrim/Forms/TESForm.h | 1 + Code/client/Games/Skyrim/Forms/TESLevItem.h | 6 +++ .../client/Games/Skyrim/Forms/TESObjectARMO.h | 2 + Code/client/Services/Debug/DebugService.cpp | 10 ++++- .../Services/Generic/InventoryService.cpp | 5 ++- 8 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 Code/client/Games/Skyrim/Forms/TESLevItem.h diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 1a04ae5f8..00374d0d2 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -44,6 +44,8 @@ #include #include #include +#include +#include #ifdef SAVE_STUFF @@ -196,9 +198,44 @@ bool Actor::IsWearingBodyPiece() const noexcept return GetContainerChanges()->GetArmor(32) != nullptr; } -bool Actor::IsHumanoidNPC() const noexcept +bool Actor::ShouldWearBodyPiece() const noexcept { - return Cast(baseForm)->outfits[0] != nullptr; + TESNPC* pBase = Cast(baseForm); + if (!pBase) + return false; + + BGSOutfit* pDefaultOutfit = pBase->outfits[0]; + if (!pDefaultOutfit) + return false; + + for (auto* pItem : pDefaultOutfit->outfitItems) + { + TESObjectARMO* pArmor = nullptr; + + if (pItem->formType == FormType::Armor) + pArmor = Cast(pItem); + else if (pItem->formType == FormType::LeveledItem) + { + // TODO: leveled items can probably also be handled but afaik, + // the naked npc bug mostly occurs to town NPCs. + // Since this is a bit more complex and possibly error prone, leave out for now. +#if 0 + TESLevItem* pLevItem = Cast(pItem); + if (!pLevItem) + continue; +#endif + } + else + continue; + + if (!pArmor) + continue; + + if (pArmor->slotType & 0x4) // 0x4 is flag for body piece + return true; + } + + return false; } // Get owner of a summon or raised corpse diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index c0d77c00b..24cfd4438 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -206,7 +206,7 @@ struct Actor : TESObjectREFR [[nodiscard]] bool HasPerk(uint32_t aPerkFormId) const noexcept; [[nodiscard]] uint8_t GetPerkRank(uint32_t aPerkFormId) const noexcept; [[nodiscard]] bool IsWearingBodyPiece() const noexcept; - [[nodiscard]] bool IsHumanoidNPC() const noexcept; + [[nodiscard]] bool ShouldWearBodyPiece() const noexcept; // Setters void SetSpeed(float aSpeed) noexcept; diff --git a/Code/client/Games/Skyrim/Forms/BGSOutfit.h b/Code/client/Games/Skyrim/Forms/BGSOutfit.h index d4df8a487..cfbfc111d 100644 --- a/Code/client/Games/Skyrim/Forms/BGSOutfit.h +++ b/Code/client/Games/Skyrim/Forms/BGSOutfit.h @@ -4,4 +4,5 @@ struct BGSOutfit : TESForm { + GameArray outfitItems; }; diff --git a/Code/client/Games/Skyrim/Forms/TESForm.h b/Code/client/Games/Skyrim/Forms/TESForm.h index 74698014f..9772c7e12 100644 --- a/Code/client/Games/Skyrim/Forms/TESForm.h +++ b/Code/client/Games/Skyrim/Forms/TESForm.h @@ -14,6 +14,7 @@ enum class FormType : uint8_t Npc = 43, LeveledCharacter = 44, Alchemy = 46, + LeveledItem = 53, Character = 62, QuestItem = 77, Count = 0x87 diff --git a/Code/client/Games/Skyrim/Forms/TESLevItem.h b/Code/client/Games/Skyrim/Forms/TESLevItem.h new file mode 100644 index 000000000..30aefe0fe --- /dev/null +++ b/Code/client/Games/Skyrim/Forms/TESLevItem.h @@ -0,0 +1,6 @@ +#pragma once + +struct TESLevItem : BaseFormComponent +{ + +}; diff --git a/Code/client/Games/Skyrim/Forms/TESObjectARMO.h b/Code/client/Games/Skyrim/Forms/TESObjectARMO.h index fb2cf1af0..f03f30041 100644 --- a/Code/client/Games/Skyrim/Forms/TESObjectARMO.h +++ b/Code/client/Games/Skyrim/Forms/TESObjectARMO.h @@ -4,4 +4,6 @@ struct TESObjectARMO : TESForm { + uint8_t unk20[0x1B8 - sizeof(TESForm)]; + uint32_t slotType; }; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 85639f819..7e909b59b 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -213,7 +213,15 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - PlaceActorInWorld(); + //PlaceActorInWorld(); + Actor* pActor = Cast(TESForm::GetById(m_formId)); + if (pActor) + { + if (pActor->ShouldWearBodyPiece()) + spdlog::warn("Actor has body piece"); + else + spdlog::error("Actor does not have body piece"); + } } } else diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 9942f4d37..3742cfdd7 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #if TP_FALLOUT4 #include @@ -324,10 +325,10 @@ void InventoryService::RunNakedNPCBugChecks() noexcept if (pActor->IsDead()) continue; - if (!pActor->IsHumanoidNPC()) + if (pActor->IsWearingBodyPiece()) continue; - if (pActor->IsWearingBodyPiece()) + if (!pActor->ShouldWearBodyPiece()) continue; // Don't broadcast changes if a remote actor needs fixing