From b4aa6ef089d45d8a87b51fe5a1d52941c32c1b38 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 23 Dec 2021 20:30:24 +0100 Subject: [PATCH 01/48] feat: GetContainer() proof of concept --- Code/client/Events/ReferenceAddedEvent.h | 6 +- Code/client/Events/ReferenceSpawnedEvent.h | 4 +- Code/client/Games/FormFactory.cpp | 8 +- Code/client/Games/IFormFactory.h | 5 +- Code/client/Games/Skyrim/Actor.cpp | 165 +++++++++++++++++- Code/client/Games/Skyrim/Actor.h | 4 +- Code/client/Games/Skyrim/Forms/TESForm.h | 2 +- Code/client/Games/Skyrim/Forms/TESNPC.h | 2 +- Code/client/Games/Skyrim/PlayerCharacter.h | 2 +- .../client/Services/Generic/EntityService.cpp | 2 +- Code/client/Services/Generic/TestService.cpp | 2 + Code/encoding/Structs/Container.h | 69 ++++++++ 12 files changed, 254 insertions(+), 17 deletions(-) create mode 100644 Code/encoding/Structs/Container.h diff --git a/Code/client/Events/ReferenceAddedEvent.h b/Code/client/Events/ReferenceAddedEvent.h index b56063ecc..b244f512e 100644 --- a/Code/client/Events/ReferenceAddedEvent.h +++ b/Code/client/Events/ReferenceAddedEvent.h @@ -1,12 +1,14 @@ #pragma once +#include + struct ReferenceAddedEvent { - explicit ReferenceAddedEvent(const uint32_t aFormId, const uint8_t aFormType) + explicit ReferenceAddedEvent(const uint32_t aFormId, const FormType aFormType) : FormId(aFormId) , FormType(aFormType) {} uint32_t FormId; - uint8_t FormType; + FormType FormType; }; diff --git a/Code/client/Events/ReferenceSpawnedEvent.h b/Code/client/Events/ReferenceSpawnedEvent.h index 2d7e615c8..816a219f7 100644 --- a/Code/client/Events/ReferenceSpawnedEvent.h +++ b/Code/client/Events/ReferenceSpawnedEvent.h @@ -3,13 +3,13 @@ // Not to be confused with added event, this event represents a form that the mod created struct ReferenceSpawnedEvent { - explicit ReferenceSpawnedEvent(const uint32_t aFormId, const uint8_t aFormType, const entt::entity aEntity) + explicit ReferenceSpawnedEvent(const uint32_t aFormId, const FormType aFormType, const entt::entity aEntity) : FormId(aFormId) , FormType(aFormType) , Entity(aEntity) {} uint32_t FormId; - uint8_t FormType; + FormType FormType; entt::entity Entity; }; diff --git a/Code/client/Games/FormFactory.cpp b/Code/client/Games/FormFactory.cpp index 101d6904c..d548771d7 100644 --- a/Code/client/Games/FormFactory.cpp +++ b/Code/client/Games/FormFactory.cpp @@ -2,16 +2,14 @@ #include -#include - -IFormFactory* IFormFactory::GetForType(const uint32_t aId) noexcept +IFormFactory* IFormFactory::GetForType(const FormType aId) noexcept { - if (aId >= Count) + if (aId >= FormType::Count) return nullptr; POINTER_FALLOUT4(IFormFactory*, s_factories, 0x1458D3BF0 - 0x140000000); POINTER_SKYRIMSE(IFormFactory*, s_factories, 0x141F5E4A0 - 0x140000000); - return s_factories.Get()[aId]; + return s_factories.Get()[(uint32_t)aId]; } diff --git a/Code/client/Games/IFormFactory.h b/Code/client/Games/IFormFactory.h index 8b2729846..644612575 100644 --- a/Code/client/Games/IFormFactory.h +++ b/Code/client/Games/IFormFactory.h @@ -1,6 +1,7 @@ #pragma once -struct TESForm; +#include + struct IFormFactory { virtual ~IFormFactory(); @@ -11,7 +12,7 @@ struct IFormFactory virtual void sub_5(); virtual void sub_6(); - static IFormFactory* GetForType(uint32_t aId) noexcept; + static IFormFactory* GetForType(const FormType aId) noexcept; template static T* Create() diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 1eb8d5129..93501443a 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -25,6 +25,16 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #ifdef SAVE_STUFF #include @@ -211,6 +221,159 @@ Inventory Actor::GetInventory() const noexcept return inventory; } +Container Actor::GetFullContainer() const noexcept +{ + auto& modSystem = World::Get().GetModSystem(); + Container fullContainer{}; + + if (TESContainer* pBaseContainer = GetContainer()) + { + for (int i = 0; i < pBaseContainer->count; i++) + { + TESContainer::Entry* pGameEntry = pBaseContainer->entries[i]; + if (!pGameEntry || !pGameEntry->form) + { + spdlog::warn("Entry or form for inventory item is null."); + continue; + } + + Container::Entry entry; + modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); + entry.Count = pGameEntry->count; + + fullContainer.Entries.push_back(std::move(entry)); + } + } + + Container extraContainer{}; + + auto pExtraContChangesEntries = GetContainerChanges()->entries; + for (auto pGameEntry : *pExtraContChangesEntries) + { + Container::Entry entry; + modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); + entry.Count = pGameEntry->count; + + for (BSExtraDataList* pExtraDataList : *pGameEntry->dataList) + { + if (!pExtraDataList) + { + spdlog::warn("Null ExtraDataList?"); + continue; + } + + Container::Entry innerEntry; + innerEntry.BaseId = entry.BaseId; + innerEntry.Count = 1; + + if (ExtraCount* pExtraCount = (ExtraCount*)pExtraDataList->GetByType(ExtraData::Count)) + { + innerEntry.Count = pExtraCount->count; + } + + if (ExtraCharge* pExtraCharge = (ExtraCharge*)pExtraDataList->GetByType(ExtraData::Charge)) + { + innerEntry.ExtraCharge = pExtraCharge->fCharge; + } + + if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)pExtraDataList->GetByType(ExtraData::Enchantment)) + { + TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); + // TODO: enchantments seem to always be temporaries, keep this in mind when trying to apply container + // Get base form id of enchantment instead? Probably gonna have to serialize more data + modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); + innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; + innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; + } + + if (ExtraHealth* pExtraHealth = (ExtraHealth*)pExtraDataList->GetByType(ExtraData::Health)) + { + innerEntry.ExtraHealth = pExtraHealth->fHealth; + } + + if (ExtraPoison* pExtraPoison = (ExtraPoison*)pExtraDataList->GetByType(ExtraData::Poison)) + { + TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); + modSystem.GetServerModId(pExtraPoison->pPoison->formID, innerEntry.ExtraPoisonId); + innerEntry.ExtraPoisonCount = pExtraPoison->uiCount; + } + + if (ExtraSoul* pExtraSoul = (ExtraSoul*)pExtraDataList->GetByType(ExtraData::Soul)) + { + innerEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; + } + + if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)pExtraDataList->GetByType(ExtraData::TextDisplayData)) + { + if (pExtraTextDisplayData->DisplayName) + innerEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; + else + innerEntry.ExtraTextDisplayName = "NULL DISPLAY NAME"; + } + + innerEntry.ExtraWorn = pExtraDataList->Contains(ExtraData::Worn); + innerEntry.ExtraWornLeft = pExtraDataList->Contains(ExtraData::WornLeft); + + entry.Count -= innerEntry.Count; + + extraContainer.Entries.push_back(std::move(innerEntry)); + } + + if (entry.Count != 0) + extraContainer.Entries.push_back(std::move(entry)); + } + + spdlog::info("ExtraContainer count: {}", extraContainer.Entries.size()); + + Container minimizedExtraContainer{}; + + for (auto& entry : extraContainer.Entries) + { + auto& duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](Container::Entry newEntry) { + return newEntry.CanBeMerged(entry); + }); + + if (duplicate == std::end(minimizedExtraContainer.Entries)) + { + minimizedExtraContainer.Entries.push_back(entry); + continue; + } + + duplicate->Count += entry.Count; + } + + spdlog::info("MinExtraContainer count: {}", minimizedExtraContainer.Entries.size()); + + for (auto& entry : minimizedExtraContainer.Entries) + { + if (entry.ContainsExtraData()) + continue; + + auto& duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](Container::Entry newEntry) { + return newEntry.CanBeMerged(entry); + }); + + if (duplicate == std::end(fullContainer.Entries)) + continue; + + entry.Count += duplicate->Count; + duplicate->Count = 0; + } + + spdlog::info("MinExtraContainer count after: {}", minimizedExtraContainer.Entries.size()); + + fullContainer.Entries.insert(fullContainer.Entries.end(), minimizedExtraContainer.Entries.begin(), + minimizedExtraContainer.Entries.end()); + + std::remove_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [](Container::Entry entry) { + return entry.Count == 0; + }); + + spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); + + return fullContainer; +} + Factions Actor::GetFactions() const noexcept { Factions result; @@ -451,7 +614,7 @@ void Actor::UnEquipAll() noexcept RemoveAllItems(); // Taken from skyrim's code shouts can be two form types apparently - if (equippedShout && (equippedShout->formType - 41) <= 1) + if (equippedShout && ((int)equippedShout->formType - 41) <= 1) { EquipManager::Get()->UnEquipShout(this, equippedShout); equippedShout = nullptr; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 6851e6b41..b1b7d1828 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -13,6 +13,7 @@ #include #include #include +#include struct TESNPC; struct TESRace; @@ -24,7 +25,7 @@ struct CombatController; struct Actor : TESObjectREFR { - static constexpr uint32_t Type = FormType::Character; + static constexpr FormType Type = FormType::Character; // Allocs and calls constructor static GamePtr New() noexcept; @@ -192,6 +193,7 @@ struct Actor : TESObjectREFR float GetActorMaxValue(uint32_t aId) const noexcept; Inventory GetInventory() const noexcept; + Container GetFullContainer() const noexcept; Factions GetFactions() const noexcept; ActorValues GetEssentialActorValues() const noexcept; diff --git a/Code/client/Games/Skyrim/Forms/TESForm.h b/Code/client/Games/Skyrim/Forms/TESForm.h index fcb28109a..2ff4ec2ab 100644 --- a/Code/client/Games/Skyrim/Forms/TESForm.h +++ b/Code/client/Games/Skyrim/Forms/TESForm.h @@ -2,7 +2,7 @@ #include -enum FormType : uint8_t +enum class FormType : uint8_t { Book = 27, Container = 28, diff --git a/Code/client/Games/Skyrim/Forms/TESNPC.h b/Code/client/Games/Skyrim/Forms/TESNPC.h index 79717f948..8d57135e9 100644 --- a/Code/client/Games/Skyrim/Forms/TESNPC.h +++ b/Code/client/Games/Skyrim/Forms/TESNPC.h @@ -16,7 +16,7 @@ struct BGSRelationship; struct TESNPC : TESActorBase { - static constexpr uint32_t Type = FormType::Npc; + static constexpr FormType Type = FormType::Npc; static TESNPC* Create(const String& acBuffer, uint32_t aChangeFlags) noexcept; diff --git a/Code/client/Games/Skyrim/PlayerCharacter.h b/Code/client/Games/Skyrim/PlayerCharacter.h index a441c49dd..7de3d3b59 100644 --- a/Code/client/Games/Skyrim/PlayerCharacter.h +++ b/Code/client/Games/Skyrim/PlayerCharacter.h @@ -7,7 +7,7 @@ struct TESQuest; struct PlayerCharacter : Actor { - static constexpr uint32_t Type = FormType::Character; + static constexpr FormType Type = FormType::Character; static PlayerCharacter* Get() noexcept; diff --git a/Code/client/Services/Generic/EntityService.cpp b/Code/client/Services/Generic/EntityService.cpp index 69f53b7ce..5252e3676 100644 --- a/Code/client/Services/Generic/EntityService.cpp +++ b/Code/client/Services/Generic/EntityService.cpp @@ -23,7 +23,7 @@ EntityService::EntityService(World& aWorld, entt::dispatcher& aDispatcher) noexc void EntityService::OnReferenceAdded(const ReferenceAddedEvent& acEvent) noexcept { - if (acEvent.FormType == Character) + if (acEvent.FormType == FormType::Character) { if (acEvent.FormId == 0x14) { diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index 76a3ad3ef..6c837c458 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -181,6 +181,8 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; + auto fullContainer = PlayerCharacter::Get()->GetFullContainer(); + spdlog::info("Full container entries: {}", fullContainer.Entries.size()); /* auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h new file mode 100644 index 000000000..a636e2ed6 --- /dev/null +++ b/Code/encoding/Structs/Container.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +using TiltedPhoques::Buffer; + +struct Container +{ + struct Entry + { + GameId BaseId{}; + int32_t Count{}; + + // TODO: refactor extra data stuff + // these are the extra data items seemingly relevant to container items + float ExtraCharge{}; + + GameId ExtraEnchantId{}; + uint16_t ExtraEnchantCharge{}; + bool ExtraEnchantRemoveUnequip{}; + + float ExtraHealth{}; + + GameId ExtraPoisonId{}; + uint32_t ExtraPoisonCount{}; + + int32_t ExtraSoulLevel{}; + + String ExtraTextDisplayName{}; + + bool ExtraWorn{}; + bool ExtraWornLeft{}; + + bool ContainsExtraData() const noexcept + { + return !IsExtraDataEquals(Entry{}); + } + + bool CanBeMerged(const Entry& acRhs) const noexcept + { + return BaseId == acRhs.BaseId && IsExtraDataEquals(acRhs); + } + + bool IsExtraDataEquals(const Entry& acRhs) const noexcept + { + return ExtraCharge == acRhs.ExtraCharge && + ExtraEnchantId == acRhs.ExtraEnchantId && + ExtraEnchantCharge == acRhs.ExtraEnchantCharge && + ExtraEnchantRemoveUnequip == acRhs.ExtraEnchantRemoveUnequip && + ExtraHealth == acRhs.ExtraHealth && + ExtraPoisonId == acRhs.ExtraPoisonId && + ExtraPoisonCount == acRhs.ExtraPoisonCount && + ExtraSoulLevel == acRhs.ExtraSoulLevel && + //ExtraTextDisplayName == acRhs.ExtraTextDisplayName && + ExtraWorn == acRhs.ExtraWorn && + ExtraWornLeft == acRhs.ExtraWornLeft; + } + }; + + Container() = default; + ~Container() = default; + + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; + void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; + + + Vector Entries{}; +}; From f96ae176aab5a8b30da41d2af8f542e2f3d47ff0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 23 Dec 2021 22:55:47 +0100 Subject: [PATCH 02/48] feat: TESObjectREFR::AddItem() --- Code/client/Games/ExtraData.cpp | 29 ++++++ Code/client/Games/ExtraData.h | 6 +- Code/client/Games/Skyrim/Actor.cpp | 1 + .../Skyrim/ExtraData/ExtraTextDisplayData.h | 3 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 94 +++++++++++++++++++ Code/client/Games/Skyrim/TESObjectREFR.h | 5 +- Code/client/Services/Generic/TestService.cpp | 19 +++- 7 files changed, 153 insertions(+), 4 deletions(-) diff --git a/Code/client/Games/ExtraData.cpp b/Code/client/Games/ExtraData.cpp index 0535d40a0..2c67ea653 100644 --- a/Code/client/Games/ExtraData.cpp +++ b/Code/client/Games/ExtraData.cpp @@ -36,3 +36,32 @@ BSExtraData* BSExtraDataList::GetByType(ExtraData aType) const return pEntry; } + +bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) +{ + if (Contains(aType)) + return false; + + lock.Lock(); + + BSExtraData* pNext = data; + data = apNewData; + apNewData->next = pNext; + SetType(aType, false); + + lock.Unlock(); + + return true; +} + +void BSExtraDataList::SetType(ExtraData aType, bool aClear) +{ + uint32_t index = static_cast(aType) >> 3; + uint8_t bitmask = 1 << (static_cast(aType) % 8); + uint8_t& flag = bitfield->data[index]; + if (aClear) + flag &= ~bitmask; + else + flag |= bitmask; +} + diff --git a/Code/client/Games/ExtraData.h b/Code/client/Games/ExtraData.h index a85b35e4c..837411d4c 100644 --- a/Code/client/Games/ExtraData.h +++ b/Code/client/Games/ExtraData.h @@ -33,8 +33,10 @@ struct BSExtraDataList bool Contains(ExtraData aType) const; void Set(ExtraData aType, bool aSet); - bool Add(ExtraData aType, BSExtraData* apOther); - bool Remove(ExtraData aType, BSExtraData* apOther); + bool Add(ExtraData aType, BSExtraData* apNewData); + bool Remove(ExtraData aType, BSExtraData* apNewData); + + void SetType(ExtraData aType, bool aClear); BSExtraData* GetByType(ExtraData type) const; #if TP_FALLOUT4 diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 93501443a..239baef48 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -371,6 +371,7 @@ Container Actor::GetFullContainer() const noexcept spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); + // TODO: doesn't filter all duplicates? return fullContainer; } diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h index a80026519..1275681ec 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h @@ -8,6 +8,7 @@ struct ExtraTextDisplayData : BSExtraData inline static constexpr auto eExtraData = ExtraData::TextDisplayData; BSFixedString DisplayName; + uint8_t pad[0x20]; // TODO: implement the rest when i dont feel lazy /* @@ -18,3 +19,5 @@ struct ExtraTextDisplayData : BSExtraData unsigned __int16 usCustomNameLength; */ }; + +static_assert(sizeof(ExtraTextDisplayData) == 0x38); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 902620c0a..233f5add3 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -9,6 +9,16 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing); TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); TP_THIS_FUNCTION(TRemoveInventoryItem, void*, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, BSExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3); @@ -139,6 +149,90 @@ int64_t TESObjectREFR::GetItemCountInInventory(TESForm* apItem) const noexcept return count; } +void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept +{ + ModSystem& modSystem = World::Get().GetModSystem(); + + uint32_t objectId = modSystem.GetGameId(arEntry.BaseId); + TESBoundObject* pObject = RTTI_CAST(TESForm::GetById(objectId), TESForm, TESBoundObject); + if (!pObject) + { + spdlog::warn("{}: Object to add not found.", __FUNCTION__); + return; + } + + BSExtraDataList* pExtraData = nullptr; + + if (arEntry.ContainsExtraData()) + { + pExtraData = Memory::Allocate(); + pExtraData->bitfield = Memory::Allocate(); + + if (arEntry.ExtraCharge > 0.f) + { + ExtraCharge* pExtraCharge = Memory::Allocate(); + pExtraCharge->fCharge = arEntry.ExtraCharge; + pExtraData->Add(ExtraData::Charge, pExtraCharge); + } + + // TODO: deal with temp forms for enchanted items + if (arEntry.ExtraEnchantId != 0) + { + } + + if (arEntry.ExtraHealth > 0.f) + { + ExtraHealth* pExtraHealth = Memory::Allocate(); + pExtraHealth->fHealth = arEntry.ExtraHealth; + pExtraData->Add(ExtraData::Health, pExtraHealth); + } + + // TODO: does poison have the same temp problem as enchants? + // put an assert for now + if (arEntry.ExtraPoisonId != 0) + { + TP_ASSERT(arEntry.ExtraPoisonId.ModId == 0xFFFFFFFF, "Poison is sent as temp!"); + + uint32_t poisonId = modSystem.GetGameId(arEntry.ExtraPoisonId); + if (AlchemyItem* pPoison = RTTI_CAST(TESForm::GetById(poisonId), TESForm, AlchemyItem)) + { + ExtraPoison* pExtraPoison = Memory::Allocate(); + pExtraPoison->pPoison = pPoison; + pExtraPoison->uiCount = arEntry.ExtraPoisonCount; + pExtraData->Add(ExtraData::Poison, pExtraPoison); + } + } + + if (arEntry.ExtraSoulLevel > 0 && arEntry.ExtraSoulLevel <= 5) + { + ExtraSoul* pExtraSoul = Memory::Allocate(); + pExtraSoul->cSoul = static_cast(arEntry.ExtraSoulLevel); + pExtraData->Add(ExtraData::Soul, pExtraSoul); + } + + if (!arEntry.ExtraTextDisplayName.empty()) + { + ExtraTextDisplayData* pExtraText = Memory::Allocate(); + pExtraText->DisplayName = arEntry.ExtraTextDisplayName.c_str(); + pExtraData->Add(ExtraData::TextDisplayData, pExtraText); + } + + if (arEntry.ExtraWorn) + { + ExtraWorn* pExtraWorn = Memory::Allocate(); + pExtraData->Add(ExtraData::Worn, pExtraWorn); + } + + if (arEntry.ExtraWornLeft) + { + ExtraWornLeft* pExtraWornLeft = Memory::Allocate(); + pExtraData->Add(ExtraData::WornLeft, pExtraWornLeft); + } + } + + AddObjectToContainer(pObject, pExtraData, arEntry.Count, nullptr); +} + void TESObjectREFR::Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* aObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept { return ThisCall(RealActivate, this, apActivator, aUnk1, aObjectToGet, aCount, aDefaultProcessing); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 873872598..d1649761e 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -9,6 +9,7 @@ #include #include #include +#include struct AnimationVariables; struct TESWorldSpace; @@ -70,7 +71,7 @@ struct TESObjectREFR : TESForm virtual void sub_57(); virtual void sub_58(); virtual void sub_59(); - virtual void sub_5A(); + virtual void AddObjectToContainer(TESBoundObject* apObj, BSExtraDataList* aspExtra, int32_t aicount, TESObjectREFR* apOldContainer); virtual void sub_5B(); virtual MagicCaster* GetMagicCaster(MagicSystem::CastingSource aeSource); virtual void sub_5D(); @@ -169,6 +170,8 @@ struct TESObjectREFR : TESForm Lock* CreateLock() noexcept; void LockChange() noexcept; + void AddItem(Container::Entry& arEntry) noexcept; + BSHandleRefObject handleRefObject; uintptr_t unk1C; IAnimationGraphManagerHolder animationGraphHolder; diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index 6c837c458..5205730cc 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -71,7 +71,7 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept //const auto pNpc = TESNPC::Create(data, pPlayerBaseForm->GetChangeFlags()); auto pActor = Actor::Create(pPlayerBaseForm); - pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); + //pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); m_actors.emplace_back(pActor); } @@ -161,6 +161,7 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { static std::atomic s_f8Pressed = false; static std::atomic s_f7Pressed = false; + static std::atomic s_f6Pressed = false; RunDiff(); @@ -184,6 +185,10 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept auto fullContainer = PlayerCharacter::Get()->GetFullContainer(); spdlog::info("Full container entries: {}", fullContainer.Entries.size()); + auto pActor = m_actors[0]; + auto* pObj = RTTI_CAST(TESForm::GetById(0x139B9), TESForm, TESBoundObject); + pActor->AddObjectToContainer(pObj, nullptr, 1, nullptr); + /* auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); pActor->SetWeaponDrawnEx(true); @@ -207,6 +212,18 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept } else s_f8Pressed = false; + + if (GetAsyncKeyState(VK_F6)) + { + if (!s_f6Pressed) + { + s_f6Pressed = true; + + PlaceActorInWorld(); + } + } + else + s_f6Pressed = false; } uint64_t TestService::DisplayGraphDescriptorKey(BSAnimationGraphManager* pManager) noexcept From c5412d2c12943d8c086f3e54e185aaff6372799b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 23 Dec 2021 23:07:22 +0100 Subject: [PATCH 03/48] fix: deadlock issue with AddItem --- Code/client/Games/ExtraData.cpp | 4 +--- Code/client/Services/Generic/TestService.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Code/client/Games/ExtraData.cpp b/Code/client/Games/ExtraData.cpp index 2c67ea653..685519686 100644 --- a/Code/client/Games/ExtraData.cpp +++ b/Code/client/Games/ExtraData.cpp @@ -42,15 +42,13 @@ bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) if (Contains(aType)) return false; - lock.Lock(); + BSScopedLock _(lock); BSExtraData* pNext = data; data = apNewData; apNewData->next = pNext; SetType(aType, false); - lock.Unlock(); - return true; } diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index 5205730cc..05e6d9a21 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #endif #include @@ -185,9 +186,14 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept auto fullContainer = PlayerCharacter::Get()->GetFullContainer(); spdlog::info("Full container entries: {}", fullContainer.Entries.size()); - auto pActor = m_actors[0]; - auto* pObj = RTTI_CAST(TESForm::GetById(0x139B9), TESForm, TESBoundObject); - pActor->AddObjectToContainer(pObj, nullptr, 1, nullptr); + for (auto entry : fullContainer.Entries) + { + if (entry.BaseId.BaseId == 0x139b9) + { + m_actors[0]->AddItem(entry); + break; + } + } /* auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); From e1f7c9402a9d7328dfcc56fed4a638fbbf478b68 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 24 Dec 2021 01:58:29 +0100 Subject: [PATCH 04/48] tweak: null and vtables and pointers and stuff --- Code/client/Games/ExtraData.cpp | 17 +- Code/client/Games/ExtraData.h | 4 +- Code/client/Games/Skyrim/Actor.cpp | 5 +- .../Games/Skyrim/ExtraData/ExtraCharge.h | 2 +- .../Games/Skyrim/ExtraData/ExtraHealth.h | 2 +- .../Games/Skyrim/ExtraData/ExtraPoison.h | 4 +- .../client/Games/Skyrim/ExtraData/ExtraSoul.h | 2 +- .../Skyrim/ExtraData/ExtraTextDisplayData.h | 4 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 9 +- Code/client/Services/Generic/TestService.cpp | 302 ++++++++++-------- 10 files changed, 200 insertions(+), 151 deletions(-) diff --git a/Code/client/Games/ExtraData.cpp b/Code/client/Games/ExtraData.cpp index 685519686..4501f126a 100644 --- a/Code/client/Games/ExtraData.cpp +++ b/Code/client/Games/ExtraData.cpp @@ -42,7 +42,8 @@ bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) if (Contains(aType)) return false; - BSScopedLock _(lock); + // TODO: this sometimes causes a deadlock + //BSScopedLock _(lock); BSExtraData* pNext = data; data = apNewData; @@ -52,6 +53,20 @@ bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) return true; } +uint32_t BSExtraDataList::GetCount() const +{ + uint32_t count = 0; + + BSExtraData* pNext = data; + while (pNext) + { + count++; + pNext = pNext->next; + } + + return count; +} + void BSExtraDataList::SetType(ExtraData aType, bool aClear) { uint32_t index = static_cast(aType) >> 3; diff --git a/Code/client/Games/ExtraData.h b/Code/client/Games/ExtraData.h index 837411d4c..97cb0a5e2 100644 --- a/Code/client/Games/ExtraData.h +++ b/Code/client/Games/ExtraData.h @@ -13,7 +13,7 @@ struct BSExtraData virtual ~BSExtraData() = 0; virtual ExtraData GetType() const noexcept = 0; - BSExtraData* next; + BSExtraData* next{}; #if TP_FALLOUT4 uint8_t pad10[2]; @@ -36,6 +36,8 @@ struct BSExtraDataList bool Add(ExtraData aType, BSExtraData* apNewData); bool Remove(ExtraData aType, BSExtraData* apNewData); + uint32_t GetCount() const; + void SetType(ExtraData aType, bool aClear); BSExtraData* GetByType(ExtraData type) const; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 239baef48..79fd3552f 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -853,7 +853,8 @@ static TiltedPhoques::Initializer s_actorHooks([]() RealDamageActor = s_damageActor.Get(); RealApplyActorEffect = s_applyActorEffect.Get(); RealRegenAttributes = s_regenAttributes.Get(); - RealAddInventoryItem = s_addInventoryItem.Get(); + // TODO: re-enable this hook (obviously interferes with AddItem()) + //RealAddInventoryItem = s_addInventoryItem.Get(); RealPickUpItem = s_pickUpItem.Get(); RealUpdateDetectionState = s_updateDetectionState.Get(); RealProcessResponse = s_processResponse.Get(); @@ -865,7 +866,7 @@ static TiltedPhoques::Initializer s_actorHooks([]() TP_HOOK(&RealDamageActor, HookDamageActor); TP_HOOK(&RealApplyActorEffect, HookApplyActorEffect); TP_HOOK(&RealRegenAttributes, HookRegenAttributes); - TP_HOOK(&RealAddInventoryItem, HookAddInventoryItem); + //TP_HOOK(&RealAddInventoryItem, HookAddInventoryItem); TP_HOOK(&RealPickUpItem, HookPickUpItem); TP_HOOK(&RealUpdateDetectionState, HookUpdateDetectionState); TP_HOOK(&RealProcessResponse, HookProcessResponse); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraCharge.h b/Code/client/Games/Skyrim/ExtraData/ExtraCharge.h index 7cef3242a..09e3bfc30 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraCharge.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraCharge.h @@ -6,7 +6,7 @@ struct ExtraCharge : BSExtraData { inline static constexpr auto eExtraData = ExtraData::Charge; - float fCharge; + float fCharge{}; }; static_assert(sizeof(ExtraCharge) == 0x18); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraHealth.h b/Code/client/Games/Skyrim/ExtraData/ExtraHealth.h index 686021d3f..8a4d514d4 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraHealth.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraHealth.h @@ -6,7 +6,7 @@ struct ExtraHealth : BSExtraData { inline static constexpr auto eExtraData = ExtraData::Health; - float fHealth; + float fHealth{}; }; static_assert(sizeof(ExtraHealth) == 0x18); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraPoison.h b/Code/client/Games/Skyrim/ExtraData/ExtraPoison.h index 692848db0..014116f70 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraPoison.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraPoison.h @@ -8,8 +8,8 @@ struct ExtraPoison : BSExtraData { inline static constexpr auto eExtraData = ExtraData::Poison; - AlchemyItem* pPoison; - uint32_t uiCount; + AlchemyItem* pPoison{}; + uint32_t uiCount{}; }; static_assert(sizeof(ExtraPoison) == 0x20); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraSoul.h b/Code/client/Games/Skyrim/ExtraData/ExtraSoul.h index 88f4ed135..1cd09d7a6 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraSoul.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraSoul.h @@ -17,7 +17,7 @@ struct ExtraSoul : BSExtraData { inline static constexpr auto eExtraData = ExtraData::Soul; - SOUL_LEVEL cSoul; + SOUL_LEVEL cSoul{}; }; static_assert(sizeof(ExtraSoul) == 0x18); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h index 1275681ec..f0e7dc3e7 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h @@ -7,8 +7,8 @@ struct ExtraTextDisplayData : BSExtraData { inline static constexpr auto eExtraData = ExtraData::TextDisplayData; - BSFixedString DisplayName; - uint8_t pad[0x20]; + BSFixedString DisplayName{}; + uint8_t pad[0x20]{}; // TODO: implement the rest when i dont feel lazy /* diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 233f5add3..66236d523 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -166,7 +166,9 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ContainsExtraData()) { pExtraData = Memory::Allocate(); + pExtraData->lock.m_counter = pExtraData->lock.m_tid = 0; pExtraData->bitfield = Memory::Allocate(); + memset(pExtraData->bitfield, 0, 0x18); if (arEntry.ExtraCharge > 0.f) { @@ -191,12 +193,13 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept // put an assert for now if (arEntry.ExtraPoisonId != 0) { - TP_ASSERT(arEntry.ExtraPoisonId.ModId == 0xFFFFFFFF, "Poison is sent as temp!"); + TP_ASSERT(arEntry.ExtraPoisonId.ModId != 0xFFFFFFFF, "Poison is sent as temp!"); uint32_t poisonId = modSystem.GetGameId(arEntry.ExtraPoisonId); if (AlchemyItem* pPoison = RTTI_CAST(TESForm::GetById(poisonId), TESForm, AlchemyItem)) { ExtraPoison* pExtraPoison = Memory::Allocate(); + *((uint64_t*)pExtraPoison) = 0x141623E50; pExtraPoison->pPoison = pPoison; pExtraPoison->uiCount = arEntry.ExtraPoisonCount; pExtraData->Add(ExtraData::Poison, pExtraPoison); @@ -220,17 +223,21 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ExtraWorn) { ExtraWorn* pExtraWorn = Memory::Allocate(); + *((uint64_t*)pExtraWorn) = 0x1416239F0; pExtraData->Add(ExtraData::Worn, pExtraWorn); } if (arEntry.ExtraWornLeft) { ExtraWornLeft* pExtraWornLeft = Memory::Allocate(); + *((uint64_t*)pExtraWornLeft) = 0x141623A10; pExtraData->Add(ExtraData::WornLeft, pExtraWornLeft); } } AddObjectToContainer(pObject, pExtraData, arEntry.Count, nullptr); + spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, + pExtraData ? pExtraData->GetCount() : -1, arEntry.Count); } void TESObjectREFR::Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* aObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index 05e6d9a21..ea85cb9a9 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -188,7 +188,7 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept for (auto entry : fullContainer.Entries) { - if (entry.BaseId.BaseId == 0x139b9) + if (entry.BaseId.BaseId == 0x139B9) { m_actors[0]->AddItem(entry); break; @@ -978,182 +978,206 @@ void TestService::OnDraw() noexcept ImGui::SetNextWindowSize(ImVec2(250, 300), ImGuiCond_FirstUseEver); ImGui::Begin("Container debug"); - if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) + static uint32_t fetchContActorId = 0; + static Actor* pContainerActor = nullptr; + + ImGui::InputScalar("Form ID", ImGuiDataType_U32, &fetchContActorId, 0, 0, "%" PRIx32, ImGuiInputTextFlags_CharsHexadecimal); + + if (ImGui::Button("Look up")) { - if (ImGui::BeginTabItem("ExtraContainerChanges")) + if (fetchContActorId) { - static uint32_t s_selectedInvFormId = 0; + auto* pFetchForm = TESForm::GetById(fetchContActorId); + if (pFetchForm) + pContainerActor = RTTI_CAST(pFetchForm, TESForm, Actor); + } + } - auto* pContainerActor = PlayerCharacter::Get(); + if (pContainerActor) + { + if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) + { + if (ImGui::BeginTabItem("ExtraContainerChanges")) + { + static uint32_t s_selectedInvFormId = 0; - const auto pContainerChanges = pContainerActor->GetContainerChanges()->entries; + const auto pContainerChanges = pContainerActor->GetContainerChanges()->entries; - ImGui::BeginChild("Items", ImVec2(0, 200), true); + ImGui::BeginChild("Items", ImVec2(0, 200), true); - int i = 0; - for (auto pChange : *pContainerChanges) - { - if (pChange && pChange->form && pChange->dataList) + int i = 0; + for (auto pChange : *pContainerChanges) { - char name[256]; - sprintf_s(name, std::size(name), "%x", pChange->form->formID); - - if (ImGui::Selectable(name, s_selectedInvFormId == pChange->form->formID)) + if (pChange && pChange->form && pChange->dataList) { - s_selectedInvFormId = pChange->form->formID; + char name[256]; + sprintf_s(name, std::size(name), "%x", pChange->form->formID); + + if (ImGui::Selectable(name, s_selectedInvFormId == pChange->form->formID)) + { + s_selectedInvFormId = pChange->form->formID; + } } } - } - ImGui::EndChild(); + ImGui::EndChild(); - int itemCount = 0; - int dataListCount = 0; + int itemCount = 0; + int dataListCount = 0; - ImVec4 red{255.f, 0.f, 0.f, 255.f}; - ImVec4 green{0.f, 255.f, 0.f, 255.f}; + ImVec4 red{255.f, 0.f, 0.f, 255.f}; + ImVec4 green{0.f, 255.f, 0.f, 255.f}; - for (auto pChange : *pContainerChanges) - { - if (pChange && pChange->form && pChange->form->formID == s_selectedInvFormId) + for (auto pChange : *pContainerChanges) { - itemCount++; - - const auto pDataLists = pChange->dataList; - for (auto* pDataList : *pDataLists) + if (pChange && pChange->form && pChange->form->formID == s_selectedInvFormId) { - if (pDataList) - { - dataListCount++; - - char name[256]; - sprintf_s(name, std::size(name), "Item %d", itemCount); + itemCount++; - if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) + const auto pDataLists = pChange->dataList; + for (auto* pDataList : *pDataLists) + { + if (pDataList) { - BSScopedLock _(pDataList->lock); - - bool charge = pDataList->Contains(ExtraData::Charge); - ImGui::TextColored(charge ? green : red, "charge"); - if (charge) - { - auto pCharge = (ExtraCharge*)pDataList->GetByType(ExtraData::Charge); - ImGui::InputFloat("Charge", &pCharge->fCharge, 0, 0, "%.3f", - ImGuiInputTextFlags_ReadOnly); - } - - bool count = pDataList->Contains(ExtraData::Count); - ImGui::TextColored(count ? green : red, "count"); - if (count) - { - auto pCount = (ExtraCount*)pDataList->GetByType(ExtraData::Count); - auto iCount = int(pCount->count); - ImGui::InputInt("Item count", &iCount, 0, 0, ImGuiInputTextFlags_ReadOnly); - } - - bool enchantment = pDataList->Contains(ExtraData::Enchantment); - ImGui::TextColored(enchantment ? green : red, "enchantment"); - if (enchantment) - { - auto pEnchantment = (ExtraEnchantment*)pDataList->GetByType(ExtraData::Enchantment); - int enchantmentID = - pEnchantment->pEnchantment ? int(pEnchantment->pEnchantment->formID) : 0; - ImGui::InputInt("Enchantment id", &enchantmentID, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - int iCharge = int(pEnchantment->usCharge); - ImGui::InputInt("Charge", &iCharge, 0, 0, ImGuiInputTextFlags_ReadOnly); - int iRemoveOnUnequip = int(pEnchantment->bRemoveOnUnequip); - ImGui::InputInt("Remove on unequip?", &iRemoveOnUnequip, 0, 0, ImGuiInputTextFlags_ReadOnly); - } - - bool health = pDataList->Contains(ExtraData::Health); - ImGui::TextColored(health ? green : red, "health"); - if (health) - { - auto pHealth = (ExtraHealth*)pDataList->GetByType(ExtraData::Health); - ImGui::InputFloat("Health", &pHealth->fHealth, 0, 0, "%.3f", - ImGuiInputTextFlags_ReadOnly); - } - - bool poison = pDataList->Contains(ExtraData::Poison); - ImGui::TextColored(poison ? green : red, "poison"); - if (poison) - { - auto pPoison = (ExtraPoison*)pDataList->GetByType(ExtraData::Poison); - int poisonID = - pPoison->pPoison ? int(pPoison->pPoison->formID) : 0; - ImGui::InputInt("Poison id", &poisonID, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - int iCount = int(pPoison->uiCount); - ImGui::InputInt("Count", &iCount, 0, 0, ImGuiInputTextFlags_ReadOnly); - } + dataListCount++; - bool soul = pDataList->Contains(ExtraData::Soul); - ImGui::TextColored(soul ? green : red, "soul"); - if (soul) - { - auto pSoul = (ExtraSoul*)pDataList->GetByType(ExtraData::Soul); - auto iSoulLevel = int(pSoul->cSoul); - ImGui::InputInt("Soul level", &iSoulLevel, 0, 0, ImGuiInputTextFlags_ReadOnly); - } + char name[256]; + sprintf_s(name, std::size(name), "Item %d", itemCount); - bool textDisplayData = pDataList->Contains(ExtraData::TextDisplayData); - ImGui::TextColored(textDisplayData ? green : red, "textDisplayData"); - if (textDisplayData) + if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) { - auto pTextDisplayData = (ExtraTextDisplayData*)pDataList->GetByType(ExtraData::TextDisplayData); - char name[256]; - sprintf_s(name, std::size(name), "%s", pTextDisplayData->DisplayName.AsAscii()); - ImGui::InputText("Name", name, std::size(name), ImGuiInputTextFlags_ReadOnly); + BSScopedLock _(pDataList->lock); + + bool charge = pDataList->Contains(ExtraData::Charge); + ImGui::TextColored(charge ? green : red, "charge"); + if (charge) + { + auto pCharge = (ExtraCharge*)pDataList->GetByType(ExtraData::Charge); + ImGui::InputFloat("Charge", &pCharge->fCharge, 0, 0, "%.3f", + ImGuiInputTextFlags_ReadOnly); + } + + bool count = pDataList->Contains(ExtraData::Count); + ImGui::TextColored(count ? green : red, "count"); + if (count) + { + auto pCount = (ExtraCount*)pDataList->GetByType(ExtraData::Count); + auto iCount = int(pCount->count); + ImGui::InputInt("Item count", &iCount, 0, 0, ImGuiInputTextFlags_ReadOnly); + } + + bool enchantment = pDataList->Contains(ExtraData::Enchantment); + ImGui::TextColored(enchantment ? green : red, "enchantment"); + if (enchantment) + { + auto pEnchantment = + (ExtraEnchantment*)pDataList->GetByType(ExtraData::Enchantment); + int enchantmentID = + pEnchantment->pEnchantment ? int(pEnchantment->pEnchantment->formID) : 0; + ImGui::InputInt("Enchantment id", &enchantmentID, 0, 0, + ImGuiInputTextFlags_ReadOnly | + ImGuiInputTextFlags_CharsHexadecimal); + int iCharge = int(pEnchantment->usCharge); + ImGui::InputInt("Charge", &iCharge, 0, 0, ImGuiInputTextFlags_ReadOnly); + int iRemoveOnUnequip = int(pEnchantment->bRemoveOnUnequip); + ImGui::InputInt("Remove on unequip?", &iRemoveOnUnequip, 0, 0, + ImGuiInputTextFlags_ReadOnly); + } + + bool health = pDataList->Contains(ExtraData::Health); + ImGui::TextColored(health ? green : red, "health"); + if (health) + { + auto pHealth = (ExtraHealth*)pDataList->GetByType(ExtraData::Health); + ImGui::InputFloat("Health", &pHealth->fHealth, 0, 0, "%.3f", + ImGuiInputTextFlags_ReadOnly); + } + + bool poison = pDataList->Contains(ExtraData::Poison); + ImGui::TextColored(poison ? green : red, "poison"); + if (poison) + { + auto pPoison = (ExtraPoison*)pDataList->GetByType(ExtraData::Poison); + int poisonID = pPoison->pPoison ? int(pPoison->pPoison->formID) : 0; + ImGui::InputInt("Poison id", &poisonID, 0, 0, + ImGuiInputTextFlags_ReadOnly | + ImGuiInputTextFlags_CharsHexadecimal); + int iCount = int(pPoison->uiCount); + ImGui::InputInt("Count", &iCount, 0, 0, ImGuiInputTextFlags_ReadOnly); + } + + bool soul = pDataList->Contains(ExtraData::Soul); + ImGui::TextColored(soul ? green : red, "soul"); + if (soul) + { + auto pSoul = (ExtraSoul*)pDataList->GetByType(ExtraData::Soul); + auto iSoulLevel = int(pSoul->cSoul); + ImGui::InputInt("Soul level", &iSoulLevel, 0, 0, ImGuiInputTextFlags_ReadOnly); + } + + bool textDisplayData = pDataList->Contains(ExtraData::TextDisplayData); + ImGui::TextColored(textDisplayData ? green : red, "textDisplayData"); + if (textDisplayData) + { + auto pTextDisplayData = + (ExtraTextDisplayData*)pDataList->GetByType(ExtraData::TextDisplayData); + char name[256]; + sprintf_s(name, std::size(name), "%s", pTextDisplayData->DisplayName.AsAscii()); + ImGui::InputText("Name", name, std::size(name), ImGuiInputTextFlags_ReadOnly); + } + + bool worn = pDataList->Contains(ExtraData::Worn); + ImGui::TextColored(worn ? green : red, "worn"); + bool wornLeft = pDataList->Contains(ExtraData::WornLeft); + ImGui::TextColored(wornLeft ? green : red, "wornLeft"); } - - bool worn = pDataList->Contains(ExtraData::Worn); - ImGui::TextColored(worn ? green : red, "worn"); - bool wornLeft = pDataList->Contains(ExtraData::WornLeft); - ImGui::TextColored(wornLeft ? green : red, "wornLeft"); } } } } - } - if (ImGui::CollapsingHeader("Metadata", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::InputInt("Item count", &itemCount, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputInt("Data list count", &dataListCount, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - } - - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("TESContainer")) - { - static uint32_t s_selectedInvFormId = 0; + if (ImGui::CollapsingHeader("Metadata", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::InputInt("Item count", &itemCount, 0, 0, + ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Data list count", &dataListCount, 0, 0, + ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + } - PlayerCharacter* pPlayer = PlayerCharacter::Get(); - TESContainer* pContainer = pPlayer->GetContainer(); - if (pContainer && pContainer->entries) + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("TESContainer")) { - ImGui::Text("Items (%d)", pContainer->count); - - ImGui::BeginChild("Items", ImVec2(0, 200), true); + static uint32_t s_selectedInvFormId = 0; - for (int i = 0; i < pContainer->count; ++i) + PlayerCharacter* pPlayer = PlayerCharacter::Get(); + TESContainer* pContainer = pPlayer->GetContainer(); + if (pContainer && pContainer->entries) { - auto pEntry = pContainer->entries[i]; - if (!pEntry || !pEntry->form) - continue; + ImGui::Text("Items (%d)", pContainer->count); - char name[256]; - sprintf_s(name, std::size(name), "%x (%d)", pEntry->form->formID, pEntry->count); + ImGui::BeginChild("Items", ImVec2(0, 200), true); - if (ImGui::Selectable(name, s_selectedInvFormId == pEntry->form->formID)) + for (int i = 0; i < pContainer->count; ++i) { - s_selectedInvFormId = pEntry->form->formID; + auto pEntry = pContainer->entries[i]; + if (!pEntry || !pEntry->form) + continue; + + char name[256]; + sprintf_s(name, std::size(name), "%x (%d)", pEntry->form->formID, pEntry->count); + + if (ImGui::Selectable(name, s_selectedInvFormId == pEntry->form->formID)) + { + s_selectedInvFormId = pEntry->form->formID; + } } + + ImGui::EndChild(); } - ImGui::EndChild(); + ImGui::EndTabItem(); } - - ImGui::EndTabItem(); } } From 1110e42b206e34beccc6c57e4d9cf0bea360a3b3 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 27 Dec 2021 17:27:40 +0100 Subject: [PATCH 05/48] feat: Actor::SetFullContainer() --- Code/client/Games/ExtraData.h | 6 ++-- Code/client/Games/Skyrim/Actor.cpp | 38 ++++++++++++++++++++ Code/client/Games/Skyrim/Actor.h | 1 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 7 +++- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- Code/client/Services/Generic/TestService.cpp | 3 +- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Code/client/Games/ExtraData.h b/Code/client/Games/ExtraData.h index 97cb0a5e2..154d636d7 100644 --- a/Code/client/Games/ExtraData.h +++ b/Code/client/Games/ExtraData.h @@ -44,7 +44,7 @@ struct BSExtraDataList #if TP_FALLOUT4 void* unk0; #endif - BSExtraData* data; + BSExtraData* data = nullptr; struct Bitfield { @@ -54,6 +54,6 @@ struct BSExtraDataList void* unk10; #endif - Bitfield* bitfield; - mutable BSRecursiveLock lock; + Bitfield* bitfield{}; + mutable BSRecursiveLock lock{}; }; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 79fd3552f..33350ab26 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -375,6 +375,44 @@ Container Actor::GetFullContainer() const noexcept return fullContainer; } +void Actor::SetFullContainer(Container& acContainer) noexcept +{ + RemoveAllItems(); + + std::sort(acContainer.Entries.begin(), acContainer.Entries.end(), [](Container::Entry lhs, Container::Entry rhs) { + return lhs.Count < rhs.Count; + }); + + Container currentContainer = GetFullContainer(); + for (auto currentEntry : currentContainer.Entries) + { + auto& duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](Container::Entry newEntry) { + return newEntry.CanBeMerged(currentEntry); + }); + + if (duplicate != std::end(acContainer.Entries)) + { + duplicate->Count -= currentEntry.Count; + continue; + } + else + { + acContainer.Entries.push_back(*duplicate); + Container::Entry& back = acContainer.Entries.back(); + back.Count *= -1; + } + } + + std::remove_if(acContainer.Entries.begin(), acContainer.Entries.end(), [](Container::Entry entry) { + return entry.Count == 0; + }); + + for (const Container::Entry& entry : acContainer.Entries) + { + AddItem(entry); + } +} + Factions Actor::GetFactions() const noexcept { Factions result; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index b1b7d1828..8f29ed934 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -208,6 +208,7 @@ struct Actor : TESObjectREFR void SetFactionRank(const TESFaction* apFaction, int8_t aRank) noexcept; void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; + void SetFullContainer(Container& acContainer) noexcept; // Actions void UnEquipAll() noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 66236d523..0071e043c 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -149,7 +149,7 @@ int64_t TESObjectREFR::GetItemCountInInventory(TESForm* apItem) const noexcept return count; } -void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept +void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ModSystem& modSystem = World::Get().GetModSystem(); @@ -166,6 +166,7 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ContainsExtraData()) { pExtraData = Memory::Allocate(); + pExtraData->data = nullptr; pExtraData->lock.m_counter = pExtraData->lock.m_tid = 0; pExtraData->bitfield = Memory::Allocate(); memset(pExtraData->bitfield, 0, 0x18); @@ -173,6 +174,7 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ExtraCharge > 0.f) { ExtraCharge* pExtraCharge = Memory::Allocate(); + *((uint64_t*)pExtraCharge) = 0x141623AB0; pExtraCharge->fCharge = arEntry.ExtraCharge; pExtraData->Add(ExtraData::Charge, pExtraCharge); } @@ -185,6 +187,7 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ExtraHealth > 0.f) { ExtraHealth* pExtraHealth = Memory::Allocate(); + *((uint64_t*)pExtraHealth) = 0x141623A50; pExtraHealth->fHealth = arEntry.ExtraHealth; pExtraData->Add(ExtraData::Health, pExtraHealth); } @@ -209,6 +212,7 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (arEntry.ExtraSoulLevel > 0 && arEntry.ExtraSoulLevel <= 5) { ExtraSoul* pExtraSoul = Memory::Allocate(); + *((uint64_t*)pExtraSoul) = 0x141627220; pExtraSoul->cSoul = static_cast(arEntry.ExtraSoulLevel); pExtraData->Add(ExtraData::Soul, pExtraSoul); } @@ -216,6 +220,7 @@ void TESObjectREFR::AddItem(Container::Entry& arEntry) noexcept if (!arEntry.ExtraTextDisplayName.empty()) { ExtraTextDisplayData* pExtraText = Memory::Allocate(); + *((uint64_t*)pExtraText) = 0x1416244D0; pExtraText->DisplayName = arEntry.ExtraTextDisplayName.c_str(); pExtraData->Add(ExtraData::TextDisplayData, pExtraText); } diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index d1649761e..2e1e67636 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -170,7 +170,7 @@ struct TESObjectREFR : TESForm Lock* CreateLock() noexcept; void LockChange() noexcept; - void AddItem(Container::Entry& arEntry) noexcept; + void AddItem(const Container::Entry& arEntry) noexcept; BSHandleRefObject handleRefObject; uintptr_t unk1C; diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index ea85cb9a9..fc7136bcf 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -73,6 +73,7 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); //pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); + pActor->SetFullContainer(PlayerCharacter::Get()->GetFullContainer()); m_actors.emplace_back(pActor); } @@ -183,6 +184,7 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; + /* auto fullContainer = PlayerCharacter::Get()->GetFullContainer(); spdlog::info("Full container entries: {}", fullContainer.Entries.size()); @@ -195,7 +197,6 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept } } - /* auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); pActor->SetWeaponDrawnEx(true); From efde771dc28352677a0a28b27b9d0fd7248c6cc4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Wed, 26 Jan 2022 18:46:12 +0100 Subject: [PATCH 06/48] tweak: updated TiltedReverse --- Libraries/TiltedReverse | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/TiltedReverse b/Libraries/TiltedReverse index 775f16594..263057ed1 160000 --- a/Libraries/TiltedReverse +++ b/Libraries/TiltedReverse @@ -1 +1 @@ -Subproject commit 775f16594ccbfcdbdaa18db28a629ba20a92ba4b +Subproject commit 263057ed1f8420aee724db400601de2ee765bf64 From 893591952a9a2e3be52619614b1fb7ea7575394e Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 28 Jan 2022 15:00:47 +0100 Subject: [PATCH 07/48] fix: build errors --- Code/client/Games/Skyrim/Actor.cpp | 6 +++--- Code/client/Services/Generic/TestService.cpp | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index f1a8420f5..da4a06400 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -330,7 +330,7 @@ Container Actor::GetFullContainer() const noexcept for (auto& entry : extraContainer.Entries) { - auto& duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](Container::Entry newEntry) { + auto duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](const Container::Entry& newEntry) { return newEntry.CanBeMerged(entry); }); @@ -350,7 +350,7 @@ Container Actor::GetFullContainer() const noexcept if (entry.ContainsExtraData()) continue; - auto& duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](Container::Entry newEntry) { + auto duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](const Container::Entry& newEntry) { return newEntry.CanBeMerged(entry); }); @@ -387,7 +387,7 @@ void Actor::SetFullContainer(Container& acContainer) noexcept Container currentContainer = GetFullContainer(); for (auto currentEntry : currentContainer.Entries) { - auto& duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](Container::Entry newEntry) { + auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Container::Entry& newEntry) { return newEntry.CanBeMerged(currentEntry); }); diff --git a/Code/client/Services/Generic/TestService.cpp b/Code/client/Services/Generic/TestService.cpp index fc7136bcf..2eeb02743 100644 --- a/Code/client/Services/Generic/TestService.cpp +++ b/Code/client/Services/Generic/TestService.cpp @@ -73,7 +73,8 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); //pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); - pActor->SetFullContainer(PlayerCharacter::Get()->GetFullContainer()); + Container container = PlayerCharacter::Get()->GetFullContainer(); + pActor->SetFullContainer(container); m_actors.emplace_back(pActor); } @@ -184,7 +185,6 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - /* auto fullContainer = PlayerCharacter::Get()->GetFullContainer(); spdlog::info("Full container entries: {}", fullContainer.Entries.size()); @@ -197,6 +197,7 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept } } + /* auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); pActor->SetWeaponDrawnEx(true); From 5fe65331c281e747b52ebf28697781c01dc43431 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 28 Jan 2022 22:41:14 +0100 Subject: [PATCH 08/48] tweak: (de)serialize entries in Container struct --- Code/common/Structs/Container.cpp | 67 +++++++++++++++++++++++++++++++ Code/encoding/Structs/Container.h | 9 ++++- 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Code/common/Structs/Container.cpp diff --git a/Code/common/Structs/Container.cpp b/Code/common/Structs/Container.cpp new file mode 100644 index 000000000..5fb29e589 --- /dev/null +++ b/Code/common/Structs/Container.cpp @@ -0,0 +1,67 @@ +#include +#include + +using TiltedPhoques::Serialization; + +void Container::Entry::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + BaseId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, Count); + Serialization::WriteFloat(aWriter, ExtraCharge); + ExtraEnchantId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, ExtraEnchantCharge); + Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); + Serialization::WriteFloat(aWriter, ExtraHealth); + ExtraPoisonId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, ExtraPoisonCount); + Serialization::WriteVarInt(aWriter, ExtraSoulLevel); + Serialization::WriteString(aWriter, ExtraTextDisplayName); + Serialization::WriteBool(aWriter, ExtraWorn); + Serialization::WriteBool(aWriter, ExtraWornLeft); +} + +void Container::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + BaseId.Deserialize(aReader); + Count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraCharge = Serialization::ReadFloat(aReader); + ExtraEnchantId.Deserialize(aReader); + ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; + ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); + ExtraHealth = Serialization::ReadFloat(aReader); + ExtraPoisonId.Deserialize(aReader); + ExtraPoisonCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraSoulLevel = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraTextDisplayName = Serialization::ReadString(aReader); + ExtraWorn = Serialization::ReadBool(aReader); + ExtraWornLeft = Serialization::ReadBool(aReader); +} + +bool Container::operator==(const Container& acRhs) const noexcept +{ + return Entries == acRhs.Entries; +} + +bool Container::operator!=(const Container& acRhs) const noexcept +{ + return !this->operator==(acRhs); +} + +void Container::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteVarInt(aWriter, Entries.size()); + for (auto& entry : Entries) + { + entry.Serialize(aWriter); + } +} + +void Container::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + for (int i = 0; i < count; i++) + { + Entry entry; + entry.Deserialize(aReader); + } +} diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h index a636e2ed6..d399c8e3c 100644 --- a/Code/encoding/Structs/Container.h +++ b/Code/encoding/Structs/Container.h @@ -4,6 +4,8 @@ #include using TiltedPhoques::Buffer; +using TiltedPhoques::String; +using TiltedPhoques::Vector; struct Container { @@ -32,6 +34,9 @@ struct Container bool ExtraWorn{}; bool ExtraWornLeft{}; + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; + void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; + bool ContainsExtraData() const noexcept { return !IsExtraDataEquals(Entry{}); @@ -61,9 +66,11 @@ struct Container Container() = default; ~Container() = default; + bool operator==(const Container& acRhs) const noexcept; + bool operator!=(const Container& acRhs) const noexcept; + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; - Vector Entries{}; }; From 395713ab2c2f5f2dc6e2461c73164852bab29e4e Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 28 Jan 2022 22:45:15 +0100 Subject: [PATCH 09/48] tweak: added Container.cpp to the right folder --- Code/{common => encoding}/Structs/Container.cpp | 1 + 1 file changed, 1 insertion(+) rename Code/{common => encoding}/Structs/Container.cpp (98%) diff --git a/Code/common/Structs/Container.cpp b/Code/encoding/Structs/Container.cpp similarity index 98% rename from Code/common/Structs/Container.cpp rename to Code/encoding/Structs/Container.cpp index 5fb29e589..e3b1ca5d9 100644 --- a/Code/common/Structs/Container.cpp +++ b/Code/encoding/Structs/Container.cpp @@ -63,5 +63,6 @@ void Container::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept { Entry entry; entry.Deserialize(aReader); + Entries.push_back(entry); } } From b9751bfcd555007f67cb730b0695f7f67a7659c7 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 28 Jan 2022 22:48:04 +0100 Subject: [PATCH 10/48] tweak: further cleanup of TestService --- Code/client/Services/Debug/TestService.cpp | 98 +--------------------- Code/client/Services/TestService.h | 2 +- 2 files changed, 2 insertions(+), 98 deletions(-) diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index 09d86a627..1c8f65284 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -47,8 +47,6 @@ #include #endif -//#include - #include #include extern thread_local bool g_overrideFormId; @@ -57,7 +55,6 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept { const auto pPlayerBaseForm = static_cast(PlayerCharacter::Get()->baseForm); - // const auto pNpc = TESNPC::Create(data, pPlayerBaseForm->GetChangeFlags()); auto pActor = Actor::Create(pPlayerBaseForm); pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); @@ -73,81 +70,6 @@ TestService::TestService(entt::dispatcher& aDispatcher, World& aWorld, Transport m_drawImGuiConnection = aImguiService.OnDraw.connect<&TestService::OnDraw>(this); } -void TestService::RunDiff() -{ - /* - BSAnimationGraphManager* pManager = nullptr; - BSAnimationGraphManager* pActorManager = nullptr; - - static Map s_values; - - if (m_actors.empty()) - return; - - auto pActor = m_actors[0]; - - AnimationVariables vars; - - PlayerCharacter::Get()->SaveAnimationVariables(vars); - pActor->LoadAnimationVariables(vars); - - if (PlayerCharacter::Get()->animationGraphHolder.GetBSAnimationGraph(&pManager) && - pActor->animationGraphHolder.GetBSAnimationGraph(&pActorManager)) - { - if (pManager->animationGraphIndex < pManager->animationGraphs.size) - { - const auto pGraph = pManager->animationGraphs.Get(pManager->animationGraphIndex); - const auto pActorGraph = pActorManager->animationGraphs.Get(pActorManager->animationGraphIndex); - if (pGraph && pActorGraph) - { - const auto pDb = pGraph->hkxDB; - const auto pBuckets = pDb->animationVariables.buckets; - const auto pVariableSet = pGraph->behaviorGraph->animationVariables; - const auto pActorVariableSet = pActorGraph->behaviorGraph->animationVariables; - - auto pDescriptor = - AnimationGraphDescriptorManager::Get().GetDescriptor(pGraph->behaviorGraph->stateMachine->name); - - if (pBuckets && pVariableSet && pActorVariableSet) - { - for (auto i = 0u; i < pVariableSet->size; ++i) - { - //if (pVariableSet->data[i] != pActorVariableSet->data[i]) - //spdlog::info("Diff {} expected: {} got: {}", i, pVariableSet->data[i], - pActorVariableSet->data[i]); - - auto itor = s_values.find(i); - if (itor == std::end(s_values)) - { - s_values[i] = pVariableSet->data[i]; - - if (!pDescriptor->IsSynced(i)) - { - spdlog::info("Variable {} initialized to f: {} i: {}", i, - *(float*)&pVariableSet->data[i], - *(int32_t*)&pVariableSet->data[i]); - } - } - else if (itor->second != pVariableSet->data[i] && !pDescriptor->IsSynced(i)) - { - spdlog::warn("Variable {} changed to f: {} i: {}", i, *(float*)&pVariableSet->data[i], - *(int32_t*)&pVariableSet->data[i]); - - s_values[i] = pVariableSet->data[i]; - //itor->second = pVariableSet->data[i]; - } - } - } - } - } - - pManager->Release(); - } - */ -} - -TestService::~TestService() noexcept = default; - void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { static std::atomic s_f8Pressed = false; @@ -177,25 +99,7 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - auto* pActor = (Actor*)TESForm::GetById(0xFF000DA5); - pActor->SetWeaponDrawnEx(true); - - // PlaceActorInWorld(); - - /* - const auto pPlayerBaseForm = static_cast(PlayerCharacter::Get()->baseForm); - - //const auto pNpc = TESNPC::Create(data, pPlayerBaseForm->GetChangeFlags()); - auto pActor = Actor::Create(pPlayerBaseForm); - pActor->SaveInventory(0); - - #if TP_SKYRIM64 - auto& objManager = DefaultObjectManager::Get(); - spdlog::info(objManager.isSomeActionReady); - #endif - - TP_ASSERT(0, "{}", 5) - */ + PlaceActorInWorld(); } } else diff --git a/Code/client/Services/TestService.h b/Code/client/Services/TestService.h index 52a386003..d2f127235 100644 --- a/Code/client/Services/TestService.h +++ b/Code/client/Services/TestService.h @@ -11,7 +11,7 @@ struct Actor; struct TestService { TestService(entt::dispatcher& aDispatcher, World& aWorld, TransportService& aTransport, ImguiService& aImguiService); - ~TestService() noexcept; + ~TestService() noexcept = default; TP_NOCOPYMOVE(TestService); From 2695ed069a1a7de54e2f1ee1c76a016c03b067ca Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 29 Jan 2022 18:56:47 +0100 Subject: [PATCH 11/48] feat: successfully transfer inventory to dummy --- Code/client/Games/Skyrim/Actor.cpp | 16 ++------------- Code/client/Services/Debug/TestService.cpp | 6 +++--- Code/client/Services/TestService.h | 4 ++-- Code/encoding/Structs/Container.cpp | 23 ++++++++++++++++++---- Code/encoding/Structs/Container.h | 3 +++ 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index da4a06400..d1b3a972c 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -366,10 +366,6 @@ Container Actor::GetFullContainer() const noexcept fullContainer.Entries.insert(fullContainer.Entries.end(), minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end()); - std::remove_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [](Container::Entry entry) { - return entry.Count == 0; - }); - spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); // TODO: doesn't filter all duplicates? @@ -380,10 +376,6 @@ void Actor::SetFullContainer(Container& acContainer) noexcept { RemoveAllItems(); - std::sort(acContainer.Entries.begin(), acContainer.Entries.end(), [](Container::Entry lhs, Container::Entry rhs) { - return lhs.Count < rhs.Count; - }); - Container currentContainer = GetFullContainer(); for (auto currentEntry : currentContainer.Entries) { @@ -394,7 +386,6 @@ void Actor::SetFullContainer(Container& acContainer) noexcept if (duplicate != std::end(acContainer.Entries)) { duplicate->Count -= currentEntry.Count; - continue; } else { @@ -404,13 +395,10 @@ void Actor::SetFullContainer(Container& acContainer) noexcept } } - std::remove_if(acContainer.Entries.begin(), acContainer.Entries.end(), [](Container::Entry entry) { - return entry.Count == 0; - }); - for (const Container::Entry& entry : acContainer.Entries) { - AddItem(entry); + if (entry.Count != 0) + AddItem(entry); } } diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index 1c8f65284..e9cd1859b 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -57,7 +57,9 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); - pActor->SetInventory(PlayerCharacter::Get()->GetInventory()); + auto container = PlayerCharacter::Get()->GetFullContainer(); + spdlog::info("Container size: {}", container.Entries.size()); + pActor->SetFullContainer(container); m_actors.emplace_back(pActor); } @@ -75,8 +77,6 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept static std::atomic s_f8Pressed = false; static std::atomic s_f7Pressed = false; - RunDiff(); - if (GetAsyncKeyState(VK_F7)) { if (!s_f7Pressed) diff --git a/Code/client/Services/TestService.h b/Code/client/Services/TestService.h index d2f127235..aac595564 100644 --- a/Code/client/Services/TestService.h +++ b/Code/client/Services/TestService.h @@ -1,5 +1,7 @@ #pragma once +#include + struct World; struct ImguiService; struct UpdateEvent; @@ -7,7 +9,6 @@ struct UpdateEvent; struct TransportService; struct BSAnimationGraphManager; -struct Actor; struct TestService { TestService(entt::dispatcher& aDispatcher, World& aWorld, TransportService& aTransport, ImguiService& aImguiService); @@ -24,7 +25,6 @@ struct TestService private: void PlaceActorInWorld() noexcept; - void RunDiff(); void DrawComponentDebugView(); void DrawPlayerDebugView(); diff --git a/Code/encoding/Structs/Container.cpp b/Code/encoding/Structs/Container.cpp index e3b1ca5d9..b5199d145 100644 --- a/Code/encoding/Structs/Container.cpp +++ b/Code/encoding/Structs/Container.cpp @@ -10,12 +10,13 @@ void Container::Entry::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const n Serialization::WriteFloat(aWriter, ExtraCharge); ExtraEnchantId.Serialize(aWriter); Serialization::WriteVarInt(aWriter, ExtraEnchantCharge); - Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); Serialization::WriteFloat(aWriter, ExtraHealth); ExtraPoisonId.Serialize(aWriter); Serialization::WriteVarInt(aWriter, ExtraPoisonCount); Serialization::WriteVarInt(aWriter, ExtraSoulLevel); Serialization::WriteString(aWriter, ExtraTextDisplayName); + + Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); Serialization::WriteBool(aWriter, ExtraWorn); Serialization::WriteBool(aWriter, ExtraWornLeft); } @@ -27,12 +28,13 @@ void Container::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexc ExtraCharge = Serialization::ReadFloat(aReader); ExtraEnchantId.Deserialize(aReader); ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; - ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); ExtraHealth = Serialization::ReadFloat(aReader); ExtraPoisonId.Deserialize(aReader); ExtraPoisonCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; ExtraSoulLevel = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; ExtraTextDisplayName = Serialization::ReadString(aReader); + + ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); ExtraWorn = Serialization::ReadBool(aReader); ExtraWornLeft = Serialization::ReadBool(aReader); } @@ -47,10 +49,23 @@ bool Container::operator!=(const Container& acRhs) const noexcept return !this->operator==(acRhs); } +bool Container::Entry::operator==(const Container::Entry& acRhs) const noexcept +{ + return BaseId == acRhs.BaseId && + Count == acRhs.Count && + ExtraTextDisplayName == acRhs.ExtraTextDisplayName && + IsExtraDataEquals(acRhs); +} + +bool Container::Entry::operator!=(const Container::Entry& acRhs) const noexcept +{ + return !this->operator==(acRhs); +} + void Container::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { Serialization::WriteVarInt(aWriter, Entries.size()); - for (auto& entry : Entries) + for (const Entry& entry : Entries) { entry.Serialize(aWriter); } @@ -59,7 +74,7 @@ void Container::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept void Container::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept { uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - for (int i = 0; i < count; i++) + for (uint32_t i = 0; i < count; i++) { Entry entry; entry.Deserialize(aReader); diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h index d399c8e3c..667692c4c 100644 --- a/Code/encoding/Structs/Container.h +++ b/Code/encoding/Structs/Container.h @@ -34,6 +34,9 @@ struct Container bool ExtraWorn{}; bool ExtraWornLeft{}; + bool operator==(const Entry& acRhs) const noexcept; + bool operator!=(const Entry& acRhs) const noexcept; + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; From bdedb97875eee7c195c264621874c75062755dbf Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 6 Feb 2022 17:31:32 +0100 Subject: [PATCH 12/48] tweak: minor cleanup --- Code/client/Games/Skyrim/Actor.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index d1b3a972c..f299a1dab 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -258,10 +258,7 @@ Container Actor::GetFullContainer() const noexcept for (BSExtraDataList* pExtraDataList : *pGameEntry->dataList) { if (!pExtraDataList) - { - spdlog::warn("Null ExtraDataList?"); continue; - } Container::Entry innerEntry; innerEntry.BaseId = entry.BaseId; @@ -368,7 +365,6 @@ Container Actor::GetFullContainer() const noexcept spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); - // TODO: doesn't filter all duplicates? return fullContainer; } From e52d8bf567705daeeb004ab4ac81d92d6739fd3d Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 11 Feb 2022 19:57:52 +0100 Subject: [PATCH 13/48] fix: build error --- Code/client/Services/Debug/TestService.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index fe176a901..311980e4c 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -72,8 +72,6 @@ TestService::TestService(entt::dispatcher& aDispatcher, World& aWorld, Transport m_drawImGuiConnection = aImguiService.OnDraw.connect<&TestService::OnDraw>(this); } -TestService::~TestService() noexcept = default; - void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { static std::atomic s_f8Pressed = false; From 6b9264f3ec57fa12136304759e196d2e80ec231a Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Fri, 11 Feb 2022 21:32:46 +0100 Subject: [PATCH 14/48] tweak: more hacks to make it not crash --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 15 ++++++++++++++- Code/client/Services/Debug/TestService.cpp | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 0cc60ca05..42fc81a79 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -163,7 +163,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept BSExtraDataList* pExtraData = nullptr; - if (arEntry.ContainsExtraData()) + if (arEntry.ContainsExtraData() && arEntry.ExtraEnchantId.BaseId == 0) { pExtraData = Memory::Allocate(); pExtraData->data = nullptr; @@ -175,6 +175,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraCharge* pExtraCharge = Memory::Allocate(); *((uint64_t*)pExtraCharge) = 0x141623AB0; + pExtraCharge->next = nullptr; pExtraCharge->fCharge = arEntry.ExtraCharge; pExtraData->Add(ExtraData::Charge, pExtraCharge); } @@ -188,6 +189,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraHealth* pExtraHealth = Memory::Allocate(); *((uint64_t*)pExtraHealth) = 0x141623A50; + pExtraHealth->next = nullptr; pExtraHealth->fHealth = arEntry.ExtraHealth; pExtraData->Add(ExtraData::Health, pExtraHealth); } @@ -203,6 +205,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraPoison* pExtraPoison = Memory::Allocate(); *((uint64_t*)pExtraPoison) = 0x141623E50; + pExtraPoison->next = nullptr; pExtraPoison->pPoison = pPoison; pExtraPoison->uiCount = arEntry.ExtraPoisonCount; pExtraData->Add(ExtraData::Poison, pExtraPoison); @@ -213,6 +216,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraSoul* pExtraSoul = Memory::Allocate(); *((uint64_t*)pExtraSoul) = 0x141627220; + pExtraSoul->next = nullptr; pExtraSoul->cSoul = static_cast(arEntry.ExtraSoulLevel); pExtraData->Add(ExtraData::Soul, pExtraSoul); } @@ -221,6 +225,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraTextDisplayData* pExtraText = Memory::Allocate(); *((uint64_t*)pExtraText) = 0x1416244D0; + pExtraText->next = nullptr; pExtraText->DisplayName = arEntry.ExtraTextDisplayName.c_str(); pExtraData->Add(ExtraData::TextDisplayData, pExtraText); } @@ -229,6 +234,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraWorn* pExtraWorn = Memory::Allocate(); *((uint64_t*)pExtraWorn) = 0x1416239F0; + pExtraWorn->next = nullptr; pExtraData->Add(ExtraData::Worn, pExtraWorn); } @@ -236,10 +242,17 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ExtraWornLeft* pExtraWornLeft = Memory::Allocate(); *((uint64_t*)pExtraWornLeft) = 0x141623A10; + pExtraWornLeft->next = nullptr; pExtraData->Add(ExtraData::WornLeft, pExtraWornLeft); } } + /* + BSExtraDataList* pExtraData2 = pExtraData; + if (pExtraData) + DebugBreak(); + */ + AddObjectToContainer(pObject, pExtraData, arEntry.Count, nullptr); spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, pExtraData ? pExtraData->GetCount() : -1, arEntry.Count); diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index 311980e4c..7037e4334 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -102,7 +102,10 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - PlaceActorInWorld(); + //PlaceActorInWorld(); + Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); + auto container = PlayerCharacter::Get()->GetFullContainer(); + pActor->SetFullContainer(container); } } else From 1ade1221e4513b891cb92ef86e365d2cc18a90a9 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 00:03:17 +0100 Subject: [PATCH 15/48] tweak: properly handle no parsed ExtraData --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 42fc81a79..2d925bf66 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -163,7 +163,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept BSExtraDataList* pExtraData = nullptr; - if (arEntry.ContainsExtraData() && arEntry.ExtraEnchantId.BaseId == 0) + if (arEntry.ContainsExtraData()) { pExtraData = Memory::Allocate(); pExtraData->data = nullptr; @@ -183,6 +183,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept // TODO: deal with temp forms for enchanted items if (arEntry.ExtraEnchantId != 0) { + spdlog::info("Enchanted"); } if (arEntry.ExtraHealth > 0.f) @@ -245,6 +246,12 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraWornLeft->next = nullptr; pExtraData->Add(ExtraData::WornLeft, pExtraWornLeft); } + + if (pExtraData->data == nullptr) + { + Memory::Delete(pExtraData); + pExtraData = nullptr; + } } /* From 1c9be4236a64b2ba4aa4b90d7e37b4ffec4afd10 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 00:10:54 +0100 Subject: [PATCH 16/48] feat: sync enchantments --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 2d925bf66..2034e3c21 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -183,7 +183,18 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept // TODO: deal with temp forms for enchanted items if (arEntry.ExtraEnchantId != 0) { - spdlog::info("Enchanted"); + TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); + + uint32_t enchantId = modSystem.GetGameId(arEntry.ExtraEnchantId); + if (EnchantmentItem* pEnchantment = RTTI_CAST(TESForm::GetById(enchantId), TESForm, EnchantmentItem)) + { + ExtraEnchantment* pExtraEnchantment = Memory::Allocate(); + *((uint64_t*)pExtraEnchantment) = 0x141623E70; + pExtraEnchantment->next = nullptr; + pExtraEnchantment->pEnchantment = pEnchantment; + pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; + pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; + } } if (arEntry.ExtraHealth > 0.f) From 57ed4f13cbf2896b2f8a5f1d550256c8f303e739 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 00:40:50 +0100 Subject: [PATCH 17/48] tweak: decoded EnchantmentItem struct --- Code/client/Games/Magic/MagicSystem.h | 29 +++++++++++++++++++ .../Games/Skyrim/Forms/EnchantmentItem.cpp | 6 ++++ .../Games/Skyrim/Forms/EnchantmentItem.h | 11 +++++++ Code/encoding/Structs/Container.h | 5 ++++ 4 files changed, 51 insertions(+) create mode 100644 Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp diff --git a/Code/client/Games/Magic/MagicSystem.h b/Code/client/Games/Magic/MagicSystem.h index 7bdf76d27..a24cb2df1 100644 --- a/Code/client/Games/Magic/MagicSystem.h +++ b/Code/client/Games/Magic/MagicSystem.h @@ -46,6 +46,35 @@ enum CastingType : int32_t CASTING_COUNT = 0x3, }; +enum Delivery : int32_t +{ + SELF = 0x0, + TOUCH = 0x1, + AIMED = 0x2, + TARGET_ACTOR = 0x3, + TARGET_LOCATION = 0x4, + DELIVERY_COUNT = 0x5, +}; + +enum SpellType : int32_t +{ + SPELL = 0x0, + DISEASE = 0x1, + POWER = 0x2, + LESSER_POWER = 0x3, + ABILITY = 0x4, + POISON = 0x5, + ENCHANTMENT = 0x6, + POTION = 0x7, + WORTCRAFT = 0x8, + LEVELED_SPELL = 0x9, + ADDICTION = 0xA, + VOICE_POWER = 0xB, + STAFF_ENCHANTMENT = 0xC, + SCROLL = 0xD, + SPELL_TYPE_COUNT = 0xE, +}; + } namespace EffectArchetypes diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp new file mode 100644 index 000000000..c16ba750a --- /dev/null +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -0,0 +1,6 @@ +#include "EnchantmentItem.h" + +void EnchantmentItem::Init(const Container::EnchantmentData& aData) +{ + +} diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h index dd6933ecc..7a3c39332 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h @@ -2,7 +2,18 @@ #include "TESForm.h" +#include +#include + struct EnchantmentItem : TESForm { + void Init(const Container::EnchantmentData& aData); + MagicSystem::CastingType eCastingType; + int32_t iChargeOverride; + MagicSystem::Delivery eDelivery; + MagicSystem::SpellType eSpellType; + float fChargeTime; + EnchantmentItem* pBaseEnchantment; + void* pWornRestrictions; }; diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h index 667692c4c..09fd0f575 100644 --- a/Code/encoding/Structs/Container.h +++ b/Code/encoding/Structs/Container.h @@ -9,6 +9,11 @@ using TiltedPhoques::Vector; struct Container { + struct EnchantmentData + { + GameId EnchantmentId{}; + }; + struct Entry { GameId BaseId{}; From 0bc2e1b7f844022d2297b6d0910126365157e067 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 02:30:53 +0100 Subject: [PATCH 18/48] feat: serializing custom enchantments --- Code/client/Games/Skyrim/Actor.cpp | 17 ++++++ Code/client/Games/Skyrim/Forms/BGSListForm.h | 12 ++++ .../Games/Skyrim/Forms/EnchantmentItem.cpp | 55 +++++++++++++++++++ .../Games/Skyrim/Forms/EnchantmentItem.h | 14 ++++- Code/client/Games/Skyrim/Forms/MagicItem.h | 6 +- Code/client/Games/Skyrim/Forms/TESForm.h | 1 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 26 ++++++--- Code/encoding/Structs/Container.h | 29 +++++++++- 8 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 Code/client/Games/Skyrim/Forms/BGSListForm.h diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index c494e71ac..d69b28437 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -280,6 +280,23 @@ Container Actor::GetFullContainer() const noexcept // TODO: enchantments seem to always be temporaries, keep this in mind when trying to apply container // Get base form id of enchantment instead? Probably gonna have to serialize more data modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); + if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) + { + for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) + { + // TODO: null checking and that + Container::EffectItem effect; + effect.Magnitude = pEffectItem->data.fMagnitude; + effect.Area = pEffectItem->data.iArea; + effect.Duration = pEffectItem->data.iDuration; + effect.RawCost = pEffectItem->fRawCost; + modSystem.GetServerModId(pEffectItem->pEffectSetting->formID, effect.EffectId); + innerEntry.EnchantData.Effects.push_back(effect); + } + + uint32_t objectId = modSystem.GetGameId(innerEntry.BaseId); + innerEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; + } innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; } diff --git a/Code/client/Games/Skyrim/Forms/BGSListForm.h b/Code/client/Games/Skyrim/Forms/BGSListForm.h new file mode 100644 index 000000000..8739992f8 --- /dev/null +++ b/Code/client/Games/Skyrim/Forms/BGSListForm.h @@ -0,0 +1,12 @@ +#pragma once + +#include "TESForm.h" + +struct BGSListForm : TESForm +{ + GameArray ArrayOfForms{}; + GameArray* pScriptAddedTempFormA{}; + uint32_t iScriptAddedFormCount{}; +}; + +static_assert(sizeof(BGSListForm) == 0x48); diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index c16ba750a..5ad3d7666 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -1,6 +1,61 @@ #include "EnchantmentItem.h" +#include +#include +#include + +EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData) noexcept +{ + TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); + POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 0x1405C1290 - 0x140000000); + + ModSystem& modSystem = World::Get().GetModSystem(); + + GameArray effects{}; + for (Container::EffectItem effect : aData.Effects) + { + EffectItem* pEffectItem = new EffectItem; + pEffectItem->data.fMagnitude = effect.Magnitude; + pEffectItem->data.iArea = effect.Area; + pEffectItem->data.iDuration = effect.Duration; + pEffectItem->fRawCost = effect.RawCost; + pEffectItem->pEffectSetting = + RTTI_CAST(TESForm::GetById(modSystem.GetGameId(effect.EffectId)), TESForm, EffectSetting); + if (!pEffectItem->pEffectSetting) + spdlog::error("Effect setting not found: {:X}:{:X}", effect.EffectId.ModId, effect.EffectId.BaseId); + } + + EnchantmentItem* pItem = ThisCall(createNewEnchantment, &effects, aData.IsWeapon); + + for (EffectItem* pEffectItem : effects) + delete pEffectItem; + + return pItem; +} + void EnchantmentItem::Init(const Container::EnchantmentData& aData) { + /* + iCostOverride = aData.CostOverride; + iFlags = aData.Flags; + eCastingType = static_cast(aData.CastingType); + iChargeOverride = aData.ChargeOverride; + eDelivery = static_cast(aData.Delivery); + eSpellType = static_cast(aData.SpellType); + fChargeTime = aData.ChargeTime; + + ModSystem& modSystem = World::Get().GetModSystem(); + + TP_ASSERT(aData.BaseEnchantmentId.ModId != 0xFFFFFFFF, "Base enchantment is a temporary!"); + uint32_t baseEnchantId = modSystem.GetGameId(aData.BaseEnchantmentId); + pBaseEnchantment = RTTI_CAST(TESForm::GetById(baseEnchantId), TESForm, EnchantmentItem); + if (!pBaseEnchantment) + spdlog::error("{}: base enchantment not found.", __FUNCTION__); + TP_ASSERT(aData.WornRestrictionsId.ModId != 0xFFFFFFFF, "Worn restrictions is a temporary!"); + uint32_t restrictionsId = modSystem.GetGameId(aData.WornRestrictionsId); + pWornRestrictions = RTTI_CAST(TESForm::GetById(restrictionsId), TESForm, BGSListForm); + if (!pWornRestrictions) + spdlog::error("{}: worn restrictions not found.", __FUNCTION__); + */ } diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h index 7a3c39332..474c45f81 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h @@ -1,19 +1,27 @@ #pragma once -#include "TESForm.h" +#include "MagicItem.h" #include #include +#include "BGSListForm.h" -struct EnchantmentItem : TESForm +struct EnchantmentItem : MagicItem { + static EnchantmentItem* Create(const Container::EnchantmentData& aData) noexcept; + void Init(const Container::EnchantmentData& aData); + int32_t iCostOverride; + int32_t iFlags; MagicSystem::CastingType eCastingType; int32_t iChargeOverride; MagicSystem::Delivery eDelivery; MagicSystem::SpellType eSpellType; float fChargeTime; EnchantmentItem* pBaseEnchantment; - void* pWornRestrictions; + // TODO: use BGSListForm::SaveGame() and BGSListForm::LoadGame()? + BGSListForm* pWornRestrictions; }; + +static_assert(sizeof(EnchantmentItem) == 0xC0); diff --git a/Code/client/Games/Skyrim/Forms/MagicItem.h b/Code/client/Games/Skyrim/Forms/MagicItem.h index d8a8a28c0..6ed2674c7 100644 --- a/Code/client/Games/Skyrim/Forms/MagicItem.h +++ b/Code/client/Games/Skyrim/Forms/MagicItem.h @@ -14,6 +14,8 @@ struct MagicItem : TESBoundObject GameArray listOfEffects; int32_t iHostileCount; EffectSetting* pAVEffectSetting; - uint32_t unk48; - uint32_t unk4C; + uint32_t uiPreloadCount; + void* pPreloadItem; }; + +static_assert(sizeof(MagicItem) == 0x90); diff --git a/Code/client/Games/Skyrim/Forms/TESForm.h b/Code/client/Games/Skyrim/Forms/TESForm.h index 2ff4ec2ab..93e6369d2 100644 --- a/Code/client/Games/Skyrim/Forms/TESForm.h +++ b/Code/client/Games/Skyrim/Forms/TESForm.h @@ -7,6 +7,7 @@ enum class FormType : uint8_t Book = 27, Container = 28, Door = 29, + Weapon = 41, Ammo = 42, Npc = 43, LeveledCharacter = 44, diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 2034e3c21..b45c9ac15 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -18,6 +18,7 @@ #include #include #include +#include TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing); TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); @@ -183,18 +184,25 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept // TODO: deal with temp forms for enchanted items if (arEntry.ExtraEnchantId != 0) { - TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); + //TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); - uint32_t enchantId = modSystem.GetGameId(arEntry.ExtraEnchantId); - if (EnchantmentItem* pEnchantment = RTTI_CAST(TESForm::GetById(enchantId), TESForm, EnchantmentItem)) + EnchantmentItem* pEnchantment = nullptr; + if (arEntry.ExtraEnchantId.ModId == 0xFFFFFFFF) { - ExtraEnchantment* pExtraEnchantment = Memory::Allocate(); - *((uint64_t*)pExtraEnchantment) = 0x141623E70; - pExtraEnchantment->next = nullptr; - pExtraEnchantment->pEnchantment = pEnchantment; - pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; - pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; + pEnchantment = EnchantmentItem::Create(arEntry.EnchantData); } + else + { + uint32_t enchantId = modSystem.GetGameId(arEntry.ExtraEnchantId); + pEnchantment = RTTI_CAST(TESForm::GetById(enchantId), TESForm, EnchantmentItem); + } + + ExtraEnchantment* pExtraEnchantment = Memory::Allocate(); + *((uint64_t*)pExtraEnchantment) = 0x141623E70; + pExtraEnchantment->next = nullptr; + pExtraEnchantment->pEnchantment = pEnchantment; + pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; + pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; } if (arEntry.ExtraHealth > 0.f) diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h index 09fd0f575..7f117f5e1 100644 --- a/Code/encoding/Structs/Container.h +++ b/Code/encoding/Structs/Container.h @@ -9,9 +9,35 @@ using TiltedPhoques::Vector; struct Container { + struct EffectItem + { + float Magnitude{}; + int32_t Area{}; + int32_t Duration{}; + float RawCost{}; + GameId EffectId{}; + }; + struct EnchantmentData { - GameId EnchantmentId{}; + bool IsWeapon{}; + Vector Effects{}; + + /* + int32_t CostOverride{}; + int32_t Flags{}; + int32_t CastingType{}; + int32_t ChargeOverride{}; + int32_t Delivery{}; + int32_t SpellType{}; + float ChargeTime{}; + // TODO: try with IDs for base and worn restrictions first + // place asserts to see if these are ever temporary + // should probably support it anyway though + GameId BaseEnchantmentId{}; + GameId WornRestrictionsId{}; + //Vector WornRestrictions{}; + */ }; struct Entry @@ -26,6 +52,7 @@ struct Container GameId ExtraEnchantId{}; uint16_t ExtraEnchantCharge{}; bool ExtraEnchantRemoveUnequip{}; + EnchantmentData EnchantData{}; float ExtraHealth{}; From bfdc267c3c3e62dbc9f064f8735e7a6069a4a1bf Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 03:41:53 +0100 Subject: [PATCH 19/48] fix: object, not a ptr --- .../Games/Skyrim/Forms/EnchantmentItem.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index 5ad3d7666..dcdfbb404 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -6,30 +6,32 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData) noexcept { - TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); + TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 0x1405C1290 - 0x140000000); ModSystem& modSystem = World::Get().GetModSystem(); - GameArray effects{}; - for (Container::EffectItem effect : aData.Effects) + GameArray effects{}; + effects.Resize(aData.Effects.size()); + for (int i = 0; i < aData.Effects.size(); i++) { - EffectItem* pEffectItem = new EffectItem; - pEffectItem->data.fMagnitude = effect.Magnitude; - pEffectItem->data.iArea = effect.Area; - pEffectItem->data.iDuration = effect.Duration; - pEffectItem->fRawCost = effect.RawCost; - pEffectItem->pEffectSetting = + Container::EffectItem effect = aData.Effects[i]; + + EffectItem effectItem{}; + effectItem.data.fMagnitude = effect.Magnitude; + effectItem.data.iArea = effect.Area; + effectItem.data.iDuration = effect.Duration; + effectItem.fRawCost = effect.RawCost; + effectItem.pEffectSetting = RTTI_CAST(TESForm::GetById(modSystem.GetGameId(effect.EffectId)), TESForm, EffectSetting); - if (!pEffectItem->pEffectSetting) + if (!effectItem.pEffectSetting) spdlog::error("Effect setting not found: {:X}:{:X}", effect.EffectId.ModId, effect.EffectId.BaseId); + + effects[i] = effectItem; } EnchantmentItem* pItem = ThisCall(createNewEnchantment, &effects, aData.IsWeapon); - for (EffectItem* pEffectItem : effects) - delete pEffectItem; - return pItem; } From ff9adeb74af8dfab937aa6a3eb334e6b66d5f122 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Feb 2022 04:14:59 +0100 Subject: [PATCH 20/48] fix: forgot TESCondition --- Code/client/Games/Skyrim/Components/TESCondition.h | 6 ++++++ Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp | 2 ++ Code/client/Games/Skyrim/Magic/EffectItem.h | 3 +++ 3 files changed, 11 insertions(+) create mode 100644 Code/client/Games/Skyrim/Components/TESCondition.h diff --git a/Code/client/Games/Skyrim/Components/TESCondition.h b/Code/client/Games/Skyrim/Components/TESCondition.h new file mode 100644 index 000000000..331880ce5 --- /dev/null +++ b/Code/client/Games/Skyrim/Components/TESCondition.h @@ -0,0 +1,6 @@ +#pragma once + +struct TESCondition +{ + void* pHead{}; +}; diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index dcdfbb404..8f63f40f1 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -27,6 +27,8 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData if (!effectItem.pEffectSetting) spdlog::error("Effect setting not found: {:X}:{:X}", effect.EffectId.ModId, effect.EffectId.BaseId); + // TODO: TESCondition + effects[i] = effectItem; } diff --git a/Code/client/Games/Skyrim/Magic/EffectItem.h b/Code/client/Games/Skyrim/Magic/EffectItem.h index 3975d0c45..8c8086595 100644 --- a/Code/client/Games/Skyrim/Magic/EffectItem.h +++ b/Code/client/Games/Skyrim/Magic/EffectItem.h @@ -1,5 +1,7 @@ #pragma once +#include + struct EffectSetting; struct EffectItemData @@ -15,4 +17,5 @@ struct EffectItem uint32_t padC; EffectSetting* pEffectSetting; float fRawCost; + TESCondition Condition{}; }; From 001f1761e541988861de788d846d7091239fe5eb Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 13 Feb 2022 23:59:05 +0100 Subject: [PATCH 21/48] tweak: properly send extra text stuff --- Code/client/Games/Skyrim/Actor.cpp | 2 +- .../Games/Skyrim/ExtraData/ExtraTextDisplayData.h | 6 +++++- Code/client/Games/Skyrim/TESObjectREFR.cpp | 10 +++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index d69b28437..e05d8e81e 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -323,7 +323,7 @@ Container Actor::GetFullContainer() const noexcept if (pExtraTextDisplayData->DisplayName) innerEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; else - innerEntry.ExtraTextDisplayName = "NULL DISPLAY NAME"; + innerEntry.ExtraTextDisplayName = ""; } innerEntry.ExtraWorn = pExtraDataList->Contains(ExtraData::Worn); diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h index f0e7dc3e7..d4cac8ca7 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h @@ -8,7 +8,11 @@ struct ExtraTextDisplayData : BSExtraData inline static constexpr auto eExtraData = ExtraData::TextDisplayData; BSFixedString DisplayName{}; - uint8_t pad[0x20]{}; + TESForm* pDisplayNameText{}; + TESQuest* pOwnerQuest{}; + int32_t iOwnerInstance{}; + float fTemperFactor{}; + uint16_t usCustomNameLength{}; // TODO: implement the rest when i dont feel lazy /* diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index b45c9ac15..e19d9e55f 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -158,7 +158,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept TESBoundObject* pObject = RTTI_CAST(TESForm::GetById(objectId), TESForm, TESBoundObject); if (!pObject) { - spdlog::warn("{}: Object to add not found.", __FUNCTION__); + spdlog::warn("{}: Object to add not found, {:X}:{:X}.", __FUNCTION__, arEntry.BaseId.ModId, arEntry.BaseId.BaseId); return; } @@ -186,9 +186,13 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { //TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); + if (arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF) + { + EnchantmentItem* pEnchantment = nullptr; if (arEntry.ExtraEnchantId.ModId == 0xFFFFFFFF) { + spdlog::warn("Creating EnchantmentItem for {:X}:{:X}", arEntry.BaseId.ModId, arEntry.BaseId.BaseId); pEnchantment = EnchantmentItem::Create(arEntry.EnchantData); } else @@ -203,6 +207,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraEnchantment->pEnchantment = pEnchantment; pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; + } } if (arEntry.ExtraHealth > 0.f) @@ -247,6 +252,9 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept *((uint64_t*)pExtraText) = 0x1416244D0; pExtraText->next = nullptr; pExtraText->DisplayName = arEntry.ExtraTextDisplayName.c_str(); + pExtraText->usCustomNameLength = arEntry.ExtraTextDisplayName.length(); + pExtraText->iOwnerInstance = -2; + pExtraText->fTemperFactor = 1.0F; pExtraData->Add(ExtraData::TextDisplayData, pExtraText); } From 8a37d7b92e01ba03fda5be8a46a37b2afe2e220b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Feb 2022 20:11:08 +0100 Subject: [PATCH 22/48] tweak: improved enchantment sync --- .../Skyrim/ExtraData/ExtraTextDisplayData.h | 14 ++++++++++- .../Games/Skyrim/Forms/EnchantmentItem.cpp | 23 +++++++++++++++++++ Code/client/Games/Skyrim/TESObjectREFR.cpp | 7 +++--- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h index d4cac8ca7..9626edf8f 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraTextDisplayData.h @@ -2,13 +2,25 @@ #include #include +#include + +struct BGSMessage : TESForm, TESFullName, TESDescription +{ + void* pIcon; + void* pOwnerQuest; + uint8_t menuButtons[0x10]; + uint32_t uiFlags; + uint32_t uiDisplayTime; +}; + +static_assert(sizeof(BGSMessage) == 0x68); struct ExtraTextDisplayData : BSExtraData { inline static constexpr auto eExtraData = ExtraData::TextDisplayData; BSFixedString DisplayName{}; - TESForm* pDisplayNameText{}; + BGSMessage* pDisplayNameText{}; TESQuest* pOwnerQuest{}; int32_t iOwnerInstance{}; float fTemperFactor{}; diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index 8f63f40f1..b2c824b66 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -6,8 +6,15 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData) noexcept { + /* TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 0x1405C1290 - 0x140000000); + */ + + TP_THIS_FUNCTION(TAddWeaponEnchantment, EnchantmentItem*, void, GameArray*); + POINTER_SKYRIMSE(TAddWeaponEnchantment, addWeaponEnchantment, 0x1405C0370 - 0x140000000); + TP_THIS_FUNCTION(TAddArmorEnchantment, EnchantmentItem*, void, GameArray*); + POINTER_SKYRIMSE(TAddArmorEnchantment, addArmorEnchantment, 0x1405C0410 - 0x140000000); ModSystem& modSystem = World::Get().GetModSystem(); @@ -32,7 +39,23 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData effects[i] = effectItem; } + /* EnchantmentItem* pItem = ThisCall(createNewEnchantment, &effects, aData.IsWeapon); + */ + + POINTER_SKYRIMSE(void*, pObjManager, 0x141F592E8 - 0x140000000); + + void* objManager = *pObjManager.Get(); + + EnchantmentItem* pItem = nullptr; + if (aData.IsWeapon) + { + pItem = ThisCall(addWeaponEnchantment, objManager, &effects); + } + else + { + pItem = ThisCall(addArmorEnchantment, objManager, &effects); + } return pItem; } diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index e19d9e55f..13d6d1e04 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -186,9 +186,6 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { //TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); - if (arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF) - { - EnchantmentItem* pEnchantment = nullptr; if (arEntry.ExtraEnchantId.ModId == 0xFFFFFFFF) { @@ -207,7 +204,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraEnchantment->pEnchantment = pEnchantment; pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; - } + pExtraData->Add(ExtraData::Enchantment, pExtraEnchantment); } if (arEntry.ExtraHealth > 0.f) @@ -246,6 +243,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraData->Add(ExtraData::Soul, pExtraSoul); } + /* if (!arEntry.ExtraTextDisplayName.empty()) { ExtraTextDisplayData* pExtraText = Memory::Allocate(); @@ -257,6 +255,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraText->fTemperFactor = 1.0F; pExtraData->Add(ExtraData::TextDisplayData, pExtraText); } + */ if (arEntry.ExtraWorn) { From ea8a9d517545b21e0c2ec0117ecfc9370c6fd657 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Feb 2022 22:12:34 +0100 Subject: [PATCH 23/48] tweak: dramatically cleaned up TESObjectREFR::AddItem() --- Code/client/Games/ExtraData.cpp | 10 +- Code/client/Games/ExtraData.h | 29 ----- Code/client/Games/ExtraDataList.cpp | 44 +++++++ Code/client/Games/ExtraDataList.h | 45 +++++++ Code/client/Games/Fallout4/EquipManager.h | 2 +- Code/client/Games/Fallout4/TESObjectREFR.cpp | 4 +- Code/client/Games/Fallout4/TESObjectREFR.h | 4 +- Code/client/Games/References.cpp | 4 +- Code/client/Games/Skyrim/Actor.cpp | 6 +- Code/client/Games/Skyrim/EquipManager.cpp | 12 +- Code/client/Games/Skyrim/EquipManager.h | 6 +- .../Skyrim/ExtraData/ExtraContainerChanges.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 116 +++++++----------- Code/client/Games/Skyrim/TESObjectREFR.h | 6 +- 14 files changed, 158 insertions(+), 132 deletions(-) create mode 100644 Code/client/Games/ExtraDataList.cpp create mode 100644 Code/client/Games/ExtraDataList.h diff --git a/Code/client/Games/ExtraData.cpp b/Code/client/Games/ExtraData.cpp index 4501f126a..9ea676d4d 100644 --- a/Code/client/Games/ExtraData.cpp +++ b/Code/client/Games/ExtraData.cpp @@ -2,7 +2,7 @@ #include -bool BSExtraDataList::Contains(ExtraData aType) const +bool ExtraDataList::Contains(ExtraData aType) const { if(bitfield) { @@ -17,7 +17,7 @@ bool BSExtraDataList::Contains(ExtraData aType) const return false; } -BSExtraData* BSExtraDataList::GetByType(ExtraData aType) const +BSExtraData* ExtraDataList::GetByType(ExtraData aType) const { BSScopedLock _(lock); @@ -37,7 +37,7 @@ BSExtraData* BSExtraDataList::GetByType(ExtraData aType) const return pEntry; } -bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) +bool ExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) { if (Contains(aType)) return false; @@ -53,7 +53,7 @@ bool BSExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) return true; } -uint32_t BSExtraDataList::GetCount() const +uint32_t ExtraDataList::GetCount() const { uint32_t count = 0; @@ -67,7 +67,7 @@ uint32_t BSExtraDataList::GetCount() const return count; } -void BSExtraDataList::SetType(ExtraData aType, bool aClear) +void ExtraDataList::SetType(ExtraData aType, bool aClear) { uint32_t index = static_cast(aType) >> 3; uint8_t bitmask = 1 << (static_cast(aType) % 8); diff --git a/Code/client/Games/ExtraData.h b/Code/client/Games/ExtraData.h index 154d636d7..f1fb78b80 100644 --- a/Code/client/Games/ExtraData.h +++ b/Code/client/Games/ExtraData.h @@ -28,32 +28,3 @@ static_assert(offsetof(BSExtraData, form) == 0x18); static_assert(sizeof(BSExtraData) == 0x20); #endif -struct BSExtraDataList -{ - bool Contains(ExtraData aType) const; - void Set(ExtraData aType, bool aSet); - - bool Add(ExtraData aType, BSExtraData* apNewData); - bool Remove(ExtraData aType, BSExtraData* apNewData); - - uint32_t GetCount() const; - - void SetType(ExtraData aType, bool aClear); - - BSExtraData* GetByType(ExtraData type) const; -#if TP_FALLOUT4 - void* unk0; -#endif - BSExtraData* data = nullptr; - - struct Bitfield - { - uint8_t data[0x18]; - }; -#if TP_FALLOUT4 - void* unk10; -#endif - - Bitfield* bitfield{}; - mutable BSRecursiveLock lock{}; -}; diff --git a/Code/client/Games/ExtraDataList.cpp b/Code/client/Games/ExtraDataList.cpp new file mode 100644 index 000000000..1a39e809d --- /dev/null +++ b/Code/client/Games/ExtraDataList.cpp @@ -0,0 +1,44 @@ +#include "ExtraDataList.h" + +void ExtraDataList::SetSoulData(SOUL_LEVEL aSoulLevel) noexcept +{ + TP_THIS_FUNCTION(TSetSoulData, void, ExtraDataList, SOUL_LEVEL aSoulLevel); + POINTER_SKYRIMSE(TSetSoulData, setSoulData, 0x14011AF60 - 0x140000000); + ThisCall(setSoulData, this, aSoulLevel); +} + +void ExtraDataList::SetChargeData(float aCharge) noexcept +{ + TP_THIS_FUNCTION(TSetChargeData, void, ExtraDataList, float aCharge); + POINTER_SKYRIMSE(TSetChargeData, setChargeData, 0x14011AF60 - 0x140000000); + ThisCall(setChargeData, this, aCharge); +} + +void ExtraDataList::SetWorn(bool aWornLeft) noexcept +{ + // TODO: what's this bool? seems to be true always except for one instance + TP_THIS_FUNCTION(TSetWornData, void, ExtraDataList, bool aUnk1, bool aWornLeft); + POINTER_SKYRIMSE(TSetWornData, setWornData, 0x14011A5D0 - 0x140000000); + ThisCall(setWornData, this, true, aWornLeft); +} + +void ExtraDataList::SetPoison(AlchemyItem* apItem, uint32_t aCount) noexcept +{ + TP_THIS_FUNCTION(TSetPoison, void, ExtraDataList, AlchemyItem* apItem, uint32_t aCount); + POINTER_SKYRIMSE(TSetPoison, setPoison, 0x140124160 - 0x140000000); + ThisCall(setPoison, this, apItem, aCount); +} + +void ExtraDataList::SetHealth(float aHealth) noexcept +{ + TP_THIS_FUNCTION(TSetHealth, void, ExtraDataList, float aHealth); + POINTER_SKYRIMSE(TSetHealth, setHealth, 0x14011AB30 - 0x140000000); + ThisCall(setHealth, this, aHealth); +} + +void ExtraDataList::SetEnchantmentData(EnchantmentItem* apItem, uint16_t aCharge, bool aRemoveOnUnequip) noexcept +{ + TP_THIS_FUNCTION(TSetEnchantmentData, void, ExtraDataList, EnchantmentItem* apItem, uint16_t aCharge, bool aRemoveOnUnequip); + POINTER_SKYRIMSE(TSetEnchantmentData, setEnchantmentData, 0x14012D670 - 0x140000000); + ThisCall(setEnchantmentData, this, apItem, aCharge, aRemoveOnUnequip); +} diff --git a/Code/client/Games/ExtraDataList.h b/Code/client/Games/ExtraDataList.h new file mode 100644 index 000000000..af7b67dc7 --- /dev/null +++ b/Code/client/Games/ExtraDataList.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include +#include + +struct ExtraDataList +{ + bool Contains(ExtraData aType) const; + void Set(ExtraData aType, bool aSet); + + bool Add(ExtraData aType, BSExtraData* apNewData); + bool Remove(ExtraData aType, BSExtraData* apNewData); + + uint32_t GetCount() const; + + void SetType(ExtraData aType, bool aClear); + BSExtraData* GetByType(ExtraData type) const; + + void SetSoulData(SOUL_LEVEL aSoulLevel) noexcept; + void SetChargeData(float aCharge) noexcept; + void SetWorn(bool aWornLeft) noexcept; + void SetPoison(AlchemyItem* apItem, uint32_t aCount) noexcept; + void SetHealth(float aHealth) noexcept; + void SetEnchantmentData(EnchantmentItem* apItem, uint16_t aCharge, bool aRemoveOnUnequip) noexcept; + +#if TP_FALLOUT4 + void* unk0; +#endif + BSExtraData* data = nullptr; + + struct Bitfield + { + uint8_t data[0x18]; + }; +#if TP_FALLOUT4 + void* unk10; +#endif + + Bitfield* bitfield{}; + mutable BSRecursiveLock lock{}; +}; diff --git a/Code/client/Games/Fallout4/EquipManager.h b/Code/client/Games/Fallout4/EquipManager.h index f08d1e1c5..09cce1924 100644 --- a/Code/client/Games/Fallout4/EquipManager.h +++ b/Code/client/Games/Fallout4/EquipManager.h @@ -1,7 +1,7 @@ #pragma once struct TESForm; -struct BSExtraDataList; +struct ExtraDataList; struct Actor; struct BGSObjectInstance; struct BGSEquipSlot; diff --git a/Code/client/Games/Fallout4/TESObjectREFR.cpp b/Code/client/Games/Fallout4/TESObjectREFR.cpp index bc8f125c2..2de3e8f9a 100644 --- a/Code/client/Games/Fallout4/TESObjectREFR.cpp +++ b/Code/client/Games/Fallout4/TESObjectREFR.cpp @@ -5,7 +5,7 @@ #include TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, TESBoundObject* apObjectToGet, int32_t aCount, bool aDefaultProcessing, bool aFromScript, bool aIsLooping); -TP_THIS_FUNCTION(TAddInventoryItem, void, TESObjectREFR, TESBoundObject* apObject, BSExtraDataList* apExtraData, uint32_t aCount, +TP_THIS_FUNCTION(TAddInventoryItem, void, TESObjectREFR, TESBoundObject* apObject, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldContainer, void* apUnk1, void* apUnk2); TP_THIS_FUNCTION(TRemoveInventoryItem, uint32_t*, TESObjectREFR, uint32_t* apUnk1, void* apUnk2); @@ -79,7 +79,7 @@ void TP_MAKE_THISCALL(HookActivate, TESObjectREFR, TESObjectREFR* apActivator, T return ThisCall(RealActivate, apThis, apActivator, apObjectToGet, aCount, aDefaultProcessing, aFromScript, aIsLooping); } -void TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apObject, BSExtraDataList* apExtraData, +void TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apObject, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldContainer, void* apUnk1, void* apUnk2) { World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); diff --git a/Code/client/Games/Fallout4/TESObjectREFR.h b/Code/client/Games/Fallout4/TESObjectREFR.h index 4ae98d080..741e2ce7f 100644 --- a/Code/client/Games/Fallout4/TESObjectREFR.h +++ b/Code/client/Games/Fallout4/TESObjectREFR.h @@ -147,7 +147,7 @@ struct TESObjectREFR : TESForm uint32_t GetCellId() const noexcept; struct TESWorldSpace* GetWorldSpace() const noexcept; - BSExtraDataList* GetExtraDataList() noexcept; + ExtraDataList* GetExtraDataList() noexcept; ActorValueInfo* GetActorValueInfo(uint32_t aId) const noexcept; Lock* GetLock() noexcept; const BGSEquipSlot* GetEquipSlot(uint32_t uiIndex) const noexcept; @@ -190,7 +190,7 @@ struct TESObjectREFR : TESForm void* unkE8; void* loadState; void* inventory; - BSExtraDataList* extraData; + ExtraDataList* extraData; uint64_t unk108; }; diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index 0498666eb..b9c50eecf 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -275,7 +275,7 @@ TESWorldSpace* TESObjectREFR::GetWorldSpace() const noexcept return nullptr; } -BSExtraDataList* TESObjectREFR::GetExtraDataList() noexcept +ExtraDataList* TESObjectREFR::GetExtraDataList() noexcept { #if TP_FALLOUT4 return extraData; @@ -414,7 +414,7 @@ GamePtr Actor::Create(TESNPC* apBaseForm) noexcept void Actor::SetLevelMod(uint32_t aLevel) noexcept { - TP_THIS_FUNCTION(TActorSetLevelMod, void, BSExtraDataList, uint32_t); + TP_THIS_FUNCTION(TActorSetLevelMod, void, ExtraDataList, uint32_t); POINTER_SKYRIMSE(TActorSetLevelMod, realSetLevelMod, 0x1401238E0 - 0x140000000); POINTER_FALLOUT4(TActorSetLevelMod, realSetLevelMod, 0x14008F660 - 0x140000000); diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index e05d8e81e..d6c8e4112 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -89,7 +89,7 @@ void Actor::Save_Reversed(const uint32_t aChangeFlags, Buffer::Writer& aWriter) TP_THIS_FUNCTION(TCharacterConstructor, Actor*, Actor); TP_THIS_FUNCTION(TCharacterConstructor2, Actor*, Actor, uint8_t aUnk); TP_THIS_FUNCTION(TCharacterDestructor, Actor*, Actor); -TP_THIS_FUNCTION(TAddInventoryItem, void, Actor, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); +TP_THIS_FUNCTION(TAddInventoryItem, void, Actor, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); TP_THIS_FUNCTION(TPickUpItem, void*, Actor, TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2); using TGetLocation = TESForm *(TESForm *); @@ -255,7 +255,7 @@ Container Actor::GetFullContainer() const noexcept modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); entry.Count = pGameEntry->count; - for (BSExtraDataList* pExtraDataList : *pGameEntry->dataList) + for (ExtraDataList* pExtraDataList : *pGameEntry->dataList) { if (!pExtraDataList) continue; @@ -825,7 +825,7 @@ void* TP_MAKE_THISCALL(HookRegenAttributes, Actor, int aId, float aRegenValue) return ThisCall(RealRegenAttributes, apThis, aId, aRegenValue); } -void TP_MAKE_THISCALL(HookAddInventoryItem, Actor, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) +void TP_MAKE_THISCALL(HookAddInventoryItem, Actor, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) { World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner); diff --git a/Code/client/Games/Skyrim/EquipManager.cpp b/Code/client/Games/Skyrim/EquipManager.cpp index 5948cbc2c..e8c06e7c4 100644 --- a/Code/client/Games/Skyrim/EquipManager.cpp +++ b/Code/client/Games/Skyrim/EquipManager.cpp @@ -14,7 +14,7 @@ struct EquipData { - BSExtraDataList* extraDataList; // 0 + ExtraDataList* extraDataList; // 0 int count; // 8 int padC; // C struct BGSEquipSlot* slot; // 10 @@ -28,7 +28,7 @@ struct EquipData struct UnEquipData { - BSExtraDataList* extraDataList; // 0 + ExtraDataList* extraDataList; // 0 int count; // 8 int padC; // C struct BGSEquipSlot* slot; // 10 @@ -108,9 +108,9 @@ void* EquipManager::UnEquipShout(Actor* apActor, TESForm* apShout) } -void* EquipManager::Equip(Actor* apActor, TESForm* apItem, BSExtraDataList* apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3) +void* EquipManager::Equip(Actor* apActor, TESForm* apItem, ExtraDataList* apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3) { - TP_THIS_FUNCTION(TEquipInternal, void*, EquipManager, Actor * apActor, TESForm * apItem, BSExtraDataList * apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3); + TP_THIS_FUNCTION(TEquipInternal, void*, EquipManager, Actor * apActor, TESForm * apItem, ExtraDataList * apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3); POINTER_SKYRIMSE(TEquipInternal, s_equipFunc, 0x14065D480 - 0x140000000); ScopedEquipOverride equipOverride; @@ -120,9 +120,9 @@ void* EquipManager::Equip(Actor* apActor, TESForm* apItem, BSExtraDataList* apEx return result; } -void* EquipManager::UnEquip(Actor* apActor, TESForm* apItem, BSExtraDataList* apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4) +void* EquipManager::UnEquip(Actor* apActor, TESForm* apItem, ExtraDataList* apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4) { - TP_THIS_FUNCTION(TUnEquipInternal, void*, EquipManager, Actor * apActor, TESForm * apItem, BSExtraDataList * apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4); + TP_THIS_FUNCTION(TUnEquipInternal, void*, EquipManager, Actor * apActor, TESForm * apItem, ExtraDataList * apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4); POINTER_SKYRIMSE(TUnEquipInternal, s_unequipFunc, 0x14065DC70 - 0x140000000); ScopedEquipOverride equipOverride; diff --git a/Code/client/Games/Skyrim/EquipManager.h b/Code/client/Games/Skyrim/EquipManager.h index a5c930236..85af7d349 100644 --- a/Code/client/Games/Skyrim/EquipManager.h +++ b/Code/client/Games/Skyrim/EquipManager.h @@ -1,7 +1,7 @@ #pragma once struct TESForm; -struct BSExtraDataList; +struct ExtraDataList; struct Actor; struct EquipManager @@ -12,6 +12,6 @@ struct EquipManager void* UnEquipSpell(Actor* apActor, TESForm* apSpell, uint32_t aSlotId); void* EquipShout(Actor* apActor, TESForm* apShout); void* UnEquipShout(Actor* apActor, TESForm* apShout); - void* Equip(Actor* apActor, TESForm* apItem, BSExtraDataList* apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3); - void* UnEquip(Actor* apActor, TESForm* apItem, BSExtraDataList* apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4); + void* Equip(Actor* apActor, TESForm* apItem, ExtraDataList* apExtraDataList, int aCount, void* aSlot, bool aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3); + void* UnEquip(Actor* apActor, TESForm* apItem, ExtraDataList* apExtraDataList, int aCount, void* aSlot, int aUnk1, bool aPreventEquip, bool aUnk2, bool aUnk3, void* aUnk4); }; diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h b/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h index 64c5a20d0..299d881e5 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h @@ -14,7 +14,7 @@ struct ExtraContainerChanges : BSExtraData struct Entry { TESForm* form; - GameList* dataList; + GameList* dataList; int32_t count; uint8_t pad[8]; }; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 13d6d1e04..836b606ad 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -21,8 +22,8 @@ #include TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing); -TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); -TP_THIS_FUNCTION(TRemoveInventoryItem, void*, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, BSExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3); +TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); +TP_THIS_FUNCTION(TRemoveInventoryItem, void*, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, ExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3); TP_THIS_FUNCTION(TPlayAnimationAndWait, bool, void, uint32_t auiStackID, TESObjectREFR* apSelf, BSFixedString* apAnimation, BSFixedString* apEventName); TP_THIS_FUNCTION(TPlayAnimation, bool, void, uint32_t auiStackID, TESObjectREFR* apSelf, BSFixedString* apEventName); @@ -162,34 +163,26 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept return; } - BSExtraDataList* pExtraData = nullptr; + ExtraDataList* pExtraDataList = nullptr; if (arEntry.ContainsExtraData()) { - pExtraData = Memory::Allocate(); - pExtraData->data = nullptr; - pExtraData->lock.m_counter = pExtraData->lock.m_tid = 0; - pExtraData->bitfield = Memory::Allocate(); - memset(pExtraData->bitfield, 0, 0x18); + pExtraDataList = Memory::Allocate(); + pExtraDataList->data = nullptr; + pExtraDataList->lock.m_counter = pExtraDataList->lock.m_tid = 0; + pExtraDataList->bitfield = Memory::Allocate(); + memset(pExtraDataList->bitfield, 0, 0x18); if (arEntry.ExtraCharge > 0.f) { - ExtraCharge* pExtraCharge = Memory::Allocate(); - *((uint64_t*)pExtraCharge) = 0x141623AB0; - pExtraCharge->next = nullptr; - pExtraCharge->fCharge = arEntry.ExtraCharge; - pExtraData->Add(ExtraData::Charge, pExtraCharge); + pExtraDataList->SetChargeData(arEntry.ExtraCharge); } - // TODO: deal with temp forms for enchanted items if (arEntry.ExtraEnchantId != 0) { - //TP_ASSERT(arEntry.ExtraEnchantId.ModId != 0xFFFFFFFF, "Enchantment is sent as temp!"); - EnchantmentItem* pEnchantment = nullptr; if (arEntry.ExtraEnchantId.ModId == 0xFFFFFFFF) { - spdlog::warn("Creating EnchantmentItem for {:X}:{:X}", arEntry.BaseId.ModId, arEntry.BaseId.BaseId); pEnchantment = EnchantmentItem::Create(arEntry.EnchantData); } else @@ -198,51 +191,46 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pEnchantment = RTTI_CAST(TESForm::GetById(enchantId), TESForm, EnchantmentItem); } - ExtraEnchantment* pExtraEnchantment = Memory::Allocate(); - *((uint64_t*)pExtraEnchantment) = 0x141623E70; - pExtraEnchantment->next = nullptr; - pExtraEnchantment->pEnchantment = pEnchantment; - pExtraEnchantment->usCharge = arEntry.ExtraEnchantCharge; - pExtraEnchantment->bRemoveOnUnequip = arEntry.ExtraEnchantRemoveUnequip; - pExtraData->Add(ExtraData::Enchantment, pExtraEnchantment); - } + TP_ASSERT(pEnchantment, "No Enchantment created or found."); - if (arEntry.ExtraHealth > 0.f) - { - ExtraHealth* pExtraHealth = Memory::Allocate(); - *((uint64_t*)pExtraHealth) = 0x141623A50; - pExtraHealth->next = nullptr; - pExtraHealth->fHealth = arEntry.ExtraHealth; - pExtraData->Add(ExtraData::Health, pExtraHealth); + pExtraDataList->SetEnchantmentData(pEnchantment, arEntry.ExtraEnchantCharge, + arEntry.ExtraEnchantRemoveUnequip); } - // TODO: does poison have the same temp problem as enchants? - // put an assert for now if (arEntry.ExtraPoisonId != 0) { + // TODO: does poison have the same temp problem as enchants? + // doesn't seem to be the case, there are only like 3 poisons, and no custom ones TP_ASSERT(arEntry.ExtraPoisonId.ModId != 0xFFFFFFFF, "Poison is sent as temp!"); uint32_t poisonId = modSystem.GetGameId(arEntry.ExtraPoisonId); if (AlchemyItem* pPoison = RTTI_CAST(TESForm::GetById(poisonId), TESForm, AlchemyItem)) { - ExtraPoison* pExtraPoison = Memory::Allocate(); - *((uint64_t*)pExtraPoison) = 0x141623E50; - pExtraPoison->next = nullptr; - pExtraPoison->pPoison = pPoison; - pExtraPoison->uiCount = arEntry.ExtraPoisonCount; - pExtraData->Add(ExtraData::Poison, pExtraPoison); + pExtraDataList->SetPoison(pPoison, arEntry.ExtraPoisonCount); } } + if (arEntry.ExtraHealth > 0.f) + { + pExtraDataList->SetHealth(arEntry.ExtraHealth); + } + if (arEntry.ExtraSoulLevel > 0 && arEntry.ExtraSoulLevel <= 5) { - ExtraSoul* pExtraSoul = Memory::Allocate(); - *((uint64_t*)pExtraSoul) = 0x141627220; - pExtraSoul->next = nullptr; - pExtraSoul->cSoul = static_cast(arEntry.ExtraSoulLevel); - pExtraData->Add(ExtraData::Soul, pExtraSoul); + pExtraDataList->SetSoulData(static_cast(arEntry.ExtraSoulLevel)); + } + + if (arEntry.ExtraWorn) + { + pExtraDataList->SetWorn(false); } + if (arEntry.ExtraWornLeft) + { + pExtraDataList->SetWorn(true); + } + + // TODO: this is causing crashes /* if (!arEntry.ExtraTextDisplayName.empty()) { @@ -253,42 +241,20 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept pExtraText->usCustomNameLength = arEntry.ExtraTextDisplayName.length(); pExtraText->iOwnerInstance = -2; pExtraText->fTemperFactor = 1.0F; - pExtraData->Add(ExtraData::TextDisplayData, pExtraText); + pExtraDataList->Add(ExtraData::TextDisplayData, pExtraText); } */ - if (arEntry.ExtraWorn) - { - ExtraWorn* pExtraWorn = Memory::Allocate(); - *((uint64_t*)pExtraWorn) = 0x1416239F0; - pExtraWorn->next = nullptr; - pExtraData->Add(ExtraData::Worn, pExtraWorn); - } - - if (arEntry.ExtraWornLeft) - { - ExtraWornLeft* pExtraWornLeft = Memory::Allocate(); - *((uint64_t*)pExtraWornLeft) = 0x141623A10; - pExtraWornLeft->next = nullptr; - pExtraData->Add(ExtraData::WornLeft, pExtraWornLeft); - } - - if (pExtraData->data == nullptr) + if (pExtraDataList->data == nullptr) { - Memory::Delete(pExtraData); - pExtraData = nullptr; + Memory::Delete(pExtraDataList); + pExtraDataList = nullptr; } } - /* - BSExtraDataList* pExtraData2 = pExtraData; - if (pExtraData) - DebugBreak(); - */ - - AddObjectToContainer(pObject, pExtraData, arEntry.Count, nullptr); + AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, - pExtraData ? pExtraData->GetCount() : -1, arEntry.Count); + pExtraDataList ? pExtraDataList->GetCount() : -1, arEntry.Count); } void TESObjectREFR::Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* aObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept @@ -362,14 +328,14 @@ void TP_MAKE_THISCALL(HookActivate, TESObjectREFR, TESObjectREFR* apActivator, u return ThisCall(RealActivate, apThis, apActivator, aUnk1, apObjectToGet, aCount, aDefaultProcessing); } -void* TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apItem, BSExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) +void* TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) { World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); return ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner); } // TODO: here's your deadlock/memory leak, fix that -void* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, BSExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3) +void* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, ExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3) { thread_local static uint32_t count = 0; count++; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 3951f6903..3987bfadd 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -71,7 +71,7 @@ struct TESObjectREFR : TESForm virtual void sub_57(); virtual void sub_58(); virtual void sub_59(); - virtual void AddObjectToContainer(TESBoundObject* apObj, BSExtraDataList* aspExtra, int32_t aicount, TESObjectREFR* apOldContainer); + virtual void AddObjectToContainer(TESBoundObject* apObj, ExtraDataList* aspExtra, int32_t aicount, TESObjectREFR* apOldContainer); virtual void sub_5B(); virtual MagicCaster* GetMagicCaster(MagicSystem::CastingSource aeSource); virtual void sub_5D(); @@ -143,7 +143,7 @@ struct TESObjectREFR : TESForm uint32_t GetCellId() const noexcept; TESWorldSpace* GetWorldSpace() const noexcept; ExtraContainerChanges::Data* GetContainerChanges() const noexcept; - BSExtraDataList* GetExtraDataList() noexcept; + ExtraDataList* GetExtraDataList() noexcept; Lock* GetLock() noexcept; TESContainer* GetContainer() const noexcept; int64_t GetItemCountInInventory(TESForm* apItem) const noexcept; @@ -183,7 +183,7 @@ struct TESObjectREFR : TESForm NiPoint3 position; TESObjectCELL* parentCell; void* loadedState; - BSExtraDataList extraData; + ExtraDataList extraData; BSRecursiveLock refLock; uint16_t scale; uint16_t referenceFlags; From 6bd8c517e69c08b48860d4d482ac91d1b285483b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Feb 2022 22:13:54 +0100 Subject: [PATCH 24/48] fix: memory leak --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 836b606ad..5058cd3f2 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -247,6 +247,7 @@ void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept if (pExtraDataList->data == nullptr) { + Memory::Delete(pExtraDataList->bitfield); Memory::Delete(pExtraDataList); pExtraDataList = nullptr; } From 8f0cd0a1ea2573ffbfe04117398f5d11706f07f4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Feb 2022 23:01:26 +0100 Subject: [PATCH 25/48] fix: build errors --- Code/client/Games/ExtraData.cpp | 80 ------------------- Code/client/Games/ExtraDataList.cpp | 77 ++++++++++++++++++ Code/client/Games/References.cpp | 1 + Code/client/Games/Skyrim/Actor.cpp | 1 + .../Skyrim/ExtraData/ExtraContainerChanges.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.h | 1 + 6 files changed, 81 insertions(+), 81 deletions(-) delete mode 100644 Code/client/Games/ExtraData.cpp diff --git a/Code/client/Games/ExtraData.cpp b/Code/client/Games/ExtraData.cpp deleted file mode 100644 index 9ea676d4d..000000000 --- a/Code/client/Games/ExtraData.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include - -#include - -bool ExtraDataList::Contains(ExtraData aType) const -{ - if(bitfield) - { - const auto value = static_cast(aType); - const auto index = value >> 3; - - const auto element = bitfield->data[index]; - - return (element >> (value % 8)) & 1; - } - - return false; -} - -BSExtraData* ExtraDataList::GetByType(ExtraData aType) const -{ - BSScopedLock _(lock); - - if (!Contains(aType)) - return nullptr; - - auto pEntry = data; -#if TP_SKYRIM - while (pEntry != nullptr && pEntry->GetType() != aType) -#else - while (pEntry != nullptr && pEntry->type != aType) -#endif - { - pEntry = pEntry->next; - } - - return pEntry; -} - -bool ExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) -{ - if (Contains(aType)) - return false; - - // TODO: this sometimes causes a deadlock - //BSScopedLock _(lock); - - BSExtraData* pNext = data; - data = apNewData; - apNewData->next = pNext; - SetType(aType, false); - - return true; -} - -uint32_t ExtraDataList::GetCount() const -{ - uint32_t count = 0; - - BSExtraData* pNext = data; - while (pNext) - { - count++; - pNext = pNext->next; - } - - return count; -} - -void ExtraDataList::SetType(ExtraData aType, bool aClear) -{ - uint32_t index = static_cast(aType) >> 3; - uint8_t bitmask = 1 << (static_cast(aType) % 8); - uint8_t& flag = bitfield->data[index]; - if (aClear) - flag &= ~bitmask; - else - flag |= bitmask; -} - diff --git a/Code/client/Games/ExtraDataList.cpp b/Code/client/Games/ExtraDataList.cpp index 1a39e809d..a816236bd 100644 --- a/Code/client/Games/ExtraDataList.cpp +++ b/Code/client/Games/ExtraDataList.cpp @@ -1,5 +1,82 @@ #include "ExtraDataList.h" +bool ExtraDataList::Contains(ExtraData aType) const +{ + if(bitfield) + { + const auto value = static_cast(aType); + const auto index = value >> 3; + + const auto element = bitfield->data[index]; + + return (element >> (value % 8)) & 1; + } + + return false; +} + +BSExtraData* ExtraDataList::GetByType(ExtraData aType) const +{ + BSScopedLock _(lock); + + if (!Contains(aType)) + return nullptr; + + auto pEntry = data; +#if TP_SKYRIM + while (pEntry != nullptr && pEntry->GetType() != aType) +#else + while (pEntry != nullptr && pEntry->type != aType) +#endif + { + pEntry = pEntry->next; + } + + return pEntry; +} + +bool ExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) +{ + if (Contains(aType)) + return false; + + // TODO: this sometimes causes a deadlock + //BSScopedLock _(lock); + + BSExtraData* pNext = data; + data = apNewData; + apNewData->next = pNext; + SetType(aType, false); + + return true; +} + +uint32_t ExtraDataList::GetCount() const +{ + uint32_t count = 0; + + BSExtraData* pNext = data; + while (pNext) + { + count++; + pNext = pNext->next; + } + + return count; +} + +void ExtraDataList::SetType(ExtraData aType, bool aClear) +{ + uint32_t index = static_cast(aType) >> 3; + uint8_t bitmask = 1 << (static_cast(aType) % 8); + uint8_t& flag = bitfield->data[index]; + if (aClear) + flag &= ~bitmask; + else + flag |= bitmask; +} + + void ExtraDataList::SetSoulData(SOUL_LEVEL aSoulLevel) noexcept { TP_THIS_FUNCTION(TSetSoulData, void, ExtraDataList, SOUL_LEVEL aSoulLevel); diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index b9c50eecf..e9dfa0b1c 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index d6c8e4112..bdd480df1 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include diff --git a/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h b/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h index 299d881e5..05f94d917 100644 --- a/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h +++ b/Code/client/Games/Skyrim/ExtraData/ExtraContainerChanges.h @@ -1,6 +1,6 @@ #pragma once -#include +#include struct BGSLoadFormBuffer; struct BGSSaveFormBuffer; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 3987bfadd..ebd06863f 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -10,6 +10,7 @@ #include #include #include +#include struct AnimationVariables; struct TESWorldSpace; From 5af2c5a2f1afd9682fa0792d01e8075d0ae087be Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 15 Feb 2022 00:05:16 +0100 Subject: [PATCH 26/48] tweak: moved container stuff to TESObjectREFR --- Code/client/Games/Skyrim/Actor.cpp | 193 --------------------- Code/client/Games/Skyrim/Actor.h | 3 - Code/client/Games/Skyrim/TESObjectREFR.cpp | 193 +++++++++++++++++++++ Code/client/Games/Skyrim/TESObjectREFR.h | 4 + 4 files changed, 197 insertions(+), 196 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index bdd480df1..93bb39a8b 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -223,199 +223,6 @@ Inventory Actor::GetInventory() const noexcept return inventory; } -Container Actor::GetFullContainer() const noexcept -{ - auto& modSystem = World::Get().GetModSystem(); - Container fullContainer{}; - - if (TESContainer* pBaseContainer = GetContainer()) - { - for (int i = 0; i < pBaseContainer->count; i++) - { - TESContainer::Entry* pGameEntry = pBaseContainer->entries[i]; - if (!pGameEntry || !pGameEntry->form) - { - spdlog::warn("Entry or form for inventory item is null."); - continue; - } - - Container::Entry entry; - modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); - entry.Count = pGameEntry->count; - - fullContainer.Entries.push_back(std::move(entry)); - } - } - - Container extraContainer{}; - - auto pExtraContChangesEntries = GetContainerChanges()->entries; - for (auto pGameEntry : *pExtraContChangesEntries) - { - Container::Entry entry; - modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); - entry.Count = pGameEntry->count; - - for (ExtraDataList* pExtraDataList : *pGameEntry->dataList) - { - if (!pExtraDataList) - continue; - - Container::Entry innerEntry; - innerEntry.BaseId = entry.BaseId; - innerEntry.Count = 1; - - if (ExtraCount* pExtraCount = (ExtraCount*)pExtraDataList->GetByType(ExtraData::Count)) - { - innerEntry.Count = pExtraCount->count; - } - - if (ExtraCharge* pExtraCharge = (ExtraCharge*)pExtraDataList->GetByType(ExtraData::Charge)) - { - innerEntry.ExtraCharge = pExtraCharge->fCharge; - } - - if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)pExtraDataList->GetByType(ExtraData::Enchantment)) - { - TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); - // TODO: enchantments seem to always be temporaries, keep this in mind when trying to apply container - // Get base form id of enchantment instead? Probably gonna have to serialize more data - modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); - if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) - { - for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) - { - // TODO: null checking and that - Container::EffectItem effect; - effect.Magnitude = pEffectItem->data.fMagnitude; - effect.Area = pEffectItem->data.iArea; - effect.Duration = pEffectItem->data.iDuration; - effect.RawCost = pEffectItem->fRawCost; - modSystem.GetServerModId(pEffectItem->pEffectSetting->formID, effect.EffectId); - innerEntry.EnchantData.Effects.push_back(effect); - } - - uint32_t objectId = modSystem.GetGameId(innerEntry.BaseId); - innerEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; - } - innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; - innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; - } - - if (ExtraHealth* pExtraHealth = (ExtraHealth*)pExtraDataList->GetByType(ExtraData::Health)) - { - innerEntry.ExtraHealth = pExtraHealth->fHealth; - } - - if (ExtraPoison* pExtraPoison = (ExtraPoison*)pExtraDataList->GetByType(ExtraData::Poison)) - { - TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); - modSystem.GetServerModId(pExtraPoison->pPoison->formID, innerEntry.ExtraPoisonId); - innerEntry.ExtraPoisonCount = pExtraPoison->uiCount; - } - - if (ExtraSoul* pExtraSoul = (ExtraSoul*)pExtraDataList->GetByType(ExtraData::Soul)) - { - innerEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; - } - - if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)pExtraDataList->GetByType(ExtraData::TextDisplayData)) - { - if (pExtraTextDisplayData->DisplayName) - innerEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; - else - innerEntry.ExtraTextDisplayName = ""; - } - - innerEntry.ExtraWorn = pExtraDataList->Contains(ExtraData::Worn); - innerEntry.ExtraWornLeft = pExtraDataList->Contains(ExtraData::WornLeft); - - entry.Count -= innerEntry.Count; - - extraContainer.Entries.push_back(std::move(innerEntry)); - } - - if (entry.Count != 0) - extraContainer.Entries.push_back(std::move(entry)); - } - - spdlog::info("ExtraContainer count: {}", extraContainer.Entries.size()); - - Container minimizedExtraContainer{}; - - for (auto& entry : extraContainer.Entries) - { - auto duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](const Container::Entry& newEntry) { - return newEntry.CanBeMerged(entry); - }); - - if (duplicate == std::end(minimizedExtraContainer.Entries)) - { - minimizedExtraContainer.Entries.push_back(entry); - continue; - } - - duplicate->Count += entry.Count; - } - - spdlog::info("MinExtraContainer count: {}", minimizedExtraContainer.Entries.size()); - - for (auto& entry : minimizedExtraContainer.Entries) - { - if (entry.ContainsExtraData()) - continue; - - auto duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](const Container::Entry& newEntry) { - return newEntry.CanBeMerged(entry); - }); - - if (duplicate == std::end(fullContainer.Entries)) - continue; - - entry.Count += duplicate->Count; - duplicate->Count = 0; - } - - spdlog::info("MinExtraContainer count after: {}", minimizedExtraContainer.Entries.size()); - - fullContainer.Entries.insert(fullContainer.Entries.end(), minimizedExtraContainer.Entries.begin(), - minimizedExtraContainer.Entries.end()); - - spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); - - return fullContainer; -} - -void Actor::SetFullContainer(Container& acContainer) noexcept -{ - RemoveAllItems(); - - Container currentContainer = GetFullContainer(); - for (auto currentEntry : currentContainer.Entries) - { - auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Container::Entry& newEntry) { - return newEntry.CanBeMerged(currentEntry); - }); - - if (duplicate != std::end(acContainer.Entries)) - { - duplicate->Count -= currentEntry.Count; - } - else - { - acContainer.Entries.push_back(*duplicate); - Container::Entry& back = acContainer.Entries.back(); - back.Count *= -1; - } - } - - for (const Container::Entry& entry : acContainer.Entries) - { - if (entry.Count != 0) - AddItem(entry); - } -} - Factions Actor::GetFactions() const noexcept { Factions result; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 75b38c5ae..11b942e03 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -13,7 +13,6 @@ #include #include #include -#include struct TESNPC; struct TESRace; @@ -193,7 +192,6 @@ struct Actor : TESObjectREFR float GetActorMaxValue(uint32_t aId) const noexcept; Inventory GetInventory() const noexcept; - Container GetFullContainer() const noexcept; Factions GetFactions() const noexcept; ActorValues GetEssentialActorValues() const noexcept; @@ -209,7 +207,6 @@ struct Actor : TESObjectREFR void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; void SetPackage(TESPackage* apPackage) noexcept; - void SetFullContainer(Container& acContainer) noexcept; // Actions void UnEquipAll() noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 5058cd3f2..d1656211c 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -151,6 +151,199 @@ int64_t TESObjectREFR::GetItemCountInInventory(TESForm* apItem) const noexcept return count; } +Container TESObjectREFR::GetFullContainer() const noexcept +{ + auto& modSystem = World::Get().GetModSystem(); + Container fullContainer{}; + + if (TESContainer* pBaseContainer = GetContainer()) + { + for (int i = 0; i < pBaseContainer->count; i++) + { + TESContainer::Entry* pGameEntry = pBaseContainer->entries[i]; + if (!pGameEntry || !pGameEntry->form) + { + spdlog::warn("Entry or form for inventory item is null."); + continue; + } + + Container::Entry entry; + modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); + entry.Count = pGameEntry->count; + + fullContainer.Entries.push_back(std::move(entry)); + } + } + + Container extraContainer{}; + + auto pExtraContChangesEntries = GetContainerChanges()->entries; + for (auto pGameEntry : *pExtraContChangesEntries) + { + Container::Entry entry; + modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); + entry.Count = pGameEntry->count; + + for (ExtraDataList* pExtraDataList : *pGameEntry->dataList) + { + if (!pExtraDataList) + continue; + + Container::Entry innerEntry; + innerEntry.BaseId = entry.BaseId; + innerEntry.Count = 1; + + if (ExtraCount* pExtraCount = (ExtraCount*)pExtraDataList->GetByType(ExtraData::Count)) + { + innerEntry.Count = pExtraCount->count; + } + + if (ExtraCharge* pExtraCharge = (ExtraCharge*)pExtraDataList->GetByType(ExtraData::Charge)) + { + innerEntry.ExtraCharge = pExtraCharge->fCharge; + } + + if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)pExtraDataList->GetByType(ExtraData::Enchantment)) + { + TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); + // TODO: enchantments seem to always be temporaries, keep this in mind when trying to apply container + // Get base form id of enchantment instead? Probably gonna have to serialize more data + modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); + if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) + { + for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) + { + // TODO: null checking and that + Container::EffectItem effect; + effect.Magnitude = pEffectItem->data.fMagnitude; + effect.Area = pEffectItem->data.iArea; + effect.Duration = pEffectItem->data.iDuration; + effect.RawCost = pEffectItem->fRawCost; + modSystem.GetServerModId(pEffectItem->pEffectSetting->formID, effect.EffectId); + innerEntry.EnchantData.Effects.push_back(effect); + } + + uint32_t objectId = modSystem.GetGameId(innerEntry.BaseId); + innerEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; + } + innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; + innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; + } + + if (ExtraHealth* pExtraHealth = (ExtraHealth*)pExtraDataList->GetByType(ExtraData::Health)) + { + innerEntry.ExtraHealth = pExtraHealth->fHealth; + } + + if (ExtraPoison* pExtraPoison = (ExtraPoison*)pExtraDataList->GetByType(ExtraData::Poison)) + { + TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); + modSystem.GetServerModId(pExtraPoison->pPoison->formID, innerEntry.ExtraPoisonId); + innerEntry.ExtraPoisonCount = pExtraPoison->uiCount; + } + + if (ExtraSoul* pExtraSoul = (ExtraSoul*)pExtraDataList->GetByType(ExtraData::Soul)) + { + innerEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; + } + + if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)pExtraDataList->GetByType(ExtraData::TextDisplayData)) + { + if (pExtraTextDisplayData->DisplayName) + innerEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; + else + innerEntry.ExtraTextDisplayName = ""; + } + + innerEntry.ExtraWorn = pExtraDataList->Contains(ExtraData::Worn); + innerEntry.ExtraWornLeft = pExtraDataList->Contains(ExtraData::WornLeft); + + entry.Count -= innerEntry.Count; + + extraContainer.Entries.push_back(std::move(innerEntry)); + } + + if (entry.Count != 0) + extraContainer.Entries.push_back(std::move(entry)); + } + + spdlog::info("ExtraContainer count: {}", extraContainer.Entries.size()); + + Container minimizedExtraContainer{}; + + for (auto& entry : extraContainer.Entries) + { + auto duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](const Container::Entry& newEntry) { + return newEntry.CanBeMerged(entry); + }); + + if (duplicate == std::end(minimizedExtraContainer.Entries)) + { + minimizedExtraContainer.Entries.push_back(entry); + continue; + } + + duplicate->Count += entry.Count; + } + + spdlog::info("MinExtraContainer count: {}", minimizedExtraContainer.Entries.size()); + + for (auto& entry : minimizedExtraContainer.Entries) + { + if (entry.ContainsExtraData()) + continue; + + auto duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](const Container::Entry& newEntry) { + return newEntry.CanBeMerged(entry); + }); + + if (duplicate == std::end(fullContainer.Entries)) + continue; + + entry.Count += duplicate->Count; + duplicate->Count = 0; + } + + spdlog::info("MinExtraContainer count after: {}", minimizedExtraContainer.Entries.size()); + + fullContainer.Entries.insert(fullContainer.Entries.end(), minimizedExtraContainer.Entries.begin(), + minimizedExtraContainer.Entries.end()); + + spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); + + return fullContainer; +} + +void TESObjectREFR::SetFullContainer(Container& acContainer) noexcept +{ + RemoveAllItems(); + + Container currentContainer = GetFullContainer(); + for (auto currentEntry : currentContainer.Entries) + { + auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Container::Entry& newEntry) { + return newEntry.CanBeMerged(currentEntry); + }); + + if (duplicate != std::end(acContainer.Entries)) + { + duplicate->Count -= currentEntry.Count; + } + else + { + acContainer.Entries.push_back(*duplicate); + Container::Entry& back = acContainer.Entries.back(); + back.Count *= -1; + } + } + + for (const Container::Entry& entry : acContainer.Entries) + { + if (entry.Count != 0) + AddItem(entry); + } +} + void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept { ModSystem& modSystem = World::Get().GetModSystem(); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index ebd06863f..458eead3e 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -11,6 +11,7 @@ #include #include #include +#include struct AnimationVariables; struct TESWorldSpace; @@ -174,6 +175,9 @@ struct TESObjectREFR : TESForm const float GetHeight() noexcept; void EnableImpl() noexcept; + Container GetFullContainer() const noexcept; + void SetFullContainer(Container& acContainer) noexcept; + void AddItem(const Container::Entry& arEntry) noexcept; BSHandleRefObject handleRefObject; From 3bb8caeea307d744c3ecf76dfcd3c35640cb5db0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 15 Feb 2022 00:55:32 +0100 Subject: [PATCH 27/48] tweak: network serialization for containers --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 8 ++-- Code/encoding/Structs/Container.cpp | 45 ++++++++++++++++++++++ Code/encoding/Structs/Container.h | 23 ++--------- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index d1656211c..a022f9e25 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -206,14 +206,15 @@ Container TESObjectREFR::GetFullContainer() const noexcept if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)pExtraDataList->GetByType(ExtraData::Enchantment)) { TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); - // TODO: enchantments seem to always be temporaries, keep this in mind when trying to apply container - // Get base form id of enchantment instead? Probably gonna have to serialize more data + modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); + if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) { for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) { - // TODO: null checking and that + TP_ASSERT(pEffectItem, "pEffectItem is null."); + Container::EffectItem effect; effect.Magnitude = pEffectItem->data.fMagnitude; effect.Area = pEffectItem->data.iArea; @@ -226,6 +227,7 @@ Container TESObjectREFR::GetFullContainer() const noexcept uint32_t objectId = modSystem.GetGameId(innerEntry.BaseId); innerEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; } + innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; } diff --git a/Code/encoding/Structs/Container.cpp b/Code/encoding/Structs/Container.cpp index b5199d145..f30d470cc 100644 --- a/Code/encoding/Structs/Container.cpp +++ b/Code/encoding/Structs/Container.cpp @@ -3,19 +3,50 @@ using TiltedPhoques::Serialization; +void Container::EffectItem::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteFloat(aWriter, Magnitude); + Serialization::WriteVarInt(aWriter, Area); + Serialization::WriteVarInt(aWriter, Duration); + Serialization::WriteFloat(aWriter, RawCost); + EffectId.Serialize(aWriter); +} + +void Container::EffectItem::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + Magnitude = Serialization::ReadFloat(aReader); + Area = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + Duration = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + RawCost = Serialization::ReadFloat(aReader); + uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + EffectId.Deserialize(aReader); +} + void Container::Entry::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { BaseId.Serialize(aWriter); Serialization::WriteVarInt(aWriter, Count); + Serialization::WriteFloat(aWriter, ExtraCharge); + ExtraEnchantId.Serialize(aWriter); Serialization::WriteVarInt(aWriter, ExtraEnchantCharge); + Serialization::WriteVarInt(aWriter, EnchantData.Effects.size()); + for (const EffectItem& effect : EnchantData.Effects) + { + effect.Serialize(aWriter); + } + Serialization::WriteFloat(aWriter, ExtraHealth); + ExtraPoisonId.Serialize(aWriter); Serialization::WriteVarInt(aWriter, ExtraPoisonCount); + Serialization::WriteVarInt(aWriter, ExtraSoulLevel); + Serialization::WriteString(aWriter, ExtraTextDisplayName); + Serialization::WriteBool(aWriter, EnchantData.IsWeapon); Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); Serialization::WriteBool(aWriter, ExtraWorn); Serialization::WriteBool(aWriter, ExtraWornLeft); @@ -25,15 +56,29 @@ void Container::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexc { BaseId.Deserialize(aReader); Count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraCharge = Serialization::ReadFloat(aReader); + ExtraEnchantId.Deserialize(aReader); ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; + uint32_t effectCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + for (uint32_t i = 0; i < effectCount; i++) + { + EffectItem effect; + effect.Deserialize(aReader); + EnchantData.Effects.push_back(effect); + } + ExtraHealth = Serialization::ReadFloat(aReader); + ExtraPoisonId.Deserialize(aReader); ExtraPoisonCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraSoulLevel = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + ExtraTextDisplayName = Serialization::ReadString(aReader); + EnchantData.IsWeapon = Serialization::ReadBool(aReader); ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); ExtraWorn = Serialization::ReadBool(aReader); ExtraWornLeft = Serialization::ReadBool(aReader); diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h index 7f117f5e1..681ffd8f1 100644 --- a/Code/encoding/Structs/Container.h +++ b/Code/encoding/Structs/Container.h @@ -16,28 +16,15 @@ struct Container int32_t Duration{}; float RawCost{}; GameId EffectId{}; + + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; + void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; }; struct EnchantmentData { bool IsWeapon{}; Vector Effects{}; - - /* - int32_t CostOverride{}; - int32_t Flags{}; - int32_t CastingType{}; - int32_t ChargeOverride{}; - int32_t Delivery{}; - int32_t SpellType{}; - float ChargeTime{}; - // TODO: try with IDs for base and worn restrictions first - // place asserts to see if these are ever temporary - // should probably support it anyway though - GameId BaseEnchantmentId{}; - GameId WornRestrictionsId{}; - //Vector WornRestrictions{}; - */ }; struct Entry @@ -45,13 +32,10 @@ struct Container GameId BaseId{}; int32_t Count{}; - // TODO: refactor extra data stuff - // these are the extra data items seemingly relevant to container items float ExtraCharge{}; GameId ExtraEnchantId{}; uint16_t ExtraEnchantCharge{}; - bool ExtraEnchantRemoveUnequip{}; EnchantmentData EnchantData{}; float ExtraHealth{}; @@ -63,6 +47,7 @@ struct Container String ExtraTextDisplayName{}; + bool ExtraEnchantRemoveUnequip{}; bool ExtraWorn{}; bool ExtraWornLeft{}; From a0cb1f43990be8f4513faffa165b1ed06092e733 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 15 Feb 2022 13:33:23 +0100 Subject: [PATCH 28/48] tweak: replaced old inventory infrastructure --- Code/client/Games/Skyrim/Actor.cpp | 112 ----------- Code/client/Games/Skyrim/Actor.h | 2 - .../Games/Skyrim/Forms/EnchantmentItem.cpp | 6 +- .../Games/Skyrim/Forms/EnchantmentItem.h | 4 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 62 +++--- Code/client/Games/Skyrim/TESObjectREFR.h | 6 +- Code/client/Services/Debug/TestService.cpp | 10 +- .../Services/Generic/InventoryService.cpp | 22 ++- Code/client/Services/InventoryService.h | 1 + Code/encoding/Structs/Container.cpp | 128 ------------ Code/encoding/Structs/Container.h | 96 --------- Code/encoding/Structs/Inventory.cpp | 186 ++++++++++-------- Code/encoding/Structs/Inventory.h | 96 +++++++-- 13 files changed, 247 insertions(+), 484 deletions(-) delete mode 100644 Code/encoding/Structs/Container.cpp delete mode 100644 Code/encoding/Structs/Container.h diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 93bb39a8b..fbb45f570 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -190,39 +190,6 @@ TESForm *Actor::GetCurrentLocation() return FUNC_GetActorLocation(this); } -Inventory Actor::GetInventory() const noexcept -{ - auto& modSystem = World::Get().GetModSystem(); - - Inventory inventory; - inventory.Buffer = SerializeInventory(); - - auto pMainHandWeapon = GetEquippedWeapon(0); - uint32_t mainId = pMainHandWeapon ? pMainHandWeapon->formID : 0; - modSystem.GetServerModId(mainId, inventory.LeftHandWeapon); - - auto pSecondaryHandWeapon = GetEquippedWeapon(1); - uint32_t secondaryId = pSecondaryHandWeapon ? pSecondaryHandWeapon->formID : 0; - modSystem.GetServerModId(secondaryId, inventory.RightHandWeapon); - - mainId = magicItems[0] ? magicItems[0]->formID : 0; - modSystem.GetServerModId(mainId, inventory.LeftHandSpell); - - secondaryId = magicItems[1] ? magicItems[1]->formID : 0; - modSystem.GetServerModId(secondaryId, inventory.RightHandSpell); - - uint32_t shoutId = equippedShout ? equippedShout->formID : 0; - modSystem.GetServerModId(shoutId, inventory.Shout); - - auto pAmmo = GetEquippedAmmo(); - uint32_t ammoId = pAmmo ? pAmmo->formID : 0; - modSystem.GetServerModId(ammoId, inventory.Ammo); - - inventory.IsWeaponDrawn = actorState.IsWeaponDrawn(); - - return inventory; -} - Factions Actor::GetFactions() const noexcept { Factions result; @@ -288,85 +255,6 @@ float Actor::GetActorMaxValue(uint32_t aId) const noexcept return actorValueOwner.GetMaxValue(aId); } -void Actor::SetInventory(const Inventory& acInventory) noexcept -{ - spdlog::info("Actor[{:X}]::SetInventory() with inventory size: {}", formID, acInventory.Buffer.size()); - UnEquipAll(); - - auto* pEquipManager = EquipManager::Get(); - - if (!acInventory.Buffer.empty()) - DeserializeInventory(acInventory.Buffer); - - auto& modSystem = World::Get().GetModSystem(); - - uint32_t mainHandWeaponId = modSystem.GetGameId(acInventory.LeftHandWeapon); - - if (mainHandWeaponId) - pEquipManager->Equip(this, TESForm::GetById(mainHandWeaponId), nullptr, 1, DefaultObjectManager::Get().leftEquipSlot, false, true, false, false); - - uint32_t secondaryHandWeaponId = modSystem.GetGameId(acInventory.RightHandWeapon); - - if (secondaryHandWeaponId) - pEquipManager->Equip(this, TESForm::GetById(secondaryHandWeaponId), nullptr, 1, DefaultObjectManager::Get().rightEquipSlot, false, true, false, false); - - mainHandWeaponId = modSystem.GetGameId(acInventory.LeftHandSpell); - - if (mainHandWeaponId) - pEquipManager->EquipSpell(this, TESForm::GetById(mainHandWeaponId), 0); - - secondaryHandWeaponId = modSystem.GetGameId(acInventory.RightHandSpell); - - if (secondaryHandWeaponId) - pEquipManager->EquipSpell(this, TESForm::GetById(secondaryHandWeaponId), 1); - - uint32_t shoutId = modSystem.GetGameId(acInventory.Shout); - - if (shoutId) - pEquipManager->EquipShout(this, TESForm::GetById(shoutId)); - - uint32_t ammoId = modSystem.GetGameId(acInventory.Ammo); - - if (ammoId) - { - auto* pAmmo = TESForm::GetById(ammoId); - - auto count = GetItemCountInInventory(pAmmo); - - const auto pContainerChanges = GetContainerChanges()->entries; - for (auto pChange : *pContainerChanges) - { - if (pChange && pChange->form && pChange->form->formID == ammoId) - { - if (pChange->form->formID != ammoId) - continue; - - const auto pDataLists = pChange->dataList; - for (auto* pDataList : *pDataLists) - { - if (pDataList) - { - if (pDataList->Contains(ExtraData::Count)) - { - BSExtraData* pExtraData = pDataList->GetByType(ExtraData::Count); - ExtraCount* pExtraCount = RTTI_CAST(pExtraData, BSExtraData, ExtraCount); - if (pExtraCount) - { - pExtraCount->count = 0; - } - } - } - } - } - } - - pEquipManager->Equip(this, pAmmo, nullptr, count, DefaultObjectManager::Get().rightEquipSlot, false, true, false, false); - } - - // TODO: check if weapon drawn state is the same - SetWeaponDrawnEx(acInventory.IsWeaponDrawn); -} - void Actor::ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept { const float current = GetActorValue(aId); diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 11b942e03..803d12a5d 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -191,14 +191,12 @@ struct Actor : TESObjectREFR float GetActorValue(uint32_t aId) const noexcept; float GetActorMaxValue(uint32_t aId) const noexcept; - Inventory GetInventory() const noexcept; Factions GetFactions() const noexcept; ActorValues GetEssentialActorValues() const noexcept; // Setters void SetSpeed(float aSpeed) noexcept; void SetLevelMod(uint32_t aLevel) noexcept; - void SetInventory(const Inventory& acInventory) noexcept; void SetActorValue(uint32_t aId, float aValue) noexcept; void ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept; void SetActorValues(const ActorValues& acActorValues) noexcept; diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index b2c824b66..2b5b05e41 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -4,7 +4,7 @@ #include #include -EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData) noexcept +EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData) noexcept { /* TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); @@ -22,7 +22,7 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData effects.Resize(aData.Effects.size()); for (int i = 0; i < aData.Effects.size(); i++) { - Container::EffectItem effect = aData.Effects[i]; + Inventory::EffectItem effect = aData.Effects[i]; EffectItem effectItem{}; effectItem.data.fMagnitude = effect.Magnitude; @@ -60,7 +60,7 @@ EnchantmentItem* EnchantmentItem::Create(const Container::EnchantmentData& aData return pItem; } -void EnchantmentItem::Init(const Container::EnchantmentData& aData) +void EnchantmentItem::Init(const Inventory::EnchantmentData& aData) { /* iCostOverride = aData.CostOverride; diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h index 474c45f81..6a6ce9f3f 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h @@ -8,9 +8,9 @@ struct EnchantmentItem : MagicItem { - static EnchantmentItem* Create(const Container::EnchantmentData& aData) noexcept; + static EnchantmentItem* Create(const Inventory::EnchantmentData& aData) noexcept; - void Init(const Container::EnchantmentData& aData); + void Init(const Inventory::EnchantmentData& aData); int32_t iCostOverride; int32_t iFlags; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index a022f9e25..280d03858 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -151,10 +151,10 @@ int64_t TESObjectREFR::GetItemCountInInventory(TESForm* apItem) const noexcept return count; } -Container TESObjectREFR::GetFullContainer() const noexcept +Inventory TESObjectREFR::GetInventory() const noexcept { auto& modSystem = World::Get().GetModSystem(); - Container fullContainer{}; + Inventory inventory{}; if (TESContainer* pBaseContainer = GetContainer()) { @@ -167,20 +167,20 @@ Container TESObjectREFR::GetFullContainer() const noexcept continue; } - Container::Entry entry; + Inventory::Entry entry; modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); entry.Count = pGameEntry->count; - fullContainer.Entries.push_back(std::move(entry)); + inventory.Entries.push_back(std::move(entry)); } } - Container extraContainer{}; + Inventory extraInventory{}; auto pExtraContChangesEntries = GetContainerChanges()->entries; for (auto pGameEntry : *pExtraContChangesEntries) { - Container::Entry entry; + Inventory::Entry entry; modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); entry.Count = pGameEntry->count; @@ -189,7 +189,7 @@ Container TESObjectREFR::GetFullContainer() const noexcept if (!pExtraDataList) continue; - Container::Entry innerEntry; + Inventory::Entry innerEntry; innerEntry.BaseId = entry.BaseId; innerEntry.Count = 1; @@ -215,7 +215,7 @@ Container TESObjectREFR::GetFullContainer() const noexcept { TP_ASSERT(pEffectItem, "pEffectItem is null."); - Container::EffectItem effect; + Inventory::EffectItem effect; effect.Magnitude = pEffectItem->data.fMagnitude; effect.Area = pEffectItem->data.iArea; effect.Duration = pEffectItem->data.iDuration; @@ -262,68 +262,68 @@ Container TESObjectREFR::GetFullContainer() const noexcept entry.Count -= innerEntry.Count; - extraContainer.Entries.push_back(std::move(innerEntry)); + extraInventory.Entries.push_back(std::move(innerEntry)); } if (entry.Count != 0) - extraContainer.Entries.push_back(std::move(entry)); + extraInventory.Entries.push_back(std::move(entry)); } - spdlog::info("ExtraContainer count: {}", extraContainer.Entries.size()); + spdlog::info("ExtraInventory count: {}", extraInventory.Entries.size()); - Container minimizedExtraContainer{}; + Inventory minimizedExtraInventory{}; - for (auto& entry : extraContainer.Entries) + for (auto& entry : extraInventory.Entries) { - auto duplicate = std::find_if(minimizedExtraContainer.Entries.begin(), minimizedExtraContainer.Entries.end(), [entry](const Container::Entry& newEntry) { + auto duplicate = std::find_if(minimizedExtraInventory.Entries.begin(), minimizedExtraInventory.Entries.end(), [entry](const Inventory::Entry& newEntry) { return newEntry.CanBeMerged(entry); }); - if (duplicate == std::end(minimizedExtraContainer.Entries)) + if (duplicate == std::end(minimizedExtraInventory.Entries)) { - minimizedExtraContainer.Entries.push_back(entry); + minimizedExtraInventory.Entries.push_back(entry); continue; } duplicate->Count += entry.Count; } - spdlog::info("MinExtraContainer count: {}", minimizedExtraContainer.Entries.size()); + spdlog::info("MinExtraInventory count: {}", minimizedExtraInventory.Entries.size()); - for (auto& entry : minimizedExtraContainer.Entries) + for (auto& entry : minimizedExtraInventory.Entries) { if (entry.ContainsExtraData()) continue; - auto duplicate = std::find_if(fullContainer.Entries.begin(), fullContainer.Entries.end(), [entry](const Container::Entry& newEntry) { + auto duplicate = std::find_if(inventory.Entries.begin(), inventory.Entries.end(), [entry](const Inventory::Entry& newEntry) { return newEntry.CanBeMerged(entry); }); - if (duplicate == std::end(fullContainer.Entries)) + if (duplicate == std::end(inventory.Entries)) continue; entry.Count += duplicate->Count; duplicate->Count = 0; } - spdlog::info("MinExtraContainer count after: {}", minimizedExtraContainer.Entries.size()); + spdlog::info("MinExtraInventory count after: {}", minimizedExtraInventory.Entries.size()); - fullContainer.Entries.insert(fullContainer.Entries.end(), minimizedExtraContainer.Entries.begin(), - minimizedExtraContainer.Entries.end()); + inventory.Entries.insert(inventory.Entries.end(), minimizedExtraInventory.Entries.begin(), + minimizedExtraInventory.Entries.end()); - spdlog::info("fullContainer count after: {}", fullContainer.Entries.size()); + spdlog::info("Inventory count after: {}", inventory.Entries.size()); - return fullContainer; + return inventory; } -void TESObjectREFR::SetFullContainer(Container& acContainer) noexcept +void TESObjectREFR::SetInventory(Inventory& acContainer) noexcept { RemoveAllItems(); - Container currentContainer = GetFullContainer(); + Inventory currentContainer = GetInventory(); for (auto currentEntry : currentContainer.Entries) { - auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Container::Entry& newEntry) { + auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { return newEntry.CanBeMerged(currentEntry); }); @@ -334,19 +334,19 @@ void TESObjectREFR::SetFullContainer(Container& acContainer) noexcept else { acContainer.Entries.push_back(*duplicate); - Container::Entry& back = acContainer.Entries.back(); + Inventory::Entry& back = acContainer.Entries.back(); back.Count *= -1; } } - for (const Container::Entry& entry : acContainer.Entries) + for (const Inventory::Entry& entry : acContainer.Entries) { if (entry.Count != 0) AddItem(entry); } } -void TESObjectREFR::AddItem(const Container::Entry& arEntry) noexcept +void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept { ModSystem& modSystem = World::Get().GetModSystem(); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 458eead3e..5fe26f249 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -175,10 +175,10 @@ struct TESObjectREFR : TESForm const float GetHeight() noexcept; void EnableImpl() noexcept; - Container GetFullContainer() const noexcept; - void SetFullContainer(Container& acContainer) noexcept; + Inventory GetInventory() const noexcept; + void SetInventory(Inventory& acContainer) noexcept; - void AddItem(const Container::Entry& arEntry) noexcept; + void AddItem(const Inventory::Entry& arEntry) noexcept; BSHandleRefObject handleRefObject; uintptr_t unk1C; diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index 7037e4334..aa8d1f683 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -57,9 +57,9 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); - auto container = PlayerCharacter::Get()->GetFullContainer(); - spdlog::info("Container size: {}", container.Entries.size()); - pActor->SetFullContainer(container); + Inventory inventory = PlayerCharacter::Get()->GetInventory(); + spdlog::info("Inventory size: {}", inventory.Entries.size()); + pActor->SetInventory(inventory); m_actors.emplace_back(pActor); } @@ -104,8 +104,8 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept //PlaceActorInWorld(); Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); - auto container = PlayerCharacter::Get()->GetFullContainer(); - pActor->SetFullContainer(container); + auto container = PlayerCharacter::Get()->GetInventory(); + pActor->SetInventory(container); } } else diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 141645997..648f136e0 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -142,7 +142,7 @@ void InventoryService::RunObjectInventoryUpdates() noexcept continue; } - objectData.CurrentInventory.Buffer = pObject->SerializeInventory(); + objectData.CurrentInventory = pObject->GetInventory(); message.Changes[gameId] = objectData; } @@ -208,24 +208,24 @@ void InventoryService::ApplyCachedObjectInventoryChanges() noexcept if (UI::Get()->IsOpen(BSFixedString("ContainerMenu"))) return; - for (const auto& [id, inventory] : m_cachedObjectInventoryChanges) + for (auto& idInventory : m_cachedObjectInventoryChanges) { - const auto cObjectId = World::Get().GetModSystem().GetGameId(id); + const uint32_t cObjectId = World::Get().GetModSystem().GetGameId(idInventory.first); if (cObjectId == 0) { spdlog::error("Failed to retrieve object to sync inventory."); continue; } - auto* pObject = RTTI_CAST(TESForm::GetById(cObjectId), TESForm, TESObjectREFR); + TESObjectREFR* pObject = RTTI_CAST(TESForm::GetById(cObjectId), TESForm, TESObjectREFR); if (!pObject) { spdlog::error("Failed to retrieve object to sync inventory."); continue; } - pObject->RemoveAllItems(); - pObject->DeserializeInventory(inventory.Buffer); + Inventory inventory = idInventory.second; + pObject->SetInventory(inventory); } m_cachedObjectInventoryChanges.clear(); @@ -240,9 +240,9 @@ void InventoryService::ApplyCachedCharacterInventoryChanges() noexcept return; auto view = m_world.view(); - for (const auto& [id, inventory] : m_cachedCharacterInventoryChanges) + for (auto& idInventory : m_cachedCharacterInventoryChanges) { - const auto it = std::find_if(std::begin(view), std::end(view), [id = id, view](entt::entity entity) { + const auto it = std::find_if(std::begin(view), std::end(view), [id = idInventory.first, view](entt::entity entity) { return view.get(entity).Id == id; }); @@ -250,10 +250,12 @@ void InventoryService::ApplyCachedCharacterInventoryChanges() noexcept continue; const auto& formIdComponent = view.get(*it); - auto* const pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); + Actor* const pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); if (!pActor) continue; + Inventory inventory = idInventory.second; + auto& remoteComponent = m_world.get(*it); remoteComponent.SpawnRequest.InventoryContent = inventory; @@ -284,7 +286,7 @@ void InventoryService::RunWeaponStateUpdates() noexcept for (auto entity : view) { const auto& formIdComponent = view.get(entity); - auto* const pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); + Actor* const pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); auto& localComponent = view.get(entity); bool isWeaponDrawn = pActor->actorState.IsWeaponDrawn(); diff --git a/Code/client/Services/InventoryService.h b/Code/client/Services/InventoryService.h index 10db0c9b3..3c53f79e2 100644 --- a/Code/client/Services/InventoryService.h +++ b/Code/client/Services/InventoryService.h @@ -38,6 +38,7 @@ struct InventoryService entt::dispatcher& m_dispatcher; TransportService& m_transport; + // TODO: these two can probably be merged now? Set m_objectsWithInventoryChanges; Set m_charactersWithInventoryChanges; Map m_cachedObjectInventoryChanges; diff --git a/Code/encoding/Structs/Container.cpp b/Code/encoding/Structs/Container.cpp deleted file mode 100644 index f30d470cc..000000000 --- a/Code/encoding/Structs/Container.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include -#include - -using TiltedPhoques::Serialization; - -void Container::EffectItem::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept -{ - Serialization::WriteFloat(aWriter, Magnitude); - Serialization::WriteVarInt(aWriter, Area); - Serialization::WriteVarInt(aWriter, Duration); - Serialization::WriteFloat(aWriter, RawCost); - EffectId.Serialize(aWriter); -} - -void Container::EffectItem::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept -{ - Magnitude = Serialization::ReadFloat(aReader); - Area = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - Duration = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - RawCost = Serialization::ReadFloat(aReader); - uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - EffectId.Deserialize(aReader); -} - -void Container::Entry::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept -{ - BaseId.Serialize(aWriter); - Serialization::WriteVarInt(aWriter, Count); - - Serialization::WriteFloat(aWriter, ExtraCharge); - - ExtraEnchantId.Serialize(aWriter); - Serialization::WriteVarInt(aWriter, ExtraEnchantCharge); - Serialization::WriteVarInt(aWriter, EnchantData.Effects.size()); - for (const EffectItem& effect : EnchantData.Effects) - { - effect.Serialize(aWriter); - } - - Serialization::WriteFloat(aWriter, ExtraHealth); - - ExtraPoisonId.Serialize(aWriter); - Serialization::WriteVarInt(aWriter, ExtraPoisonCount); - - Serialization::WriteVarInt(aWriter, ExtraSoulLevel); - - Serialization::WriteString(aWriter, ExtraTextDisplayName); - - Serialization::WriteBool(aWriter, EnchantData.IsWeapon); - Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); - Serialization::WriteBool(aWriter, ExtraWorn); - Serialization::WriteBool(aWriter, ExtraWornLeft); -} - -void Container::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept -{ - BaseId.Deserialize(aReader); - Count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - - ExtraCharge = Serialization::ReadFloat(aReader); - - ExtraEnchantId.Deserialize(aReader); - ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; - uint32_t effectCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - for (uint32_t i = 0; i < effectCount; i++) - { - EffectItem effect; - effect.Deserialize(aReader); - EnchantData.Effects.push_back(effect); - } - - ExtraHealth = Serialization::ReadFloat(aReader); - - ExtraPoisonId.Deserialize(aReader); - ExtraPoisonCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - - ExtraSoulLevel = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - - ExtraTextDisplayName = Serialization::ReadString(aReader); - - EnchantData.IsWeapon = Serialization::ReadBool(aReader); - ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); - ExtraWorn = Serialization::ReadBool(aReader); - ExtraWornLeft = Serialization::ReadBool(aReader); -} - -bool Container::operator==(const Container& acRhs) const noexcept -{ - return Entries == acRhs.Entries; -} - -bool Container::operator!=(const Container& acRhs) const noexcept -{ - return !this->operator==(acRhs); -} - -bool Container::Entry::operator==(const Container::Entry& acRhs) const noexcept -{ - return BaseId == acRhs.BaseId && - Count == acRhs.Count && - ExtraTextDisplayName == acRhs.ExtraTextDisplayName && - IsExtraDataEquals(acRhs); -} - -bool Container::Entry::operator!=(const Container::Entry& acRhs) const noexcept -{ - return !this->operator==(acRhs); -} - -void Container::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept -{ - Serialization::WriteVarInt(aWriter, Entries.size()); - for (const Entry& entry : Entries) - { - entry.Serialize(aWriter); - } -} - -void Container::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept -{ - uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - for (uint32_t i = 0; i < count; i++) - { - Entry entry; - entry.Deserialize(aReader); - Entries.push_back(entry); - } -} diff --git a/Code/encoding/Structs/Container.h b/Code/encoding/Structs/Container.h deleted file mode 100644 index 681ffd8f1..000000000 --- a/Code/encoding/Structs/Container.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include - -using TiltedPhoques::Buffer; -using TiltedPhoques::String; -using TiltedPhoques::Vector; - -struct Container -{ - struct EffectItem - { - float Magnitude{}; - int32_t Area{}; - int32_t Duration{}; - float RawCost{}; - GameId EffectId{}; - - void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; - void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; - }; - - struct EnchantmentData - { - bool IsWeapon{}; - Vector Effects{}; - }; - - struct Entry - { - GameId BaseId{}; - int32_t Count{}; - - float ExtraCharge{}; - - GameId ExtraEnchantId{}; - uint16_t ExtraEnchantCharge{}; - EnchantmentData EnchantData{}; - - float ExtraHealth{}; - - GameId ExtraPoisonId{}; - uint32_t ExtraPoisonCount{}; - - int32_t ExtraSoulLevel{}; - - String ExtraTextDisplayName{}; - - bool ExtraEnchantRemoveUnequip{}; - bool ExtraWorn{}; - bool ExtraWornLeft{}; - - bool operator==(const Entry& acRhs) const noexcept; - bool operator!=(const Entry& acRhs) const noexcept; - - void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; - void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; - - bool ContainsExtraData() const noexcept - { - return !IsExtraDataEquals(Entry{}); - } - - bool CanBeMerged(const Entry& acRhs) const noexcept - { - return BaseId == acRhs.BaseId && IsExtraDataEquals(acRhs); - } - - bool IsExtraDataEquals(const Entry& acRhs) const noexcept - { - return ExtraCharge == acRhs.ExtraCharge && - ExtraEnchantId == acRhs.ExtraEnchantId && - ExtraEnchantCharge == acRhs.ExtraEnchantCharge && - ExtraEnchantRemoveUnequip == acRhs.ExtraEnchantRemoveUnequip && - ExtraHealth == acRhs.ExtraHealth && - ExtraPoisonId == acRhs.ExtraPoisonId && - ExtraPoisonCount == acRhs.ExtraPoisonCount && - ExtraSoulLevel == acRhs.ExtraSoulLevel && - //ExtraTextDisplayName == acRhs.ExtraTextDisplayName && - ExtraWorn == acRhs.ExtraWorn && - ExtraWornLeft == acRhs.ExtraWornLeft; - } - }; - - Container() = default; - ~Container() = default; - - bool operator==(const Container& acRhs) const noexcept; - bool operator!=(const Container& acRhs) const noexcept; - - void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; - void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; - - Vector Entries{}; -}; diff --git a/Code/encoding/Structs/Inventory.cpp b/Code/encoding/Structs/Inventory.cpp index edaf61b2e..1ad4086c8 100644 --- a/Code/encoding/Structs/Inventory.cpp +++ b/Code/encoding/Structs/Inventory.cpp @@ -1,104 +1,128 @@ -#include +#include #include using TiltedPhoques::Serialization; -bool Inventory::operator==(const Inventory& acRhs) const noexcept +void Inventory::EffectItem::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { - return RightHandWeapon == acRhs.RightHandWeapon - && IsWeaponDrawn == acRhs.IsWeaponDrawn - && Buffer == acRhs.Buffer -#if TP_SKYRIM - && LeftHandWeapon == acRhs.LeftHandWeapon - && RightHandSpell == acRhs.RightHandSpell - && LeftHandSpell == acRhs.LeftHandSpell - && Shout == acRhs.Shout - && Ammo == acRhs.Ammo -#endif - ; + Serialization::WriteFloat(aWriter, Magnitude); + Serialization::WriteVarInt(aWriter, Area); + Serialization::WriteVarInt(aWriter, Duration); + Serialization::WriteFloat(aWriter, RawCost); + EffectId.Serialize(aWriter); } -bool Inventory::operator!=(const Inventory& acRhs) const noexcept +void Inventory::EffectItem::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept { - return !this->operator==(acRhs); + Magnitude = Serialization::ReadFloat(aReader); + Area = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + Duration = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + RawCost = Serialization::ReadFloat(aReader); + uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + EffectId.Deserialize(aReader); } -void Inventory::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +void Inventory::Entry::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { - bool isRightWeaponSet = RightHandWeapon != GameId{}; -#if TP_SKYRIM - bool isLeftWeaponSet = LeftHandWeapon != GameId{}; - bool isLeftSpellSet = LeftHandSpell != GameId{}; - bool isRightSpellSet = RightHandSpell != GameId{}; - bool isShoutSet = Shout != GameId{}; - bool isAmmoSet = Ammo != GameId{}; -#endif - - Serialization::WriteString(aWriter, Buffer); - Serialization::WriteBool(aWriter, IsWeaponDrawn); - - Serialization::WriteBool(aWriter, isRightWeaponSet); -#if TP_SKYRIM - Serialization::WriteBool(aWriter, isLeftWeaponSet); - Serialization::WriteBool(aWriter, isLeftSpellSet); - Serialization::WriteBool(aWriter, isRightSpellSet); - Serialization::WriteBool(aWriter, isShoutSet); - Serialization::WriteBool(aWriter, isAmmoSet); -#endif - - if (isRightWeaponSet) - RightHandWeapon.Serialize(aWriter); - -#if TP_SKYRIM - if (isLeftWeaponSet) - LeftHandWeapon.Serialize(aWriter); - - if (isLeftSpellSet) - LeftHandSpell.Serialize(aWriter); - - if (isRightSpellSet) - RightHandSpell.Serialize(aWriter); - - if (isShoutSet) - Shout.Serialize(aWriter); - - if (isAmmoSet) - Ammo.Serialize(aWriter); -#endif + BaseId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, Count); + + Serialization::WriteFloat(aWriter, ExtraCharge); + + ExtraEnchantId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, ExtraEnchantCharge); + Serialization::WriteVarInt(aWriter, EnchantData.Effects.size()); + for (const EffectItem& effect : EnchantData.Effects) + { + effect.Serialize(aWriter); + } + + Serialization::WriteFloat(aWriter, ExtraHealth); + + ExtraPoisonId.Serialize(aWriter); + Serialization::WriteVarInt(aWriter, ExtraPoisonCount); + + Serialization::WriteVarInt(aWriter, ExtraSoulLevel); + + Serialization::WriteString(aWriter, ExtraTextDisplayName); + + Serialization::WriteBool(aWriter, EnchantData.IsWeapon); + Serialization::WriteBool(aWriter, ExtraEnchantRemoveUnequip); + Serialization::WriteBool(aWriter, ExtraWorn); + Serialization::WriteBool(aWriter, ExtraWornLeft); } -void Inventory::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept +void Inventory::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept { - Buffer = Serialization::ReadString(aReader); - IsWeaponDrawn = Serialization::ReadBool(aReader); + BaseId.Deserialize(aReader); + Count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - bool isRightWeaponSet = Serialization::ReadBool(aReader); -#if TP_SKYRIM - bool isLeftWeaponSet = Serialization::ReadBool(aReader); - bool isLeftSpellSet = Serialization::ReadBool(aReader); - bool isRightSpellSet = Serialization::ReadBool(aReader); - bool isShoutSet = Serialization::ReadBool(aReader); - bool isAmmoSet = Serialization::ReadBool(aReader); -#endif + ExtraCharge = Serialization::ReadFloat(aReader); - if (isRightWeaponSet) - RightHandWeapon.Deserialize(aReader); + ExtraEnchantId.Deserialize(aReader); + ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; + uint32_t effectCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + for (uint32_t i = 0; i < effectCount; i++) + { + EffectItem effect; + effect.Deserialize(aReader); + EnchantData.Effects.push_back(effect); + } -#if TP_SKYRIM - if (isLeftWeaponSet) - LeftHandWeapon.Deserialize(aReader); + ExtraHealth = Serialization::ReadFloat(aReader); - if (isLeftSpellSet) - LeftHandSpell.Deserialize(aReader); + ExtraPoisonId.Deserialize(aReader); + ExtraPoisonCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - if (isRightSpellSet) - RightHandSpell.Deserialize(aReader); + ExtraSoulLevel = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - if (isShoutSet) - Shout.Deserialize(aReader); + ExtraTextDisplayName = Serialization::ReadString(aReader); - if (isAmmoSet) - Ammo.Deserialize(aReader); -#endif + EnchantData.IsWeapon = Serialization::ReadBool(aReader); + ExtraEnchantRemoveUnequip = Serialization::ReadBool(aReader); + ExtraWorn = Serialization::ReadBool(aReader); + ExtraWornLeft = Serialization::ReadBool(aReader); +} +bool Inventory::operator==(const Inventory& acRhs) const noexcept +{ + return Entries == acRhs.Entries; +} + +bool Inventory::operator!=(const Inventory& acRhs) const noexcept +{ + return !this->operator==(acRhs); +} + +bool Inventory::Entry::operator==(const Inventory::Entry& acRhs) const noexcept +{ + return BaseId == acRhs.BaseId && + Count == acRhs.Count && + ExtraTextDisplayName == acRhs.ExtraTextDisplayName && + IsExtraDataEquals(acRhs); +} + +bool Inventory::Entry::operator!=(const Inventory::Entry& acRhs) const noexcept +{ + return !this->operator==(acRhs); +} + +void Inventory::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteVarInt(aWriter, Entries.size()); + for (const Entry& entry : Entries) + { + entry.Serialize(aWriter); + } +} + +void Inventory::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + for (uint32_t i = 0; i < count; i++) + { + Entry entry; + entry.Deserialize(aReader); + Entries.push_back(entry); + } } diff --git a/Code/encoding/Structs/Inventory.h b/Code/encoding/Structs/Inventory.h index 6a03f7a2e..65e30832f 100644 --- a/Code/encoding/Structs/Inventory.h +++ b/Code/encoding/Structs/Inventory.h @@ -5,9 +5,93 @@ using TiltedPhoques::Buffer; using TiltedPhoques::String; +using TiltedPhoques::Vector; struct Inventory { + struct EffectItem + { + EffectItem() = default; + ~EffectItem() = default; + + float Magnitude{}; + int32_t Area{}; + int32_t Duration{}; + float RawCost{}; + GameId EffectId{}; + + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; + void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; + }; + + struct EnchantmentData + { + EnchantmentData() = default; + ~EnchantmentData() = default; + + bool IsWeapon{}; + Vector Effects{}; + }; + + struct Entry + { + Entry() = default; + ~Entry() = default; + + GameId BaseId{}; + int32_t Count{}; + + float ExtraCharge{}; + + GameId ExtraEnchantId{}; + uint16_t ExtraEnchantCharge{}; + EnchantmentData EnchantData{}; + + float ExtraHealth{}; + + GameId ExtraPoisonId{}; + uint32_t ExtraPoisonCount{}; + + int32_t ExtraSoulLevel{}; + + String ExtraTextDisplayName{}; + + bool ExtraEnchantRemoveUnequip{}; + bool ExtraWorn{}; + bool ExtraWornLeft{}; + + bool operator==(const Entry& acRhs) const noexcept; + bool operator!=(const Entry& acRhs) const noexcept; + + void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; + void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; + + bool ContainsExtraData() const noexcept + { + return !IsExtraDataEquals(Entry{}); + } + + bool CanBeMerged(const Entry& acRhs) const noexcept + { + return BaseId == acRhs.BaseId && IsExtraDataEquals(acRhs); + } + + bool IsExtraDataEquals(const Entry& acRhs) const noexcept + { + return ExtraCharge == acRhs.ExtraCharge && + ExtraEnchantId == acRhs.ExtraEnchantId && + ExtraEnchantCharge == acRhs.ExtraEnchantCharge && + ExtraEnchantRemoveUnequip == acRhs.ExtraEnchantRemoveUnequip && + ExtraHealth == acRhs.ExtraHealth && + ExtraPoisonId == acRhs.ExtraPoisonId && + ExtraPoisonCount == acRhs.ExtraPoisonCount && + ExtraSoulLevel == acRhs.ExtraSoulLevel && + //ExtraTextDisplayName == acRhs.ExtraTextDisplayName && + ExtraWorn == acRhs.ExtraWorn && + ExtraWornLeft == acRhs.ExtraWornLeft; + } + }; + Inventory() = default; ~Inventory() = default; @@ -17,15 +101,5 @@ struct Inventory void Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept; void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; - String Buffer{}; - bool IsWeaponDrawn{}; - GameId RightHandWeapon{}; - -#if TP_SKYRIM - GameId LeftHandWeapon{}; - GameId LeftHandSpell{}; - GameId RightHandSpell{}; - GameId Shout{}; - GameId Ammo{}; -#endif + Vector Entries{}; }; From 86319bc55ac581e7f6cd442ca9e4a56274a2b43c Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 15 Feb 2022 15:36:53 +0100 Subject: [PATCH 29/48] fix: build fixes --- Code/client/Games/Skyrim/Forms/EnchantmentItem.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 3 +++ Code/client/Games/Skyrim/TESObjectREFR.h | 3 +-- Code/client/Services/Debug/TestService.cpp | 2 ++ Code/encoding/Structs/Inventory.cpp | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h index 6a6ce9f3f..ec637227a 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h @@ -2,7 +2,7 @@ #include "MagicItem.h" -#include +#include #include #include "BGSListForm.h" diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 280d03858..025e63b8a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -180,6 +180,9 @@ Inventory TESObjectREFR::GetInventory() const noexcept auto pExtraContChangesEntries = GetContainerChanges()->entries; for (auto pGameEntry : *pExtraContChangesEntries) { + if (!pGameEntry) + continue; + Inventory::Entry entry; modSystem.GetServerModId(pGameEntry->form->formID, entry.BaseId); entry.Count = pGameEntry->count; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 5fe26f249..287956c6a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -9,9 +9,8 @@ #include #include #include -#include +#include #include -#include struct AnimationVariables; struct TESWorldSpace; diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index aa8d1f683..af308d139 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -86,6 +86,8 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept static char s_address[256] = "127.0.0.1:10578"; if (!m_transport.IsOnline()) m_transport.Connect(s_address); + else + m_transport.Close(); } } else diff --git a/Code/encoding/Structs/Inventory.cpp b/Code/encoding/Structs/Inventory.cpp index 1ad4086c8..422ec43bc 100644 --- a/Code/encoding/Structs/Inventory.cpp +++ b/Code/encoding/Structs/Inventory.cpp @@ -1,4 +1,4 @@ -#include +#include #include using TiltedPhoques::Serialization; From d2f4077f22b245d823e1dc576a7358af67b778e0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 17 Feb 2022 19:42:44 +0100 Subject: [PATCH 30/48] fix: serialization bugs --- Code/client/Games/BSAnimationGraphManager.cpp | 1 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 5 +++-- Code/encoding/Structs/Inventory.cpp | 5 ++--- Code/server/Services/EnvironmentService.cpp | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Code/client/Games/BSAnimationGraphManager.cpp b/Code/client/Games/BSAnimationGraphManager.cpp index d6481e186..c40120acb 100644 --- a/Code/client/Games/BSAnimationGraphManager.cpp +++ b/Code/client/Games/BSAnimationGraphManager.cpp @@ -59,6 +59,7 @@ uint64_t BSAnimationGraphManager::GetDescriptorKey(int aForceIndex) using TiltedPhoques::FHash::Crc64; String variableNames{}; + variableNames.reserve(8192); std::map variables; if (animationGraphIndex < animationGraphs.size) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 025e63b8a..cb5addcbd 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -324,7 +324,7 @@ void TESObjectREFR::SetInventory(Inventory& acContainer) noexcept RemoveAllItems(); Inventory currentContainer = GetInventory(); - for (auto currentEntry : currentContainer.Entries) + for (const auto& currentEntry : currentContainer.Entries) { auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { return newEntry.CanBeMerged(currentEntry); @@ -336,7 +336,8 @@ void TESObjectREFR::SetInventory(Inventory& acContainer) noexcept } else { - acContainer.Entries.push_back(*duplicate); + // TODO: revisit + acContainer.Entries.push_back(currentEntry); Inventory::Entry& back = acContainer.Entries.back(); back.Count *= -1; } diff --git a/Code/encoding/Structs/Inventory.cpp b/Code/encoding/Structs/Inventory.cpp index 422ec43bc..0b2954ac7 100644 --- a/Code/encoding/Structs/Inventory.cpp +++ b/Code/encoding/Structs/Inventory.cpp @@ -18,7 +18,6 @@ void Inventory::EffectItem::Deserialize(TiltedPhoques::Buffer::Reader& aReader) Area = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; Duration = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; RawCost = Serialization::ReadFloat(aReader); - uint32_t count = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; EffectId.Deserialize(aReader); } @@ -61,8 +60,8 @@ void Inventory::Entry::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexc ExtraEnchantId.Deserialize(aReader); ExtraEnchantCharge = Serialization::ReadVarInt(aReader) & 0xFFFF; - uint32_t effectCount = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; - for (uint32_t i = 0; i < effectCount; i++) + uint64_t effectCount = Serialization::ReadVarInt(aReader); + for (uint64_t i = 0; i < effectCount; i++) { EffectItem effect; effect.Deserialize(aReader); diff --git a/Code/server/Services/EnvironmentService.cpp b/Code/server/Services/EnvironmentService.cpp index 9cee2253e..7c6b08796 100644 --- a/Code/server/Services/EnvironmentService.cpp +++ b/Code/server/Services/EnvironmentService.cpp @@ -84,7 +84,7 @@ void EnvironmentService::OnAssignObjectsRequest(const PacketEvent(*iter); - objectData.CurrentInventory.Buffer = inventoryComponent.Content.Buffer; + objectData.CurrentInventory = inventoryComponent.Content; response.Objects.push_back(objectData); } From 5687a6e7197fbaf0814a610c52c2ea202d00b3f6 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 17 Feb 2022 20:07:32 +0100 Subject: [PATCH 31/48] tweak: restored lock --- Code/client/Games/ExtraDataList.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Code/client/Games/ExtraDataList.cpp b/Code/client/Games/ExtraDataList.cpp index a816236bd..f1d384a55 100644 --- a/Code/client/Games/ExtraDataList.cpp +++ b/Code/client/Games/ExtraDataList.cpp @@ -40,8 +40,7 @@ bool ExtraDataList::Add(ExtraData aType, BSExtraData* apNewData) if (Contains(aType)) return false; - // TODO: this sometimes causes a deadlock - //BSScopedLock _(lock); + BSScopedLock _(lock); BSExtraData* pNext = data; data = apNewData; From de83841174bc37b18bb8ca34c9b24f729b0768dc Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 17 Feb 2022 20:57:26 +0100 Subject: [PATCH 32/48] tweak: EquipManager function calls --- Code/client/Games/Skyrim/Actor.cpp | 107 ++++++++++++++++++++- Code/client/Games/Skyrim/Actor.h | 2 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 20 ++-- Code/encoding/Structs/Inventory.cpp | 67 +++++++++++++ Code/encoding/Structs/Inventory.h | 9 ++ 5 files changed, 197 insertions(+), 8 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index fbb45f570..cc811fc93 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -317,6 +317,111 @@ void Actor::SetFactionRank(const TESFaction* apFaction, int8_t aRank) noexcept ThisCall(s_setFactionRankInternal, this, apFaction, aRank); } +Inventory Actor::GetActorInventory() const noexcept +{ + Inventory inventory = GetInventory(); + + auto& modSystem = World::Get().GetModSystem(); + + auto pMainHandWeapon = GetEquippedWeapon(0); + uint32_t mainId = pMainHandWeapon ? pMainHandWeapon->formID : 0; + modSystem.GetServerModId(mainId, inventory.LeftHandWeapon); + + auto pSecondaryHandWeapon = GetEquippedWeapon(1); + uint32_t secondaryId = pSecondaryHandWeapon ? pSecondaryHandWeapon->formID : 0; + modSystem.GetServerModId(secondaryId, inventory.RightHandWeapon); + + mainId = magicItems[0] ? magicItems[0]->formID : 0; + modSystem.GetServerModId(mainId, inventory.LeftHandSpell); + + secondaryId = magicItems[1] ? magicItems[1]->formID : 0; + modSystem.GetServerModId(secondaryId, inventory.RightHandSpell); + + uint32_t shoutId = equippedShout ? equippedShout->formID : 0; + modSystem.GetServerModId(shoutId, inventory.Shout); + + auto pAmmo = GetEquippedAmmo(); + uint32_t ammoId = pAmmo ? pAmmo->formID : 0; + modSystem.GetServerModId(ammoId, inventory.Ammo); + + return inventory; +} + +void Actor::SetActorInventory(Inventory& aInventory) noexcept +{ + UnEquipAll(); + + SetInventory(aInventory); + + auto* pEquipManager = EquipManager::Get(); + auto& modSystem = World::Get().GetModSystem(); + + uint32_t mainHandWeaponId = modSystem.GetGameId(aInventory.LeftHandWeapon); + + if (mainHandWeaponId) + pEquipManager->Equip(this, TESForm::GetById(mainHandWeaponId), nullptr, 1, DefaultObjectManager::Get().leftEquipSlot, false, true, false, false); + + uint32_t secondaryHandWeaponId = modSystem.GetGameId(aInventory.RightHandWeapon); + + if (secondaryHandWeaponId) + pEquipManager->Equip(this, TESForm::GetById(secondaryHandWeaponId), nullptr, 1, DefaultObjectManager::Get().rightEquipSlot, false, true, false, false); + + mainHandWeaponId = modSystem.GetGameId(aInventory.LeftHandSpell); + + if (mainHandWeaponId) + pEquipManager->EquipSpell(this, TESForm::GetById(mainHandWeaponId), 0); + + secondaryHandWeaponId = modSystem.GetGameId(aInventory.RightHandSpell); + + if (secondaryHandWeaponId) + pEquipManager->EquipSpell(this, TESForm::GetById(secondaryHandWeaponId), 1); + + uint32_t shoutId = modSystem.GetGameId(aInventory.Shout); + + if (shoutId) + pEquipManager->EquipShout(this, TESForm::GetById(shoutId)); + + uint32_t ammoId = modSystem.GetGameId(aInventory.Ammo); + + if (ammoId) + { + TESForm* pAmmo = TESForm::GetById(ammoId); + + auto count = GetItemCountInInventory(pAmmo); + + /* + const auto pContainerChanges = GetContainerChanges()->entries; + for (auto pChange : *pContainerChanges) + { + if (pChange && pChange->form && pChange->form->formID == ammoId) + { + if (pChange->form->formID != ammoId) + continue; + + const auto pDataLists = pChange->dataList; + for (auto* pDataList : *pDataLists) + { + if (pDataList) + { + if (pDataList->Contains(ExtraData::Count)) + { + BSExtraData* pExtraData = pDataList->GetByType(ExtraData::Count); + ExtraCount* pExtraCount = RTTI_CAST(pExtraData, BSExtraData, ExtraCount); + if (pExtraCount) + { + pExtraCount->count = 0; + } + } + } + } + } + } + */ + + pEquipManager->Equip(this, pAmmo, nullptr, count, DefaultObjectManager::Get().rightEquipSlot, false, true, false, false); + } +} + void Actor::UnEquipAll() noexcept { // For each change @@ -349,8 +454,6 @@ void Actor::UnEquipAll() noexcept } } - RemoveAllItems(); - // Taken from skyrim's code shouts can be two form types apparently if (equippedShout && ((int)equippedShout->formType - 41) <= 1) { diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 803d12a5d..73689c250 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -190,6 +190,7 @@ struct Actor : TESObjectREFR TESForm *GetCurrentLocation(); float GetActorValue(uint32_t aId) const noexcept; float GetActorMaxValue(uint32_t aId) const noexcept; + Inventory GetActorInventory() const noexcept; Factions GetFactions() const noexcept; ActorValues GetEssentialActorValues() const noexcept; @@ -205,6 +206,7 @@ struct Actor : TESObjectREFR void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; void SetPackage(TESPackage* apPackage) noexcept; + void SetActorInventory(Inventory& aInventory) noexcept; // Actions void UnEquipAll() noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index cb5addcbd..22e10ae5b 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -20,6 +20,7 @@ #include #include #include +#include TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing); TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); @@ -319,31 +320,31 @@ Inventory TESObjectREFR::GetInventory() const noexcept return inventory; } -void TESObjectREFR::SetInventory(Inventory& acContainer) noexcept +void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept { RemoveAllItems(); Inventory currentContainer = GetInventory(); for (const auto& currentEntry : currentContainer.Entries) { - auto duplicate = std::find_if(acContainer.Entries.begin(), acContainer.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { + auto duplicate = std::find_if(aInventory.Entries.begin(), aInventory.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { return newEntry.CanBeMerged(currentEntry); }); - if (duplicate != std::end(acContainer.Entries)) + if (duplicate != std::end(aInventory.Entries)) { duplicate->Count -= currentEntry.Count; } else { // TODO: revisit - acContainer.Entries.push_back(currentEntry); - Inventory::Entry& back = acContainer.Entries.back(); + aInventory.Entries.push_back(currentEntry); + Inventory::Entry& back = aInventory.Entries.back(); back.Count *= -1; } } - for (const Inventory::Entry& entry : acContainer.Entries) + for (const Inventory::Entry& entry : aInventory.Entries) { if (entry.Count != 0) AddItem(entry); @@ -455,6 +456,13 @@ void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, pExtraDataList ? pExtraDataList->GetCount() : -1, arEntry.Count); + + + // TODO: if worn, call equip + if (pExtraDataList && pExtraDataList->Contains(ExtraData::Worn)) + { + auto* pEquipManager = EquipManager::Get(); + } } void TESObjectREFR::Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* aObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept diff --git a/Code/encoding/Structs/Inventory.cpp b/Code/encoding/Structs/Inventory.cpp index 0b2954ac7..9bc2751cd 100644 --- a/Code/encoding/Structs/Inventory.cpp +++ b/Code/encoding/Structs/Inventory.cpp @@ -113,6 +113,44 @@ void Inventory::Serialize(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { entry.Serialize(aWriter); } + + bool isRightWeaponSet = RightHandWeapon != GameId{}; +#if TP_SKYRIM + bool isLeftWeaponSet = LeftHandWeapon != GameId{}; + bool isLeftSpellSet = LeftHandSpell != GameId{}; + bool isRightSpellSet = RightHandSpell != GameId{}; + bool isShoutSet = Shout != GameId{}; + bool isAmmoSet = Ammo != GameId{}; +#endif + + Serialization::WriteBool(aWriter, isRightWeaponSet); +#if TP_SKYRIM + Serialization::WriteBool(aWriter, isLeftWeaponSet); + Serialization::WriteBool(aWriter, isLeftSpellSet); + Serialization::WriteBool(aWriter, isRightSpellSet); + Serialization::WriteBool(aWriter, isShoutSet); + Serialization::WriteBool(aWriter, isAmmoSet); +#endif + + if (isRightWeaponSet) + RightHandWeapon.Serialize(aWriter); + +#if TP_SKYRIM + if (isLeftWeaponSet) + LeftHandWeapon.Serialize(aWriter); + + if (isLeftSpellSet) + LeftHandSpell.Serialize(aWriter); + + if (isRightSpellSet) + RightHandSpell.Serialize(aWriter); + + if (isShoutSet) + Shout.Serialize(aWriter); + + if (isAmmoSet) + Ammo.Serialize(aWriter); +#endif } void Inventory::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -124,4 +162,33 @@ void Inventory::Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept entry.Deserialize(aReader); Entries.push_back(entry); } + + bool isRightWeaponSet = Serialization::ReadBool(aReader); +#if TP_SKYRIM + bool isLeftWeaponSet = Serialization::ReadBool(aReader); + bool isLeftSpellSet = Serialization::ReadBool(aReader); + bool isRightSpellSet = Serialization::ReadBool(aReader); + bool isShoutSet = Serialization::ReadBool(aReader); + bool isAmmoSet = Serialization::ReadBool(aReader); +#endif + + if (isRightWeaponSet) + RightHandWeapon.Deserialize(aReader); + +#if TP_SKYRIM + if (isLeftWeaponSet) + LeftHandWeapon.Deserialize(aReader); + + if (isLeftSpellSet) + LeftHandSpell.Deserialize(aReader); + + if (isRightSpellSet) + RightHandSpell.Deserialize(aReader); + + if (isShoutSet) + Shout.Deserialize(aReader); + + if (isAmmoSet) + Ammo.Deserialize(aReader); +#endif } diff --git a/Code/encoding/Structs/Inventory.h b/Code/encoding/Structs/Inventory.h index 65e30832f..f464fd8d1 100644 --- a/Code/encoding/Structs/Inventory.h +++ b/Code/encoding/Structs/Inventory.h @@ -102,4 +102,13 @@ struct Inventory void Deserialize(TiltedPhoques::Buffer::Reader& aReader) noexcept; Vector Entries{}; + + GameId RightHandWeapon{}; +#if TP_SKYRIM + GameId LeftHandWeapon{}; + GameId LeftHandSpell{}; + GameId RightHandSpell{}; + GameId Shout{}; + GameId Ammo{}; +#endif }; From 7c9870bf5d7e2ab2c88d936a9bb257a7f84638e7 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 17 Feb 2022 21:20:46 +0100 Subject: [PATCH 33/48] tweak: enumerate calls to SetActorInventory() --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 2 +- Code/client/Services/Debug/TestService.cpp | 8 ++++---- Code/client/Services/Generic/CharacterService.cpp | 6 +++--- Code/client/Services/Generic/InventoryService.cpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 22e10ae5b..ae15eb5ac 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -455,7 +455,7 @@ void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, - pExtraDataList ? pExtraDataList->GetCount() : -1, arEntry.Count); + pExtraDataList ? (int)pExtraDataList->GetCount() : -1, arEntry.Count); // TODO: if worn, call equip diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index af308d139..32cb75ae5 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -57,9 +57,9 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); - Inventory inventory = PlayerCharacter::Get()->GetInventory(); + Inventory inventory = PlayerCharacter::Get()->GetActorInventory(); spdlog::info("Inventory size: {}", inventory.Entries.size()); - pActor->SetInventory(inventory); + pActor->SetActorInventory(inventory); m_actors.emplace_back(pActor); } @@ -106,8 +106,8 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept //PlaceActorInWorld(); Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); - auto container = PlayerCharacter::Get()->GetInventory(); - pActor->SetInventory(container); + auto container = PlayerCharacter::Get()->GetActorInventory(); + pActor->SetActorInventory(container); } } else diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 81ff73a6c..2a8391c27 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -408,7 +408,7 @@ void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessag return; pActor->SetActorValues(remoteComponent.SpawnRequest.InitialActorValues); - pActor->SetInventory(remoteComponent.SpawnRequest.InventoryContent); + pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); @@ -674,7 +674,7 @@ void CharacterService::RequestServerAssignment(entt::registry& aRegistry, const questLog.resize(std::distance(questLog.begin(), ip)); } - message.InventoryContent = pActor->GetInventory(); + message.InventoryContent = pActor->GetActorInventory(); message.FactionsContent = pActor->GetFactions(); message.AllActorValues = pActor->GetEssentialActorValues(); message.IsDead = pActor->IsDead(); @@ -908,7 +908,7 @@ void CharacterService::RunRemoteUpdates() const noexcept if (!pActor || !pActor->GetNiNode()) continue; - pActor->SetInventory(remoteComponent.SpawnRequest.InventoryContent); + pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); pActor->SetFactions(remoteComponent.SpawnRequest.FactionsContent); pActor->LoadAnimationVariables(remoteComponent.SpawnRequest.LatestAction.Variables); diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 648f136e0..5fcdabcab 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -191,7 +191,7 @@ void InventoryService::RunCharacterInventoryUpdates() noexcept if (!pActor) continue; - message.Changes[serverId] = pActor->GetInventory(); + message.Changes[serverId] = pActor->GetActorInventory(); } m_transport.Send(message); @@ -259,7 +259,7 @@ void InventoryService::ApplyCachedCharacterInventoryChanges() noexcept auto& remoteComponent = m_world.get(*it); remoteComponent.SpawnRequest.InventoryContent = inventory; - pActor->SetInventory(inventory); + pActor->SetActorInventory(inventory); } m_cachedCharacterInventoryChanges.clear(); From bc7c73fdc587833b20ede614ccec40feea612c77 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 22 Feb 2022 18:59:08 +0100 Subject: [PATCH 34/48] tweak: address lib for inventory stuff --- Code/client/Games/ExtraDataList.cpp | 13 ++--- Code/client/Games/Skyrim/Actor.cpp | 57 +------------------ .../Games/Skyrim/Forms/EnchantmentItem.cpp | 8 +-- 3 files changed, 13 insertions(+), 65 deletions(-) diff --git a/Code/client/Games/ExtraDataList.cpp b/Code/client/Games/ExtraDataList.cpp index f1d384a55..ac0d55bb3 100644 --- a/Code/client/Games/ExtraDataList.cpp +++ b/Code/client/Games/ExtraDataList.cpp @@ -75,18 +75,17 @@ void ExtraDataList::SetType(ExtraData aType, bool aClear) flag |= bitmask; } - void ExtraDataList::SetSoulData(SOUL_LEVEL aSoulLevel) noexcept { TP_THIS_FUNCTION(TSetSoulData, void, ExtraDataList, SOUL_LEVEL aSoulLevel); - POINTER_SKYRIMSE(TSetSoulData, setSoulData, 0x14011AF60 - 0x140000000); + POINTER_SKYRIMSE(TSetSoulData, setSoulData, 11620); ThisCall(setSoulData, this, aSoulLevel); } void ExtraDataList::SetChargeData(float aCharge) noexcept { TP_THIS_FUNCTION(TSetChargeData, void, ExtraDataList, float aCharge); - POINTER_SKYRIMSE(TSetChargeData, setChargeData, 0x14011AF60 - 0x140000000); + POINTER_SKYRIMSE(TSetChargeData, setChargeData, 11619); ThisCall(setChargeData, this, aCharge); } @@ -94,27 +93,27 @@ void ExtraDataList::SetWorn(bool aWornLeft) noexcept { // TODO: what's this bool? seems to be true always except for one instance TP_THIS_FUNCTION(TSetWornData, void, ExtraDataList, bool aUnk1, bool aWornLeft); - POINTER_SKYRIMSE(TSetWornData, setWornData, 0x14011A5D0 - 0x140000000); + POINTER_SKYRIMSE(TSetWornData, setWornData, 11612); ThisCall(setWornData, this, true, aWornLeft); } void ExtraDataList::SetPoison(AlchemyItem* apItem, uint32_t aCount) noexcept { TP_THIS_FUNCTION(TSetPoison, void, ExtraDataList, AlchemyItem* apItem, uint32_t aCount); - POINTER_SKYRIMSE(TSetPoison, setPoison, 0x140124160 - 0x140000000); + POINTER_SKYRIMSE(TSetPoison, setPoison, 11822); ThisCall(setPoison, this, apItem, aCount); } void ExtraDataList::SetHealth(float aHealth) noexcept { TP_THIS_FUNCTION(TSetHealth, void, ExtraDataList, float aHealth); - POINTER_SKYRIMSE(TSetHealth, setHealth, 0x14011AB30 - 0x140000000); + POINTER_SKYRIMSE(TSetHealth, setHealth, 11616); ThisCall(setHealth, this, aHealth); } void ExtraDataList::SetEnchantmentData(EnchantmentItem* apItem, uint16_t aCharge, bool aRemoveOnUnequip) noexcept { TP_THIS_FUNCTION(TSetEnchantmentData, void, ExtraDataList, EnchantmentItem* apItem, uint16_t aCharge, bool aRemoveOnUnequip); - POINTER_SKYRIMSE(TSetEnchantmentData, setEnchantmentData, 0x14012D670 - 0x140000000); + POINTER_SKYRIMSE(TSetEnchantmentData, setEnchantmentData, 12060); ThisCall(setEnchantmentData, this, apItem, aCharge, aRemoveOnUnequip); } diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index a40d6c861..e686413a1 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -255,66 +255,15 @@ float Actor::GetActorMaxValue(uint32_t aId) const noexcept return actorValueOwner.GetMaxValue(aId); } -void Actor::ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept -{ - const float current = GetActorValue(aId); - actorValueOwner.ForceCurrent(aMode, aId, aValue - current); -} - void Actor::SetActorValue(uint32_t aId, float aValue) noexcept { actorValueOwner.SetValue(aId, aValue); } -void Actor::SetActorValues(const ActorValues& acActorValues) noexcept -{ - for (auto& value : acActorValues.ActorMaxValuesList) - { - float current = actorValueOwner.GetValue(value.first); - actorValueOwner.ForceCurrent(0, value.first, value.second - current); - } - - for (auto& value : acActorValues.ActorValuesList) - { - float current = actorValueOwner.GetValue(value.first); - actorValueOwner.ForceCurrent(2, value.first, value.second - current); - } -} - -void Actor::SetFactions(const Factions& acFactions) noexcept -{ - RemoveFromAllFactions(); - - auto& modSystem = World::Get().GetModSystem(); - - for (auto& entry : acFactions.NpcFactions) - { - auto pForm = TESForm::GetById(modSystem.GetGameId(entry.Id)); - auto pFaction = RTTI_CAST(pForm, TESForm, TESFaction); - if (pFaction) - { - SetFactionRank(pFaction, entry.Rank); - } - } - - for (auto& entry : acFactions.ExtraFactions) - { - auto pForm = TESForm::GetById(modSystem.GetGameId(entry.Id)); - auto pFaction = RTTI_CAST(pForm, TESForm, TESFaction); - if (pFaction) - { - SetFactionRank(pFaction, entry.Rank); - } - } -} - -void Actor::SetFactionRank(const TESFaction* apFaction, int8_t aRank) noexcept +void Actor::ForceActorValue(uint32_t aMode, uint32_t aId, float aValue) noexcept { - TP_THIS_FUNCTION(TSetFactionRankInternal, void, Actor, const TESFaction*, int8_t); - - POINTER_SKYRIMSE(TSetFactionRankInternal, s_setFactionRankInternal, 0x14061E5B0 - 0x140000000); - - ThisCall(s_setFactionRankInternal, this, apFaction, aRank); + const float current = GetActorValue(aId); + actorValueOwner.ForceCurrent(aMode, aId, aValue - current); } Inventory Actor::GetActorInventory() const noexcept diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index 2b5b05e41..9e55c0ead 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -8,13 +8,13 @@ EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData { /* TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); - POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 0x1405C1290 - 0x140000000); + POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 36178); */ TP_THIS_FUNCTION(TAddWeaponEnchantment, EnchantmentItem*, void, GameArray*); - POINTER_SKYRIMSE(TAddWeaponEnchantment, addWeaponEnchantment, 0x1405C0370 - 0x140000000); + POINTER_SKYRIMSE(TAddWeaponEnchantment, addWeaponEnchantment, 36165); TP_THIS_FUNCTION(TAddArmorEnchantment, EnchantmentItem*, void, GameArray*); - POINTER_SKYRIMSE(TAddArmorEnchantment, addArmorEnchantment, 0x1405C0410 - 0x140000000); + POINTER_SKYRIMSE(TAddArmorEnchantment, addArmorEnchantment, 36166); ModSystem& modSystem = World::Get().GetModSystem(); @@ -43,7 +43,7 @@ EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData EnchantmentItem* pItem = ThisCall(createNewEnchantment, &effects, aData.IsWeapon); */ - POINTER_SKYRIMSE(void*, pObjManager, 0x141F592E8 - 0x140000000); + POINTER_SKYRIMSE(void*, pObjManager, 400320); void* objManager = *pObjManager.Get(); From 07f57c14772b2278c9bca8b960ab4263bedf0a9c Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 22 Feb 2022 19:59:38 +0100 Subject: [PATCH 35/48] tweak: guard inventory change event hooks --- Code/client/Games/Skyrim/Actor.cpp | 8 ++++++-- Code/client/Games/Skyrim/TESObjectREFR.cpp | 12 ++++++++++-- Code/client/Services/Debug/TestService.cpp | 5 +++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index e686413a1..7a46bad5f 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -627,15 +627,19 @@ void* TP_MAKE_THISCALL(HookRegenAttributes, Actor, int aId, float aRegenValue) return ThisCall(RealRegenAttributes, apThis, aId, aRegenValue); } +extern thread_local bool g_modifyingInventory; + void TP_MAKE_THISCALL(HookAddInventoryItem, Actor, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) { - World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); + if (!g_modifyingInventory) + World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner); } void* TP_MAKE_THISCALL(HookPickUpItem, Actor, TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2) { - World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); + if (!g_modifyingInventory) + World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); return ThisCall(RealPickUpItem, apThis, apObject, aCount, aUnk1, aUnk2); } diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index d74267e40..bd39cd74a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -320,8 +320,12 @@ Inventory TESObjectREFR::GetInventory() const noexcept return inventory; } +thread_local bool g_modifyingInventory = false; + void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept { + g_modifyingInventory = true; + RemoveAllItems(); Inventory currentContainer = GetInventory(); @@ -349,6 +353,8 @@ void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept if (entry.Count != 0) AddItem(entry); } + + g_modifyingInventory = false; } void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept @@ -544,13 +550,15 @@ void TP_MAKE_THISCALL(HookActivate, TESObjectREFR, TESObjectREFR* apActivator, u void* TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner) { - World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); + if (!g_modifyingInventory) + World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); return ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner); } void* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, ExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3) { - World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); + if (!g_modifyingInventory) + World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); return ThisCall(RealRemoveInventoryItem, apThis, apUnk0, apItem, aCount, aUnk1, apExtraData, apNewOwner, apUnk2, apUnk3); } diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index c2c3909bb..1791b3425 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -118,9 +118,10 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept s_f8Pressed = true; //PlaceActorInWorld(); - Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); + //Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); + TESObjectREFR* pActor = RTTI_CAST(TESForm::GetById(0x2015bc4), TESForm, TESObjectREFR); auto container = PlayerCharacter::Get()->GetActorInventory(); - pActor->SetActorInventory(container); + pActor->SetInventory(container); } } else From 326e6bc60b2f404f0a416431f18d9eededec3ab4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 6 Mar 2022 13:20:40 +0100 Subject: [PATCH 36/48] tweak: equip is done in Actor class --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index bd39cd74a..7f4601716 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -462,13 +462,6 @@ void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, pExtraDataList ? (int)pExtraDataList->GetCount() : -1, arEntry.Count); - - - // TODO: if worn, call equip - if (pExtraDataList && pExtraDataList->Contains(ExtraData::Worn)) - { - auto* pEquipManager = EquipManager::Get(); - } } void TESObjectREFR::Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* aObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept From c19b0f01b0b3e71491aa2a7c77a0c416cb29f3ac Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Wed, 9 Mar 2022 15:44:52 +0100 Subject: [PATCH 37/48] tweak: improved efficiency of inventory system --- Code/client/Games/Skyrim/Actor.cpp | 39 +++---------------- Code/client/Games/Skyrim/Actor.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 44 +++++++++++++--------- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- Code/client/Services/Debug/TestService.cpp | 4 +- 5 files changed, 36 insertions(+), 55 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 7a46bad5f..afccef974 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -296,12 +296,14 @@ Inventory Actor::GetActorInventory() const noexcept return inventory; } -void Actor::SetActorInventory(Inventory& aInventory) noexcept +void Actor::SetActorInventory(Inventory& aInventory, bool aReset) noexcept { - UnEquipAll(); + if (aReset) + UnEquipAll(); SetInventory(aInventory); + // TODO: if aReset is false, diff currently equipped weapons first auto* pEquipManager = EquipManager::Get(); auto& modSystem = World::Get().GetModSystem(); @@ -335,38 +337,7 @@ void Actor::SetActorInventory(Inventory& aInventory) noexcept if (ammoId) { TESForm* pAmmo = TESForm::GetById(ammoId); - - auto count = GetItemCountInInventory(pAmmo); - - /* - const auto pContainerChanges = GetContainerChanges()->entries; - for (auto pChange : *pContainerChanges) - { - if (pChange && pChange->form && pChange->form->formID == ammoId) - { - if (pChange->form->formID != ammoId) - continue; - - const auto pDataLists = pChange->dataList; - for (auto* pDataList : *pDataLists) - { - if (pDataList) - { - if (pDataList->Contains(ExtraData::Count)) - { - BSExtraData* pExtraData = pDataList->GetByType(ExtraData::Count); - ExtraCount* pExtraCount = RTTI_CAST(pExtraData, BSExtraData, ExtraCount); - if (pExtraCount) - { - pExtraCount->count = 0; - } - } - } - } - } - } - */ - + int64_t count = GetItemCountInInventory(pAmmo); pEquipManager->Equip(this, pAmmo, nullptr, count, DefaultObjectManager::Get().rightEquipSlot, false, true, false, false); } } diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 73689c250..522db9eb0 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -206,7 +206,7 @@ struct Actor : TESObjectREFR void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; void SetPackage(TESPackage* apPackage) noexcept; - void SetActorInventory(Inventory& aInventory) noexcept; + void SetActorInventory(Inventory& aInventory, bool aReset = true) noexcept; // Actions void UnEquipAll() noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 7f4601716..3681d49e6 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -315,6 +315,12 @@ Inventory TESObjectREFR::GetInventory() const noexcept inventory.Entries.insert(inventory.Entries.end(), minimizedExtraInventory.Entries.begin(), minimizedExtraInventory.Entries.end()); + spdlog::info("Inventory count before: {}", inventory.Entries.size()); + + inventory.Entries.erase(std::remove_if(inventory.Entries.begin(), inventory.Entries.end(), + [](const Inventory::Entry& entry) { return entry.Count == 0; }), + inventory.Entries.end()); + spdlog::info("Inventory count after: {}", inventory.Entries.size()); return inventory; @@ -322,29 +328,31 @@ Inventory TESObjectREFR::GetInventory() const noexcept thread_local bool g_modifyingInventory = false; -void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept +void TESObjectREFR::SetInventory(Inventory& aInventory, bool aReset) noexcept { g_modifyingInventory = true; - RemoveAllItems(); - - Inventory currentContainer = GetInventory(); - for (const auto& currentEntry : currentContainer.Entries) + if (aReset) + RemoveAllItems(); + else { - auto duplicate = std::find_if(aInventory.Entries.begin(), aInventory.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { - return newEntry.CanBeMerged(currentEntry); - }); - - if (duplicate != std::end(aInventory.Entries)) - { - duplicate->Count -= currentEntry.Count; - } - else + Inventory currentContainer = GetInventory(); + for (const auto& currentEntry : currentContainer.Entries) { - // TODO: revisit - aInventory.Entries.push_back(currentEntry); - Inventory::Entry& back = aInventory.Entries.back(); - back.Count *= -1; + auto duplicate = std::find_if(aInventory.Entries.begin(), aInventory.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { + return newEntry.CanBeMerged(currentEntry); + }); + + if (duplicate != std::end(aInventory.Entries)) + { + duplicate->Count -= currentEntry.Count; + } + else + { + aInventory.Entries.push_back(currentEntry); + Inventory::Entry& back = aInventory.Entries.back(); + back.Count *= -1; + } } } diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 3a7761687..d167eaf74 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -176,7 +176,7 @@ struct TESObjectREFR : TESForm void EnableImpl() noexcept; Inventory GetInventory() const noexcept; - void SetInventory(Inventory& acContainer) noexcept; + void SetInventory(Inventory& acContainer, bool aReset = true) noexcept; void AddItem(const Inventory::Entry& arEntry) noexcept; diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index 1791b3425..af83ce14e 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -117,11 +117,13 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - //PlaceActorInWorld(); + PlaceActorInWorld(); + /* //Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); TESObjectREFR* pActor = RTTI_CAST(TESForm::GetById(0x2015bc4), TESForm, TESObjectREFR); auto container = PlayerCharacter::Get()->GetActorInventory(); pActor->SetInventory(container); + */ } } else From d13b074bc01710b36ba451b8152b43b77678a0a0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Wed, 9 Mar 2022 16:10:06 +0100 Subject: [PATCH 38/48] tweak: inventory system clean up --- Code/client/Games/BGSCreatedObjectManager.cpp | 8 ++++ Code/client/Games/BGSCreatedObjectManager.h | 6 +++ Code/client/Games/Skyrim/Actor.cpp | 5 +-- .../Games/Skyrim/Forms/EnchantmentItem.cpp | 42 ++----------------- .../Games/Skyrim/Forms/EnchantmentItem.h | 3 -- Code/client/Games/Skyrim/TESObjectREFR.cpp | 12 +++--- Code/client/Services/Debug/TestService.cpp | 7 ---- 7 files changed, 25 insertions(+), 58 deletions(-) create mode 100644 Code/client/Games/BGSCreatedObjectManager.cpp create mode 100644 Code/client/Games/BGSCreatedObjectManager.h diff --git a/Code/client/Games/BGSCreatedObjectManager.cpp b/Code/client/Games/BGSCreatedObjectManager.cpp new file mode 100644 index 000000000..0f784b31d --- /dev/null +++ b/Code/client/Games/BGSCreatedObjectManager.cpp @@ -0,0 +1,8 @@ +#include "BGSCreatedObjectManager.h" + +BGSCreatedObjectManager* BGSCreatedObjectManager::Get() noexcept +{ + POINTER_SKYRIMSE(BGSCreatedObjectManager*, pObjManager, 400320); + + return *pObjManager.Get(); +} diff --git a/Code/client/Games/BGSCreatedObjectManager.h b/Code/client/Games/BGSCreatedObjectManager.h new file mode 100644 index 000000000..ac357a1a7 --- /dev/null +++ b/Code/client/Games/BGSCreatedObjectManager.h @@ -0,0 +1,6 @@ +#pragma once + +struct BGSCreatedObjectManager +{ + static BGSCreatedObjectManager* Get() noexcept; +}; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index afccef974..f4776dcb3 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -691,8 +691,7 @@ static TiltedPhoques::Initializer s_actorHooks([]() RealDamageActor = s_damageActor.Get(); RealApplyActorEffect = s_applyActorEffect.Get(); RealRegenAttributes = s_regenAttributes.Get(); - // TODO: re-enable this hook (obviously interferes with AddItem()) - //RealAddInventoryItem = s_addInventoryItem.Get(); + RealAddInventoryItem = s_addInventoryItem.Get(); RealPickUpItem = s_pickUpItem.Get(); RealUpdateDetectionState = s_updateDetectionState.Get(); RealProcessResponse = s_processResponse.Get(); @@ -705,7 +704,7 @@ static TiltedPhoques::Initializer s_actorHooks([]() TP_HOOK(&RealDamageActor, HookDamageActor); TP_HOOK(&RealApplyActorEffect, HookApplyActorEffect); TP_HOOK(&RealRegenAttributes, HookRegenAttributes); - //TP_HOOK(&RealAddInventoryItem, HookAddInventoryItem); + TP_HOOK(&RealAddInventoryItem, HookAddInventoryItem); TP_HOOK(&RealPickUpItem, HookPickUpItem); TP_HOOK(&RealUpdateDetectionState, HookUpdateDetectionState); TP_HOOK(&RealProcessResponse, HookProcessResponse); diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp index 9e55c0ead..c9f73998d 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.cpp @@ -3,14 +3,10 @@ #include #include #include +#include EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData) noexcept { - /* - TP_THIS_FUNCTION(TCreateNewEnchantment, EnchantmentItem*, GameArray, bool abIsWeapon); - POINTER_SKYRIMSE(TCreateNewEnchantment, createNewEnchantment, 36178); - */ - TP_THIS_FUNCTION(TAddWeaponEnchantment, EnchantmentItem*, void, GameArray*); POINTER_SKYRIMSE(TAddWeaponEnchantment, addWeaponEnchantment, 36165); TP_THIS_FUNCTION(TAddArmorEnchantment, EnchantmentItem*, void, GameArray*); @@ -34,18 +30,12 @@ EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData if (!effectItem.pEffectSetting) spdlog::error("Effect setting not found: {:X}:{:X}", effect.EffectId.ModId, effect.EffectId.BaseId); - // TODO: TESCondition + // TODO: TESCondition? effects[i] = effectItem; } - /* - EnchantmentItem* pItem = ThisCall(createNewEnchantment, &effects, aData.IsWeapon); - */ - - POINTER_SKYRIMSE(void*, pObjManager, 400320); - - void* objManager = *pObjManager.Get(); + BGSCreatedObjectManager* objManager = BGSCreatedObjectManager::Get(); EnchantmentItem* pItem = nullptr; if (aData.IsWeapon) @@ -60,29 +50,3 @@ EnchantmentItem* EnchantmentItem::Create(const Inventory::EnchantmentData& aData return pItem; } -void EnchantmentItem::Init(const Inventory::EnchantmentData& aData) -{ - /* - iCostOverride = aData.CostOverride; - iFlags = aData.Flags; - eCastingType = static_cast(aData.CastingType); - iChargeOverride = aData.ChargeOverride; - eDelivery = static_cast(aData.Delivery); - eSpellType = static_cast(aData.SpellType); - fChargeTime = aData.ChargeTime; - - ModSystem& modSystem = World::Get().GetModSystem(); - - TP_ASSERT(aData.BaseEnchantmentId.ModId != 0xFFFFFFFF, "Base enchantment is a temporary!"); - uint32_t baseEnchantId = modSystem.GetGameId(aData.BaseEnchantmentId); - pBaseEnchantment = RTTI_CAST(TESForm::GetById(baseEnchantId), TESForm, EnchantmentItem); - if (!pBaseEnchantment) - spdlog::error("{}: base enchantment not found.", __FUNCTION__); - - TP_ASSERT(aData.WornRestrictionsId.ModId != 0xFFFFFFFF, "Worn restrictions is a temporary!"); - uint32_t restrictionsId = modSystem.GetGameId(aData.WornRestrictionsId); - pWornRestrictions = RTTI_CAST(TESForm::GetById(restrictionsId), TESForm, BGSListForm); - if (!pWornRestrictions) - spdlog::error("{}: worn restrictions not found.", __FUNCTION__); - */ -} diff --git a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h index ec637227a..e85dcc124 100644 --- a/Code/client/Games/Skyrim/Forms/EnchantmentItem.h +++ b/Code/client/Games/Skyrim/Forms/EnchantmentItem.h @@ -10,8 +10,6 @@ struct EnchantmentItem : MagicItem { static EnchantmentItem* Create(const Inventory::EnchantmentData& aData) noexcept; - void Init(const Inventory::EnchantmentData& aData); - int32_t iCostOverride; int32_t iFlags; MagicSystem::CastingType eCastingType; @@ -20,7 +18,6 @@ struct EnchantmentItem : MagicItem MagicSystem::SpellType eSpellType; float fChargeTime; EnchantmentItem* pBaseEnchantment; - // TODO: use BGSListForm::SaveGame() and BGSListForm::LoadGame()? BGSListForm* pWornRestrictions; }; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 3681d49e6..77e266b92 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -273,7 +273,7 @@ Inventory TESObjectREFR::GetInventory() const noexcept extraInventory.Entries.push_back(std::move(entry)); } - spdlog::info("ExtraInventory count: {}", extraInventory.Entries.size()); + spdlog::debug("ExtraInventory count: {}", extraInventory.Entries.size()); Inventory minimizedExtraInventory{}; @@ -292,7 +292,7 @@ Inventory TESObjectREFR::GetInventory() const noexcept duplicate->Count += entry.Count; } - spdlog::info("MinExtraInventory count: {}", minimizedExtraInventory.Entries.size()); + spdlog::debug("MinExtraInventory count: {}", minimizedExtraInventory.Entries.size()); for (auto& entry : minimizedExtraInventory.Entries) { @@ -310,18 +310,18 @@ Inventory TESObjectREFR::GetInventory() const noexcept duplicate->Count = 0; } - spdlog::info("MinExtraInventory count after: {}", minimizedExtraInventory.Entries.size()); + spdlog::debug("MinExtraInventory count after: {}", minimizedExtraInventory.Entries.size()); inventory.Entries.insert(inventory.Entries.end(), minimizedExtraInventory.Entries.begin(), minimizedExtraInventory.Entries.end()); - spdlog::info("Inventory count before: {}", inventory.Entries.size()); + spdlog::debug("Inventory count before: {}", inventory.Entries.size()); inventory.Entries.erase(std::remove_if(inventory.Entries.begin(), inventory.Entries.end(), [](const Inventory::Entry& entry) { return entry.Count == 0; }), inventory.Entries.end()); - spdlog::info("Inventory count after: {}", inventory.Entries.size()); + spdlog::debug("Inventory count after: {}", inventory.Entries.size()); return inventory; } @@ -468,7 +468,7 @@ void TESObjectREFR::AddItem(const Inventory::Entry& arEntry) noexcept } AddObjectToContainer(pObject, pExtraDataList, arEntry.Count, nullptr); - spdlog::info("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, + spdlog::debug("Added object to container, form id: {:X}, extra data count: {}, entry count: {}", pObject->formID, pExtraDataList ? (int)pExtraDataList->GetCount() : -1, arEntry.Count); } diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index af83ce14e..a4c5827f2 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -71,7 +71,6 @@ void __declspec(noinline) TestService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); Inventory inventory = PlayerCharacter::Get()->GetActorInventory(); - spdlog::info("Inventory size: {}", inventory.Entries.size()); pActor->SetActorInventory(inventory); m_actors.emplace_back(pActor); @@ -118,12 +117,6 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept s_f8Pressed = true; PlaceActorInWorld(); - /* - //Actor* pActor = RTTI_CAST(TESForm::GetById(0x1348C), TESForm, Actor); - TESObjectREFR* pActor = RTTI_CAST(TESForm::GetById(0x2015bc4), TESForm, TESObjectREFR); - auto container = PlayerCharacter::Get()->GetActorInventory(); - pActor->SetInventory(container); - */ } } else From 53dcc69886f69e6d8a519fd19a77c6d3d6fc9e8c Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Wed, 9 Mar 2022 16:21:16 +0100 Subject: [PATCH 39/48] tweak: further cleanup --- Code/client/Games/Skyrim/Actor.cpp | 6 ++--- Code/client/Games/Skyrim/Actor.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.cpp | 26 ++-------------------- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index f4776dcb3..526210866 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -296,14 +296,12 @@ Inventory Actor::GetActorInventory() const noexcept return inventory; } -void Actor::SetActorInventory(Inventory& aInventory, bool aReset) noexcept +void Actor::SetActorInventory(Inventory& aInventory) noexcept { - if (aReset) - UnEquipAll(); + UnEquipAll(); SetInventory(aInventory); - // TODO: if aReset is false, diff currently equipped weapons first auto* pEquipManager = EquipManager::Get(); auto& modSystem = World::Get().GetModSystem(); diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 522db9eb0..73689c250 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -206,7 +206,7 @@ struct Actor : TESObjectREFR void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; void SetPackage(TESPackage* apPackage) noexcept; - void SetActorInventory(Inventory& aInventory, bool aReset = true) noexcept; + void SetActorInventory(Inventory& aInventory) noexcept; // Actions void UnEquipAll() noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 77e266b92..d6227ccde 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -328,33 +328,11 @@ Inventory TESObjectREFR::GetInventory() const noexcept thread_local bool g_modifyingInventory = false; -void TESObjectREFR::SetInventory(Inventory& aInventory, bool aReset) noexcept +void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept { g_modifyingInventory = true; - if (aReset) - RemoveAllItems(); - else - { - Inventory currentContainer = GetInventory(); - for (const auto& currentEntry : currentContainer.Entries) - { - auto duplicate = std::find_if(aInventory.Entries.begin(), aInventory.Entries.end(), [currentEntry](const Inventory::Entry& newEntry) { - return newEntry.CanBeMerged(currentEntry); - }); - - if (duplicate != std::end(aInventory.Entries)) - { - duplicate->Count -= currentEntry.Count; - } - else - { - aInventory.Entries.push_back(currentEntry); - Inventory::Entry& back = aInventory.Entries.back(); - back.Count *= -1; - } - } - } + RemoveAllItems(); for (const Inventory::Entry& entry : aInventory.Entries) { diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index d167eaf74..3a7761687 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -176,7 +176,7 @@ struct TESObjectREFR : TESForm void EnableImpl() noexcept; Inventory GetInventory() const noexcept; - void SetInventory(Inventory& acContainer, bool aReset = true) noexcept; + void SetInventory(Inventory& acContainer) noexcept; void AddItem(const Inventory::Entry& arEntry) noexcept; From 3687b615432ff7e2ad260b3fd7fa1aa6d3880b3b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 15:57:41 +0100 Subject: [PATCH 40/48] fix: SpellItem struct --- Code/client/Games/Skyrim/Forms/SpellItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/client/Games/Skyrim/Forms/SpellItem.h b/Code/client/Games/Skyrim/Forms/SpellItem.h index f6b336f08..40ed7cc6a 100644 --- a/Code/client/Games/Skyrim/Forms/SpellItem.h +++ b/Code/client/Games/Skyrim/Forms/SpellItem.h @@ -25,7 +25,7 @@ struct SpellItem : MagicItem BGSEquipType equipType; BGSMenuDisplayObject menuDisplayObject; TESDescription description; - uint32_t unk6C[5]; + uint32_t unk6C[3]; float castTime; MagicSystem::CastingType eCastingType; MagicSystem::Delivery eDelivery; From b614ba93fa857676f35357bb84776f98f97099f9 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 16:02:22 +0100 Subject: [PATCH 41/48] fix: temp fix for waiting spawn --- Code/client/Services/CharacterService.h | 1 + .../Services/Generic/CharacterService.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index 93c897d31..a15872213 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -70,6 +70,7 @@ struct CharacterService void RunRemoteUpdates() const noexcept; void RunFactionsUpdates() const noexcept; void RunSpawnUpdates() const noexcept; + void RunWaitingUpdates() const noexcept; World& m_world; entt::dispatcher& m_dispatcher; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 5f74e2835..1dd31ed8f 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -154,6 +154,7 @@ void CharacterService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept RunLocalUpdates(); RunFactionsUpdates(); RunRemoteUpdates(); + RunWaitingUpdates(); } void CharacterService::OnConnected(const ConnectedEvent& acConnectedEvent) const noexcept @@ -894,14 +895,27 @@ void CharacterService::RunRemoteUpdates() const noexcept FaceGenSystem::Update(m_world, pActor, faceGenComponent); } +} + +void CharacterService::RunWaitingUpdates() const noexcept +{ + // TODO: there's a bug here sometimes, WaitingFor3D keeps getting added, SetInventory and others get spammed (#64) + // ask cosi for a repro + // temporary fix is having a delay between check for waiting + static std::chrono::steady_clock::time_point lastWaitingSpawnTime; + constexpr auto cDelay= 250ms; + + const auto now = std::chrono::steady_clock::now(); + if (now - lastWaitingSpawnTime < cDelay) + return; + + lastWaitingSpawnTime = now; auto waitingView = m_world.view(); StackAllocator<1 << 13> allocator; ScopedAllocator _{allocator}; - // TODO: there's a bug here sometimes, WaitingFor3D doesn't get removed, SetInventory and others get spammed - // ask cosi for a repro Vector toRemove; for (auto entity : waitingView) { From b63ce9599ada077b0cfb7c0552179e11519df618 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 16:12:22 +0100 Subject: [PATCH 42/48] fix: health on remote actors going back up --- Code/client/Services/Generic/ActorValueService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/client/Services/Generic/ActorValueService.cpp b/Code/client/Services/Generic/ActorValueService.cpp index c3f013010..20f7a62b1 100644 --- a/Code/client/Services/Generic/ActorValueService.cpp +++ b/Code/client/Services/Generic/ActorValueService.cpp @@ -317,7 +317,7 @@ void ActorValueService::OnActorValueChanges(const NotifyActorValueChanges& acMes { #if TP_SKYRIM64 // Syncing dragon souls triggers "Dragon soul collected" event - if (key == ActorValueInfo::kDragonSouls) + if (key == ActorValueInfo::kDragonSouls || key == ActorValueInfo::kHealth) continue; spdlog::debug("Actor value update, server ID: {:X}, key: {}, value: {}", acMessage.Id, key, value); From 9c4e6970581cdb67d79d2b14839a8c1b9ae09b0d Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 16:14:35 +0100 Subject: [PATCH 43/48] tweak: logging to debug new inventory system --- Code/client/Games/Skyrim/Actor.cpp | 2 ++ Code/client/Games/Skyrim/TESObjectREFR.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index c76e4f5a8..c56f73e13 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -298,6 +298,8 @@ Inventory Actor::GetActorInventory() const noexcept void Actor::SetActorInventory(Inventory& aInventory) noexcept { + spdlog::info("Setting inventory for actor {:X}", formID); + UnEquipAll(); SetInventory(aInventory); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index d6227ccde..33641765a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -330,6 +330,8 @@ thread_local bool g_modifyingInventory = false; void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept { + spdlog::info("Setting inventory for {:X}", formID); + g_modifyingInventory = true; RemoveAllItems(); From 1f1b6055e42c7685522e41d87bb1fd46d20e9e2c Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 17:22:20 +0100 Subject: [PATCH 44/48] fix: permanent fix for waiting spawn --- Code/client/Services/CharacterService.h | 1 - .../Services/Generic/CharacterService.cpp | 22 +++++-------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index a15872213..93c897d31 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -70,7 +70,6 @@ struct CharacterService void RunRemoteUpdates() const noexcept; void RunFactionsUpdates() const noexcept; void RunSpawnUpdates() const noexcept; - void RunWaitingUpdates() const noexcept; World& m_world; entt::dispatcher& m_dispatcher; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 1dd31ed8f..2abecd0ef 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -154,7 +154,6 @@ void CharacterService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept RunLocalUpdates(); RunFactionsUpdates(); RunRemoteUpdates(); - RunWaitingUpdates(); } void CharacterService::OnConnected(const ConnectedEvent& acConnectedEvent) const noexcept @@ -895,21 +894,6 @@ void CharacterService::RunRemoteUpdates() const noexcept FaceGenSystem::Update(m_world, pActor, faceGenComponent); } -} - -void CharacterService::RunWaitingUpdates() const noexcept -{ - // TODO: there's a bug here sometimes, WaitingFor3D keeps getting added, SetInventory and others get spammed (#64) - // ask cosi for a repro - // temporary fix is having a delay between check for waiting - static std::chrono::steady_clock::time_point lastWaitingSpawnTime; - constexpr auto cDelay= 250ms; - - const auto now = std::chrono::steady_clock::now(); - if (now - lastWaitingSpawnTime < cDelay) - return; - - lastWaitingSpawnTime = now; auto waitingView = m_world.view(); @@ -938,7 +922,11 @@ void CharacterService::RunWaitingUpdates() const noexcept } for (auto entity : toRemove) - m_world.remove(entity); + { + // TODO: this used to be remove(), but this didn't work. + // Maybe check if other remove() instances are also compromised? + m_world.erase(entity); + } } void CharacterService::RunFactionsUpdates() const noexcept From b4fab965b62ef8a9845a9e2ec2ed95c5229bc1d0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 12 Mar 2022 19:56:42 +0100 Subject: [PATCH 45/48] fix: death bug --- .../Services/Generic/ActorValueService.cpp | 4 + .../Services/Generic/CharacterService.cpp | 1127 ++++++++--------- .../client/Services/Generic/EntityService.cpp | 1 - Code/server/Services/CharacterService.cpp | 145 +-- 4 files changed, 635 insertions(+), 642 deletions(-) diff --git a/Code/client/Services/Generic/ActorValueService.cpp b/Code/client/Services/Generic/ActorValueService.cpp index 20f7a62b1..8a1dd29ef 100644 --- a/Code/client/Services/Generic/ActorValueService.cpp +++ b/Code/client/Services/Generic/ActorValueService.cpp @@ -293,6 +293,10 @@ void ActorValueService::OnHealthChangeBroadcast(const NotifyHealthChangeBroadcas const float newHealth = pActor->GetActorValue(ActorValueInfo::kHealth) + acMessage.DeltaHealth; pActor->ForceActorValue(2, ActorValueInfo::kHealth, newHealth); + + const float health = pActor->GetActorValue(ActorValueInfo::kHealth); + if (health <= 0.f) + pActor->Kill(); } void ActorValueService::OnActorValueChanges(const NotifyActorValueChanges& acMessage) const noexcept diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 2abecd0ef..e4221b343 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -125,16 +125,8 @@ void CharacterService::OnFormIdComponentAdded(entt::registry& aRegistry, const e CacheSystem::Setup(World::Get(), aEntity, pActor); + // TODO: why check for npc? auto* const pNpc = RTTI_CAST(pActor->baseForm, TESForm, TESNPC); - - auto* pExtra = static_cast(pActor->GetExtraDataList()->GetByType(ExtraData::LeveledCreature)); - - auto* const pOwner = pNpc->actorData.owner; - if (pOwner) - { - // spdlog::info("\tOwner: type {}, id {:X}", static_cast(pOwner->formType), pOwner->formID); - } - if(pNpc) { RequestServerAssignment(aRegistry, aEntity); @@ -158,8 +150,6 @@ void CharacterService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept void CharacterService::OnConnected(const ConnectedEvent& acConnectedEvent) const noexcept { - //m_world.clear(); - // Go through all the forms that were previously detected auto view = m_world.view(); for (auto entity : view) @@ -460,8 +450,8 @@ void CharacterService::OnActionEvent(const ActionEvent& acActionEvent) const noe const auto itor = std::find_if(std::begin(view), std::end(view), [id = acActionEvent.ActorId, view](entt::entity entity) { - return view.get(entity).Id == id; - }); + return view.get(entity).Id == id; + }); if(itor != std::end(view)) { @@ -562,779 +552,778 @@ void CharacterService::OnRemoveCharacter(const NotifyRemoveCharacter& acMessage) } } -void CharacterService::RequestServerAssignment(entt::registry& aRegistry, const entt::entity aEntity) const noexcept +void CharacterService::OnProjectileLaunchedEvent(const ProjectileLaunchedEvent& acEvent) const noexcept { - if (!m_transport.IsOnline()) + ModSystem& modSystem = m_world.Get().GetModSystem(); + + uint32_t shooterFormId = acEvent.ShooterID; + auto view = m_world.view(); + const auto shooterEntityIt = std::find_if(std::begin(view), std::end(view), [shooterFormId, view](entt::entity entity) + { + return view.get(entity).Id == shooterFormId; + }); + + if (shooterEntityIt == std::end(view)) return; - static uint32_t sCookieSeed = 0; + LocalComponent& localComponent = view.get(*shooterEntityIt); - const auto& formIdComponent = aRegistry.get(aEntity); + ProjectileLaunchRequest request{}; - auto* pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); - if (!pActor) - return; + request.OriginX = acEvent.Origin.x; + request.OriginY = acEvent.Origin.y; + request.OriginZ = acEvent.Origin.z; - auto* const pNpc = RTTI_CAST(pActor->baseForm, TESForm, TESNPC); - if (!pNpc) - return; + modSystem.GetServerModId(acEvent.ProjectileBaseID, request.ProjectileBaseID); + modSystem.GetServerModId(acEvent.WeaponID, request.WeaponID); + modSystem.GetServerModId(acEvent.AmmoID, request.AmmoID); - uint32_t baseId = 0; - uint32_t modId = 0; - if (!m_world.GetModSystem().GetServerModId(formIdComponent.Id, modId, baseId)) - return; + request.ShooterID = localComponent.Id; - uint32_t cellBaseId = 0; - uint32_t cellModId = 0; - if (!m_world.GetModSystem().GetServerModId(pActor->parentCell->formID, cellModId, cellBaseId)) - return; + request.ZAngle = acEvent.ZAngle; + request.XAngle = acEvent.XAngle; + request.YAngle = acEvent.YAngle; - AssignCharacterRequest message; + modSystem.GetServerModId(acEvent.ParentCellID, request.ParentCellID); + modSystem.GetServerModId(acEvent.SpellID, request.SpellID); - message.Cookie = sCookieSeed; - message.ReferenceId.BaseId = baseId; - message.ReferenceId.ModId = modId; - message.CellId.BaseId = cellBaseId; - message.CellId.ModId = cellModId; + request.CastingSource = acEvent.CastingSource; - if (const auto pWorldSpace = pActor->GetWorldSpace()) - { - uint32_t worldSpaceBaseId = 0; - uint32_t worldSpaceModId = 0; - if (!m_world.GetModSystem().GetServerModId(pWorldSpace->formID, worldSpaceModId, worldSpaceBaseId)) - return; + request.Area = acEvent.Area; + request.Power = acEvent.Power; + request.Scale = acEvent.Scale; - message.WorldSpaceId.BaseId = worldSpaceBaseId; - message.WorldSpaceId.ModId = worldSpaceModId; - } + request.AlwaysHit = acEvent.AlwaysHit; + request.NoDamageOutsideCombat = acEvent.NoDamageOutsideCombat; + request.AutoAim = acEvent.AutoAim; + request.DeferInitialization = acEvent.DeferInitialization; + request.ForceConeOfFire = acEvent.ForceConeOfFire; - message.Position = pActor->position; - message.Rotation.x = pActor->rotation.x; - message.Rotation.y = pActor->rotation.z; +#if TP_SKYRIM64 + request.UnkBool1 = acEvent.UnkBool1; + request.UnkBool2 = acEvent.UnkBool2; +#else + request.ConeOfFireRadiusMult = acEvent.ConeOfFireRadiusMult; + request.Tracer = acEvent.Tracer; + request.IntentionalMiss = acEvent.IntentionalMiss; + request.Allow3D = acEvent.Allow3D; + request.Penetrates = acEvent.Penetrates; + request.IgnoreNearCollisions = acEvent.IgnoreNearCollisions; +#endif - // Serialize the base form - const auto isPlayer = (formIdComponent.Id == 0x14); - const auto isTemporary = pActor->formID >= 0xFF000000; + m_transport.Send(request); +} - if(isPlayer) - { - pNpc->MarkChanged(0x2000800); - } +void CharacterService::OnNotifyProjectileLaunch(const NotifyProjectileLaunch& acMessage) const noexcept +{ + ModSystem& modSystem = World::Get().GetModSystem(); - const auto changeFlags = pNpc->GetChangeFlags(); + auto remoteView = m_world.view(); + const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ShooterID](auto entity) + { + return remoteView.get(entity).Id == Id; + }); - if(isPlayer || pNpc->formID >= 0xFF000000 || changeFlags != 0) + if (remoteIt == std::end(remoteView)) { - message.ChangeFlags = changeFlags; - pNpc->Serialize(&message.AppearanceBuffer); + spdlog::warn("Shooter with remote id {:X} not found.", acMessage.ShooterID); + return; } -#if TP_SKYRIM - if (isPlayer) - { - auto& entries = message.FaceTints.Entries; + FormIdComponent formIdComponent = remoteView.get(*remoteIt); - const auto& tints = PlayerCharacter::Get()->GetTints(); +#if TP_SKYRIM64 + Projectile::LaunchData launchData{}; +#else + ProjectileLaunchData launchData{}; +#endif - entries.resize(tints.length); + launchData.pShooter = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, TESObjectREFR); - for (auto i = 0u; i < tints.length; ++i) - { - entries[i].Alpha = tints[i]->alpha; - entries[i].Color = tints[i]->color; - entries[i].Type = tints[i]->type; + launchData.Origin.x = acMessage.OriginX; + launchData.Origin.y = acMessage.OriginY; + launchData.Origin.z = acMessage.OriginZ; - if(tints[i]->texture) - entries[i].Name = tints[i]->texture->name.AsAscii(); - } - } + const uint32_t cProjectileBaseId = modSystem.GetGameId(acMessage.ProjectileBaseID); + launchData.pProjectileBase = TESForm::GetById(cProjectileBaseId); + +#if TP_SKYRIM64 + const uint32_t cFromWeaponId = modSystem.GetGameId(acMessage.WeaponID); + launchData.pFromWeapon = RTTI_CAST(TESForm::GetById(cFromWeaponId), TESForm, TESObjectWEAP); #endif - if (isPlayer) - { - auto& questLog = message.QuestContent.Entries; - auto& modSystem = m_world.GetModSystem(); +#if TP_FALLOUT4 + Actor* pShooter = RTTI_CAST(launchData.pShooter, TESObjectREFR, Actor); + pShooter->GetCurrentWeapon(&launchData.FromWeapon, 0); +#endif - for (const auto& objective : PlayerCharacter::Get()->objectives) - { - auto* pQuest = objective.instance->quest; - if (!pQuest) - continue; + const uint32_t cFromAmmoId = modSystem.GetGameId(acMessage.AmmoID); + launchData.pFromAmmo = RTTI_CAST(TESForm::GetById(cFromAmmoId), TESForm, TESAmmo); - if (!QuestService::IsNonSyncableQuest(pQuest)) - { - GameId id{}; + launchData.fZAngle = acMessage.ZAngle; + launchData.fXAngle = acMessage.XAngle; + launchData.fYAngle = acMessage.YAngle; - if (modSystem.GetServerModId(pQuest->formID, id)) - { - auto& entry = questLog.emplace_back(); - entry.Stage = pQuest->currentStage; - entry.Id = id; - } - } - } + const uint32_t cParentCellId = modSystem.GetGameId(acMessage.ParentCellID); + launchData.pParentCell = RTTI_CAST(TESForm::GetById(cParentCellId), TESForm, TESObjectCELL); - // remove duplicates - const auto ip = std::unique(questLog.begin(), questLog.end()); - questLog.resize(std::distance(questLog.begin(), ip)); - } + const uint32_t cSpellId = modSystem.GetGameId(acMessage.SpellID); + launchData.pSpell = RTTI_CAST(TESForm::GetById(cSpellId), TESForm, MagicItem); - message.InventoryContent = pActor->GetActorInventory(); - message.FactionsContent = pActor->GetFactions(); - message.AllActorValues = pActor->GetEssentialActorValues(); - message.IsDead = pActor->IsDead(); - message.IsWeaponDrawn = pActor->actorState.IsWeaponFullyDrawn(); + launchData.eCastingSource = (MagicSystem::CastingSource)acMessage.CastingSource; - if(isTemporary) - { - if (!World::Get().GetModSystem().GetServerModId(pNpc->formID, message.FormId)) - return; - } + launchData.iArea = acMessage.Area; + launchData.fPower = acMessage.Power; + launchData.fScale = acMessage.Scale; - // Serialize actions - auto* const pExtension = pActor->GetExtension(); + launchData.bAlwaysHit = acMessage.AlwaysHit; + launchData.bNoDamageOutsideCombat = acMessage.NoDamageOutsideCombat; + launchData.bAutoAim = acMessage.AutoAim; - message.LatestAction = pExtension->LatestAnimation; - pActor->SaveAnimationVariables(message.LatestAction.Variables); + launchData.bForceConeOfFire = acMessage.ForceConeOfFire; - spdlog::info("Request id: {:X}, cookie: {:X}, {:X}", formIdComponent.Id, sCookieSeed, aEntity); + // always use origin, or it'll recalculate it and it desyncs + launchData.bUseOrigin = true; - if (m_transport.Send(message)) - { - aRegistry.emplace(aEntity, sCookieSeed); +#if TP_SKYRIM64 + launchData.bUnkBool1 = acMessage.UnkBool1; + launchData.bUnkBool2 = acMessage.UnkBool2; +#else + launchData.eTargetLimb = -1; - sCookieSeed++; - } -} + launchData.fConeOfFireRadiusMult = acMessage.ConeOfFireRadiusMult; + launchData.bTracer = acMessage.Tracer; + launchData.bIntentionalMiss = acMessage.IntentionalMiss; + launchData.bAllow3D = acMessage.Allow3D; + launchData.bPenetrates = acMessage.Penetrates; + launchData.bIgnoreNearCollisions = acMessage.IgnoreNearCollisions; +#endif -void CharacterService::CancelServerAssignment(entt::registry& aRegistry, const entt::entity aEntity, const uint32_t aFormId) const noexcept -{ - if (aRegistry.all_of(aEntity)) - { - TESForm* const pForm = TESForm::GetById(aFormId); - Actor* const pActor = RTTI_CAST(pForm, TESForm, Actor); + BSPointerHandle result; - if (pActor && ((pActor->formID & 0xFF000000) == 0xFF000000)) - { - spdlog::info("Temporary Remote Deleted {:X}", aFormId); + Projectile::Launch(&result, launchData); +} - pActor->Delete(); - } +void CharacterService::OnMountEvent(const MountEvent& acEvent) const noexcept +{ +#if TP_SKYRIM64 + auto view = m_world.view(); - aRegistry.remove(aEntity); + const auto riderIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.RiderID, view](auto entity) { + return view.get(entity).Id == id; + }); + if (riderIt == std::end(view)) + { + spdlog::warn("Rider not found, form id: {:X}", acEvent.RiderID); return; } - // In the event we were waiting for assignment, drop it - if (aRegistry.all_of(aEntity)) - { - auto& waitingComponent = aRegistry.get(aEntity); + const entt::entity cRiderEntity = *riderIt; - CancelAssignmentRequest message; - message.Cookie = waitingComponent.Cookie; + std::optional riderServerIdRes = Utils::GetServerId(cRiderEntity); + if (!riderServerIdRes.has_value()) + return; - m_transport.Send(message); + const auto mountIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.MountID, view](auto entity) { + return view.get(entity).Id == id; + }); - aRegistry.remove(aEntity); + if (mountIt == std::end(view)) + { + spdlog::warn("Mount not found, form id: {:X}", acEvent.MountID); + return; } - if (aRegistry.all_of(aEntity)) + const entt::entity cMountEntity = *mountIt; + + std::optional mountServerIdRes = Utils::GetServerId(cMountEntity); + if (!mountServerIdRes.has_value()) + return; + + if (m_world.try_get(cMountEntity)) { - auto& localComponent = aRegistry.get(aEntity); + const TESForm* pMountForm = TESForm::GetById(acEvent.MountID); + Actor* pMount = RTTI_CAST(pMountForm, TESForm, Actor); + pMount->GetExtension()->SetRemote(false); - RequestOwnershipTransfer request; - request.ServerId = localComponent.Id; + m_world.emplace(cMountEntity, mountServerIdRes.value()); + m_world.emplace(cMountEntity); + m_world.remove(cMountEntity); - m_transport.Send(request); + RequestOwnershipClaim request; + request.ServerId = mountServerIdRes.value(); - aRegistry.remove(aEntity); + m_transport.Send(request); } + + MountRequest request; + request.MountId = mountServerIdRes.value(); + request.RiderId = riderServerIdRes.value(); + + m_transport.Send(request); +#endif } -Actor* CharacterService::CreateCharacterForEntity(entt::entity aEntity) const noexcept +void CharacterService::OnNotifyMount(const NotifyMount& acMessage) const noexcept { - auto* pRemoteComponent = m_world.try_get(aEntity); - auto* pInterpolationComponent = m_world.try_get(aEntity); +#if TP_SKYRIM64 + auto remoteView = m_world.view(); - if (!pRemoteComponent || !pInterpolationComponent) - return nullptr; + const auto riderIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.RiderId](auto entity) + { + return remoteView.get(entity).Id == Id; + }); - auto& acMessage = pRemoteComponent->SpawnRequest; + if (riderIt == std::end(remoteView)) + { + spdlog::warn("Rider with remote id {:X} not found.", acMessage.RiderId); + return; + } - Actor* pActor = nullptr; + auto riderFormIdComponent = remoteView.get(*riderIt); + TESForm* pRiderForm = TESForm::GetById(riderFormIdComponent.Id); + Actor* pRider = RTTI_CAST(pRiderForm, TESForm, Actor); - // Custom forms - if (acMessage.FormId == GameId{}) + Actor* pMount = nullptr; + + auto formView = m_world.view(); + for (auto entity : formView) { - TESNPC* pNpc = nullptr; + std::optional serverIdRes = Utils::GetServerId(entity); + if (!serverIdRes.has_value()) + continue; - if (acMessage.BaseId != GameId{}) + uint32_t serverId = serverIdRes.value(); + + if (serverId == acMessage.MountId) { - const uint32_t cNpcId = World::Get().GetModSystem().GetGameId(acMessage.BaseId); - if (cNpcId == 0) + auto mountFormIdComponent = formView.get(entity); + + if (m_world.all_of(entity)) { - spdlog::error("Failed to retrieve NPC, it will not be spawned, possibly missing mod"); - return nullptr; + m_world.remove(entity); + m_world.emplace_or_replace(entity, acMessage.MountId, mountFormIdComponent.Id); } - pNpc = RTTI_CAST(TESForm::GetById(cNpcId), TESForm, TESNPC); - pNpc->Deserialize(acMessage.AppearanceBuffer, acMessage.ChangeFlags); - } - else - { - pNpc = TESNPC::Create(acMessage.AppearanceBuffer, acMessage.ChangeFlags); - FaceGenSystem::Setup(m_world, aEntity, acMessage.FaceTints); - } - - pActor = Actor::Create(pNpc); - } + TESForm* pMountForm = TESForm::GetById(mountFormIdComponent.Id); + pMount = RTTI_CAST(pMountForm, TESForm, Actor); + pMount->GetExtension()->SetRemote(true); - if (!pActor) - return nullptr; + InterpolationSystem::Setup(m_world, entity); + AnimationSystem::Setup(m_world, entity); - pActor->GetExtension()->SetRemote(true); - pActor->rotation.x = acMessage.Rotation.x; - pActor->rotation.z = acMessage.Rotation.y; - pActor->MoveTo(PlayerCharacter::Get()->parentCell, pInterpolationComponent->Position); - pActor->SetActorValues(acMessage.InitialActorValues); - pActor->GetExtension()->SetPlayer(acMessage.IsPlayer); - if (acMessage.IsPlayer) - { - pActor->SetIgnoreFriendlyHit(true); + break; + } } - if (pActor->IsDead() != acMessage.IsDead) - acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - - m_world.emplace(aEntity); - - return pActor; + pRider->InitiateMountPackage(pMount); +#endif } -void CharacterService::RunLocalUpdates() const noexcept +void CharacterService::OnInitPackageEvent(const InitPackageEvent& acEvent) const noexcept { - static std::chrono::steady_clock::time_point lastSendTimePoint; - constexpr auto cDelayBetweenSnapshots = 100ms; - - const auto now = std::chrono::steady_clock::now(); - if (now - lastSendTimePoint < cDelayBetweenSnapshots) + if (!m_transport.IsConnected()) return; - lastSendTimePoint = now; + auto view = m_world.view(); - ClientReferencesMoveRequest message; - message.Tick = m_transport.GetClock().GetCurrentTick(); + const auto actorIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.ActorId, view](auto entity) { + return view.get(entity).Id == id; + }); - auto animatedLocalView = m_world.view(); + if (actorIt == std::end(view)) + return; - for (auto entity : animatedLocalView) - { - auto& localComponent = animatedLocalView.get(entity); - auto& animationComponent = animatedLocalView.get(entity); - auto& formIdComponent = animatedLocalView.get(entity); + const entt::entity cActorEntity = *actorIt; - AnimationSystem::Serialize(m_world, message, localComponent, animationComponent, formIdComponent); - } + std::optional actorServerIdRes = Utils::GetServerId(cActorEntity); + if (!actorServerIdRes.has_value()) + return; - m_transport.Send(message); + NewPackageRequest request; + request.ActorId = actorServerIdRes.value(); + if (!m_world.GetModSystem().GetServerModId(acEvent.PackageId, request.PackageId.ModId, request.PackageId.BaseId)) + return; + + m_transport.Send(request); } -void CharacterService::RunRemoteUpdates() const noexcept +void CharacterService::OnNotifyNewPackage(const NotifyNewPackage& acMessage) const noexcept { - // Delay by 120ms to let the interpolation system accumulate interpolation points - const auto tick = m_transport.GetClock().GetCurrentTick() - 120; - - // Interpolation has to keep running even if the actor is not in view, otherwise we will never know if we need to spawn it - auto interpolatedEntities = - m_world.view(); - - for (auto entity : interpolatedEntities) + auto remoteView = m_world.view(); + const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ActorId](auto entity) { - auto* pFormIdComponent = m_world.try_get(entity); - auto& interpolationComponent = interpolatedEntities.get(entity); + return remoteView.get(entity).Id == Id; + }); - Actor* pActor = nullptr; - if (pFormIdComponent) - { - auto* pForm = TESForm::GetById(pFormIdComponent->Id); - pActor = RTTI_CAST(pForm, TESForm, Actor); - } - - InterpolationSystem::Update(pActor, interpolationComponent, tick); + if (remoteIt == std::end(remoteView)) + { + spdlog::warn("Actor for package with remote id {:X} not found.", acMessage.ActorId); + return; } - auto animatedView = m_world.view(); - - for (auto entity : animatedView) - { - auto& animationComponent = animatedView.get(entity); - auto& formIdComponent = animatedView.get(entity); + auto formIdComponent = remoteView.get(*remoteIt); - auto* pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = RTTI_CAST(pForm, TESForm, Actor); - if (!pActor) - continue; + const TESForm* pForm = TESForm::GetById(formIdComponent.Id); + Actor* pActor = RTTI_CAST(pForm, TESForm, Actor); - AnimationSystem::Update(m_world, pActor, animationComponent, tick); + const uint32_t cPackageFormId = World::Get().GetModSystem().GetGameId(acMessage.PackageId); + const TESForm* pPackageForm = TESForm::GetById(cPackageFormId); + if (!pPackageForm) + { + spdlog::warn("Actor package not found, base id: {:X}, mod id: {:X}", acMessage.PackageId.BaseId, + acMessage.PackageId.ModId); + return; } - auto facegenView = m_world.view(); + TESPackage* pPackage = RTTI_CAST(pPackageForm, TESForm, TESPackage); - for(auto entity : facegenView) - { - auto& formIdComponent = facegenView.get(entity); - auto& faceGenComponent = facegenView.get(entity); + pActor->SetPackage(pPackage); +} - const auto* pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = RTTI_CAST(pForm, TESForm, Actor); - if (!pActor) - continue; +void CharacterService::RequestServerAssignment(entt::registry& aRegistry, const entt::entity aEntity) const noexcept +{ + if (!m_transport.IsOnline()) + return; - FaceGenSystem::Update(m_world, pActor, faceGenComponent); - } + static uint32_t sCookieSeed = 0; - auto waitingView = m_world.view(); + const auto& formIdComponent = aRegistry.get(aEntity); - StackAllocator<1 << 13> allocator; - ScopedAllocator _{allocator}; + auto* pActor = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, Actor); + if (!pActor) + return; - Vector toRemove; - for (auto entity : waitingView) - { - auto& formIdComponent = waitingView.get(entity); - auto& remoteComponent = waitingView.get(entity); + auto* const pNpc = RTTI_CAST(pActor->baseForm, TESForm, TESNPC); + if (!pNpc) + return; - const auto* pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = RTTI_CAST(pForm, TESForm, Actor); - if (!pActor || !pActor->GetNiNode()) - continue; + uint32_t baseId = 0; + uint32_t modId = 0; + if (!m_world.GetModSystem().GetServerModId(formIdComponent.Id, modId, baseId)) + return; - pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); - pActor->SetFactions(remoteComponent.SpawnRequest.FactionsContent); - pActor->LoadAnimationVariables(remoteComponent.SpawnRequest.LatestAction.Variables); + uint32_t cellBaseId = 0; + uint32_t cellModId = 0; + if (!m_world.GetModSystem().GetServerModId(pActor->parentCell->formID, cellModId, cellBaseId)) + return; - if (pActor->IsDead() != remoteComponent.SpawnRequest.IsDead) - remoteComponent.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); + AssignCharacterRequest message; - toRemove.push_back(entity); - } + message.Cookie = sCookieSeed; + message.ReferenceId.BaseId = baseId; + message.ReferenceId.ModId = modId; + message.CellId.BaseId = cellBaseId; + message.CellId.ModId = cellModId; - for (auto entity : toRemove) + if (const auto pWorldSpace = pActor->GetWorldSpace()) { - // TODO: this used to be remove(), but this didn't work. - // Maybe check if other remove() instances are also compromised? - m_world.erase(entity); + uint32_t worldSpaceBaseId = 0; + uint32_t worldSpaceModId = 0; + if (!m_world.GetModSystem().GetServerModId(pWorldSpace->formID, worldSpaceModId, worldSpaceBaseId)) + return; + + message.WorldSpaceId.BaseId = worldSpaceBaseId; + message.WorldSpaceId.ModId = worldSpaceModId; } -} -void CharacterService::RunFactionsUpdates() const noexcept -{ - static std::chrono::steady_clock::time_point lastSendTimePoint; - constexpr auto cDelayBetweenSnapshots = 2000ms; + message.Position = pActor->position; + message.Rotation.x = pActor->rotation.x; + message.Rotation.y = pActor->rotation.z; - const auto now = std::chrono::steady_clock::now(); - if (now - lastSendTimePoint < cDelayBetweenSnapshots) - return; + // Serialize the base form + const auto isPlayer = (formIdComponent.Id == 0x14); + const auto isTemporary = pActor->formID >= 0xFF000000; - lastSendTimePoint = now; + if(isPlayer) + { + pNpc->MarkChanged(0x2000800); + } - RequestFactionsChanges message; + const auto changeFlags = pNpc->GetChangeFlags(); - auto factionedActors = m_world.view(); - for (auto entity : factionedActors) + if(isPlayer || pNpc->formID >= 0xFF000000 || changeFlags != 0) { - auto& formIdComponent = factionedActors.get(entity); - auto& localComponent = factionedActors.get(entity); - auto& cacheComponent = factionedActors.get(entity); + message.ChangeFlags = changeFlags; + pNpc->Serialize(&message.AppearanceBuffer); + } - const auto* pForm = TESForm::GetById(formIdComponent.Id); - const auto* pActor = RTTI_CAST(pForm, TESForm, Actor); - if (!pActor) - continue; +#if TP_SKYRIM + if (isPlayer) + { + auto& entries = message.FaceTints.Entries; - // Check if cached factions and current factions are identical - auto factions = pActor->GetFactions(); + const auto& tints = PlayerCharacter::Get()->GetTints(); - if (cacheComponent.FactionsContent == factions) - continue; + entries.resize(tints.length); - cacheComponent.FactionsContent = factions; + for (auto i = 0u; i < tints.length; ++i) + { + entries[i].Alpha = tints[i]->alpha; + entries[i].Color = tints[i]->color; + entries[i].Type = tints[i]->type; - // If not send the current factions and replace the cached factions - message.Changes[localComponent.Id] = factions; + if(tints[i]->texture) + entries[i].Name = tints[i]->texture->name.AsAscii(); + } } +#endif - if(!message.Changes.empty()) - m_transport.Send(message); -} - -void CharacterService::RunSpawnUpdates() const noexcept -{ - auto invisibleView = m_world.view(entt::exclude); - - for (auto entity : invisibleView) + if (isPlayer) { - auto& remoteComponent = invisibleView.get(entity); - auto& interpolationComponent = invisibleView.get(entity); + auto& questLog = message.QuestContent.Entries; + auto& modSystem = m_world.GetModSystem(); - if (const auto pWorldSpace = PlayerCharacter::Get()->GetWorldSpace()) + for (const auto& objective : PlayerCharacter::Get()->objectives) { - float characterX = interpolationComponent.Position.x; - float characterY = interpolationComponent.Position.y; - const auto characterCoords = GridCellCoords::CalculateGridCellCoords(characterX, characterY); - const TES* pTES = TES::Get(); - const auto playerCoords = GridCellCoords::GridCellCoords(pTES->centerGridX, pTES->centerGridY); + auto* pQuest = objective.instance->quest; + if (!pQuest) + continue; - if (GridCellCoords::IsCellInGridCell(characterCoords, playerCoords)) + if (!QuestService::IsNonSyncableQuest(pQuest)) { - auto* pActor = RTTI_CAST(TESForm::GetById(remoteComponent.CachedRefId), TESForm, Actor); - if (!pActor) - { - pActor = CreateCharacterForEntity(entity); - if (!pActor) - { - continue; - } + GameId id{}; - remoteComponent.CachedRefId = pActor->formID; + if (modSystem.GetServerModId(pQuest->formID, id)) + { + auto& entry = questLog.emplace_back(); + entry.Stage = pQuest->currentStage; + entry.Id = id; } - - pActor->MoveTo(PlayerCharacter::Get()->parentCell, interpolationComponent.Position); } } + + // remove duplicates + const auto ip = std::unique(questLog.begin(), questLog.end()); + questLog.resize(std::distance(questLog.begin(), ip)); } -} -void CharacterService::OnProjectileLaunchedEvent(const ProjectileLaunchedEvent& acEvent) const noexcept -{ - ModSystem& modSystem = m_world.Get().GetModSystem(); + message.InventoryContent = pActor->GetActorInventory(); + message.FactionsContent = pActor->GetFactions(); + message.AllActorValues = pActor->GetEssentialActorValues(); + message.IsDead = pActor->IsDead(); + message.IsWeaponDrawn = pActor->actorState.IsWeaponFullyDrawn(); - uint32_t shooterFormId = acEvent.ShooterID; - auto view = m_world.view(); - const auto shooterEntityIt = std::find_if(std::begin(view), std::end(view), [shooterFormId, view](entt::entity entity) + if(isTemporary) { - return view.get(entity).Id == shooterFormId; - }); - - if (shooterEntityIt == std::end(view)) - return; - - LocalComponent& localComponent = view.get(*shooterEntityIt); - - ProjectileLaunchRequest request{}; - - request.OriginX = acEvent.Origin.x; - request.OriginY = acEvent.Origin.y; - request.OriginZ = acEvent.Origin.z; - - modSystem.GetServerModId(acEvent.ProjectileBaseID, request.ProjectileBaseID); - modSystem.GetServerModId(acEvent.WeaponID, request.WeaponID); - modSystem.GetServerModId(acEvent.AmmoID, request.AmmoID); - - request.ShooterID = localComponent.Id; - - request.ZAngle = acEvent.ZAngle; - request.XAngle = acEvent.XAngle; - request.YAngle = acEvent.YAngle; - - modSystem.GetServerModId(acEvent.ParentCellID, request.ParentCellID); - modSystem.GetServerModId(acEvent.SpellID, request.SpellID); + if (!World::Get().GetModSystem().GetServerModId(pNpc->formID, message.FormId)) + return; + } - request.CastingSource = acEvent.CastingSource; + // Serialize actions + auto* const pExtension = pActor->GetExtension(); - request.Area = acEvent.Area; - request.Power = acEvent.Power; - request.Scale = acEvent.Scale; + message.LatestAction = pExtension->LatestAnimation; + pActor->SaveAnimationVariables(message.LatestAction.Variables); - request.AlwaysHit = acEvent.AlwaysHit; - request.NoDamageOutsideCombat = acEvent.NoDamageOutsideCombat; - request.AutoAim = acEvent.AutoAim; - request.DeferInitialization = acEvent.DeferInitialization; - request.ForceConeOfFire = acEvent.ForceConeOfFire; + spdlog::info("Request id: {:X}, cookie: {:X}, {:X}", formIdComponent.Id, sCookieSeed, aEntity); -#if TP_SKYRIM64 - request.UnkBool1 = acEvent.UnkBool1; - request.UnkBool2 = acEvent.UnkBool2; -#else - request.ConeOfFireRadiusMult = acEvent.ConeOfFireRadiusMult; - request.Tracer = acEvent.Tracer; - request.IntentionalMiss = acEvent.IntentionalMiss; - request.Allow3D = acEvent.Allow3D; - request.Penetrates = acEvent.Penetrates; - request.IgnoreNearCollisions = acEvent.IgnoreNearCollisions; -#endif + if (m_transport.Send(message)) + { + aRegistry.emplace(aEntity, sCookieSeed); - m_transport.Send(request); + sCookieSeed++; + } } -void CharacterService::OnNotifyProjectileLaunch(const NotifyProjectileLaunch& acMessage) const noexcept +void CharacterService::CancelServerAssignment(entt::registry& aRegistry, const entt::entity aEntity, const uint32_t aFormId) const noexcept { - ModSystem& modSystem = World::Get().GetModSystem(); - - auto remoteView = m_world.view(); - const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ShooterID](auto entity) + if (aRegistry.all_of(aEntity)) { - return remoteView.get(entity).Id == Id; - }); + TESForm* const pForm = TESForm::GetById(aFormId); + Actor* const pActor = RTTI_CAST(pForm, TESForm, Actor); + + if (pActor && ((pActor->formID & 0xFF000000) == 0xFF000000)) + { + spdlog::info("Temporary Remote Deleted {:X}", aFormId); + + pActor->Delete(); + } + + aRegistry.remove(aEntity); - if (remoteIt == std::end(remoteView)) - { - spdlog::warn("Shooter with remote id {:X} not found.", acMessage.ShooterID); return; } - FormIdComponent formIdComponent = remoteView.get(*remoteIt); + // In the event we were waiting for assignment, drop it + if (aRegistry.all_of(aEntity)) + { + auto& waitingComponent = aRegistry.get(aEntity); -#if TP_SKYRIM64 - Projectile::LaunchData launchData{}; -#else - ProjectileLaunchData launchData{}; -#endif + CancelAssignmentRequest message; + message.Cookie = waitingComponent.Cookie; - launchData.pShooter = RTTI_CAST(TESForm::GetById(formIdComponent.Id), TESForm, TESObjectREFR); + m_transport.Send(message); - launchData.Origin.x = acMessage.OriginX; - launchData.Origin.y = acMessage.OriginY; - launchData.Origin.z = acMessage.OriginZ; + aRegistry.remove(aEntity); + } - const uint32_t cProjectileBaseId = modSystem.GetGameId(acMessage.ProjectileBaseID); - launchData.pProjectileBase = TESForm::GetById(cProjectileBaseId); + if (aRegistry.all_of(aEntity)) + { + auto& localComponent = aRegistry.get(aEntity); -#if TP_SKYRIM64 - const uint32_t cFromWeaponId = modSystem.GetGameId(acMessage.WeaponID); - launchData.pFromWeapon = RTTI_CAST(TESForm::GetById(cFromWeaponId), TESForm, TESObjectWEAP); -#endif + RequestOwnershipTransfer request; + request.ServerId = localComponent.Id; -#if TP_FALLOUT4 - Actor* pShooter = RTTI_CAST(launchData.pShooter, TESObjectREFR, Actor); - pShooter->GetCurrentWeapon(&launchData.FromWeapon, 0); -#endif + m_transport.Send(request); - const uint32_t cFromAmmoId = modSystem.GetGameId(acMessage.AmmoID); - launchData.pFromAmmo = RTTI_CAST(TESForm::GetById(cFromAmmoId), TESForm, TESAmmo); + aRegistry.remove(aEntity); + } +} - launchData.fZAngle = acMessage.ZAngle; - launchData.fXAngle = acMessage.XAngle; - launchData.fYAngle = acMessage.YAngle; +Actor* CharacterService::CreateCharacterForEntity(entt::entity aEntity) const noexcept +{ + auto* pRemoteComponent = m_world.try_get(aEntity); + auto* pInterpolationComponent = m_world.try_get(aEntity); - const uint32_t cParentCellId = modSystem.GetGameId(acMessage.ParentCellID); - launchData.pParentCell = RTTI_CAST(TESForm::GetById(cParentCellId), TESForm, TESObjectCELL); + if (!pRemoteComponent || !pInterpolationComponent) + return nullptr; - const uint32_t cSpellId = modSystem.GetGameId(acMessage.SpellID); - launchData.pSpell = RTTI_CAST(TESForm::GetById(cSpellId), TESForm, MagicItem); + auto& acMessage = pRemoteComponent->SpawnRequest; - launchData.eCastingSource = (MagicSystem::CastingSource)acMessage.CastingSource; + Actor* pActor = nullptr; - launchData.iArea = acMessage.Area; - launchData.fPower = acMessage.Power; - launchData.fScale = acMessage.Scale; + // Custom forms + if (acMessage.FormId == GameId{}) + { + TESNPC* pNpc = nullptr; - launchData.bAlwaysHit = acMessage.AlwaysHit; - launchData.bNoDamageOutsideCombat = acMessage.NoDamageOutsideCombat; - launchData.bAutoAim = acMessage.AutoAim; + if (acMessage.BaseId != GameId{}) + { + const uint32_t cNpcId = World::Get().GetModSystem().GetGameId(acMessage.BaseId); + if (cNpcId == 0) + { + spdlog::error("Failed to retrieve NPC, it will not be spawned, possibly missing mod"); + return nullptr; + } - launchData.bForceConeOfFire = acMessage.ForceConeOfFire; + pNpc = RTTI_CAST(TESForm::GetById(cNpcId), TESForm, TESNPC); + pNpc->Deserialize(acMessage.AppearanceBuffer, acMessage.ChangeFlags); + } + else + { + pNpc = TESNPC::Create(acMessage.AppearanceBuffer, acMessage.ChangeFlags); + FaceGenSystem::Setup(m_world, aEntity, acMessage.FaceTints); + } - // always use origin, or it'll recalculate it and it desyncs - launchData.bUseOrigin = true; + pActor = Actor::Create(pNpc); + } -#if TP_SKYRIM64 - launchData.bUnkBool1 = acMessage.UnkBool1; - launchData.bUnkBool2 = acMessage.UnkBool2; -#else - launchData.eTargetLimb = -1; + if (!pActor) + return nullptr; - launchData.fConeOfFireRadiusMult = acMessage.ConeOfFireRadiusMult; - launchData.bTracer = acMessage.Tracer; - launchData.bIntentionalMiss = acMessage.IntentionalMiss; - launchData.bAllow3D = acMessage.Allow3D; - launchData.bPenetrates = acMessage.Penetrates; - launchData.bIgnoreNearCollisions = acMessage.IgnoreNearCollisions; -#endif + pActor->GetExtension()->SetRemote(true); + pActor->rotation.x = acMessage.Rotation.x; + pActor->rotation.z = acMessage.Rotation.y; + pActor->MoveTo(PlayerCharacter::Get()->parentCell, pInterpolationComponent->Position); + pActor->SetActorValues(acMessage.InitialActorValues); + pActor->GetExtension()->SetPlayer(acMessage.IsPlayer); + if (acMessage.IsPlayer) + { + pActor->SetIgnoreFriendlyHit(true); + } - BSPointerHandle result; + if (pActor->IsDead() != acMessage.IsDead) + acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - Projectile::Launch(&result, launchData); + m_world.emplace(aEntity); + + return pActor; } -void CharacterService::OnMountEvent(const MountEvent& acEvent) const noexcept +void CharacterService::RunLocalUpdates() const noexcept { -#if TP_SKYRIM64 - auto view = m_world.view(); - - const auto riderIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.RiderID, view](auto entity) { - return view.get(entity).Id == id; - }); + static std::chrono::steady_clock::time_point lastSendTimePoint; + constexpr auto cDelayBetweenSnapshots = 100ms; - if (riderIt == std::end(view)) - { - spdlog::warn("Rider not found, form id: {:X}", acEvent.RiderID); + const auto now = std::chrono::steady_clock::now(); + if (now - lastSendTimePoint < cDelayBetweenSnapshots) return; - } - const entt::entity cRiderEntity = *riderIt; + lastSendTimePoint = now; - std::optional riderServerIdRes = Utils::GetServerId(cRiderEntity); - if (!riderServerIdRes.has_value()) - return; + ClientReferencesMoveRequest message; + message.Tick = m_transport.GetClock().GetCurrentTick(); - const auto mountIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.MountID, view](auto entity) { - return view.get(entity).Id == id; - }); + auto animatedLocalView = m_world.view(); - if (mountIt == std::end(view)) + for (auto entity : animatedLocalView) { - spdlog::warn("Mount not found, form id: {:X}", acEvent.MountID); - return; + auto& localComponent = animatedLocalView.get(entity); + auto& animationComponent = animatedLocalView.get(entity); + auto& formIdComponent = animatedLocalView.get(entity); + + AnimationSystem::Serialize(m_world, message, localComponent, animationComponent, formIdComponent); } - const entt::entity cMountEntity = *mountIt; + m_transport.Send(message); +} - std::optional mountServerIdRes = Utils::GetServerId(cMountEntity); - if (!mountServerIdRes.has_value()) - return; +void CharacterService::RunRemoteUpdates() const noexcept +{ + // Delay by 120ms to let the interpolation system accumulate interpolation points + const auto tick = m_transport.GetClock().GetCurrentTick() - 120; - if (m_world.try_get(cMountEntity)) + // Interpolation has to keep running even if the actor is not in view, otherwise we will never know if we need to spawn it + auto interpolatedEntities = + m_world.view(); + + for (auto entity : interpolatedEntities) { - const TESForm* pMountForm = TESForm::GetById(acEvent.MountID); - Actor* pMount = RTTI_CAST(pMountForm, TESForm, Actor); - pMount->GetExtension()->SetRemote(false); + auto* pFormIdComponent = m_world.try_get(entity); + auto& interpolationComponent = interpolatedEntities.get(entity); - m_world.emplace(cMountEntity, mountServerIdRes.value()); - m_world.emplace(cMountEntity); - m_world.remove(cMountEntity); + Actor* pActor = nullptr; + if (pFormIdComponent) + { + auto* pForm = TESForm::GetById(pFormIdComponent->Id); + pActor = RTTI_CAST(pForm, TESForm, Actor); + } + + InterpolationSystem::Update(pActor, interpolationComponent, tick); + } - RequestOwnershipClaim request; - request.ServerId = mountServerIdRes.value(); + auto animatedView = m_world.view(); - m_transport.Send(request); - } + for (auto entity : animatedView) + { + auto& animationComponent = animatedView.get(entity); + auto& formIdComponent = animatedView.get(entity); - MountRequest request; - request.MountId = mountServerIdRes.value(); - request.RiderId = riderServerIdRes.value(); + auto* pForm = TESForm::GetById(formIdComponent.Id); + auto* pActor = RTTI_CAST(pForm, TESForm, Actor); + if (!pActor) + continue; - m_transport.Send(request); -#endif -} + AnimationSystem::Update(m_world, pActor, animationComponent, tick); + } -void CharacterService::OnNotifyMount(const NotifyMount& acMessage) const noexcept -{ -#if TP_SKYRIM64 - auto remoteView = m_world.view(); + auto facegenView = m_world.view(); - const auto riderIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.RiderId](auto entity) + for(auto entity : facegenView) { - return remoteView.get(entity).Id == Id; - }); + auto& formIdComponent = facegenView.get(entity); + auto& faceGenComponent = facegenView.get(entity); - if (riderIt == std::end(remoteView)) - { - spdlog::warn("Rider with remote id {:X} not found.", acMessage.RiderId); - return; + const auto* pForm = TESForm::GetById(formIdComponent.Id); + auto* pActor = RTTI_CAST(pForm, TESForm, Actor); + if (!pActor) + continue; + + FaceGenSystem::Update(m_world, pActor, faceGenComponent); } - auto riderFormIdComponent = remoteView.get(*riderIt); - TESForm* pRiderForm = TESForm::GetById(riderFormIdComponent.Id); - Actor* pRider = RTTI_CAST(pRiderForm, TESForm, Actor); + auto waitingView = m_world.view(); - Actor* pMount = nullptr; + StackAllocator<1 << 13> allocator; + ScopedAllocator _{allocator}; - auto formView = m_world.view(); - for (auto entity : formView) + Vector toRemove; + for (auto entity : waitingView) { - std::optional serverIdRes = Utils::GetServerId(entity); - if (!serverIdRes.has_value()) - continue; - - uint32_t serverId = serverIdRes.value(); - - if (serverId == acMessage.MountId) - { - auto mountFormIdComponent = formView.get(entity); + auto& formIdComponent = waitingView.get(entity); + auto& remoteComponent = waitingView.get(entity); - if (m_world.all_of(entity)) - { - m_world.remove(entity); - m_world.emplace_or_replace(entity, acMessage.MountId, mountFormIdComponent.Id); - } + const auto* pForm = TESForm::GetById(formIdComponent.Id); + auto* pActor = RTTI_CAST(pForm, TESForm, Actor); + if (!pActor || !pActor->GetNiNode()) + continue; - TESForm* pMountForm = TESForm::GetById(mountFormIdComponent.Id); - pMount = RTTI_CAST(pMountForm, TESForm, Actor); - pMount->GetExtension()->SetRemote(true); + pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); + pActor->SetFactions(remoteComponent.SpawnRequest.FactionsContent); + pActor->LoadAnimationVariables(remoteComponent.SpawnRequest.LatestAction.Variables); - InterpolationSystem::Setup(m_world, entity); - AnimationSystem::Setup(m_world, entity); + if (pActor->IsDead() != remoteComponent.SpawnRequest.IsDead) + remoteComponent.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); - break; - } + toRemove.push_back(entity); } - pRider->InitiateMountPackage(pMount); -#endif + for (auto entity : toRemove) + { + // TODO: this used to be remove(), but this didn't work. + // Maybe check if other remove() instances are also compromised? + m_world.erase(entity); + } } -void CharacterService::OnInitPackageEvent(const InitPackageEvent& acEvent) const noexcept +void CharacterService::RunFactionsUpdates() const noexcept { - if (!m_transport.IsConnected()) + static std::chrono::steady_clock::time_point lastSendTimePoint; + constexpr auto cDelayBetweenSnapshots = 2000ms; + + const auto now = std::chrono::steady_clock::now(); + if (now - lastSendTimePoint < cDelayBetweenSnapshots) return; - auto view = m_world.view(); + lastSendTimePoint = now; - const auto actorIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.ActorId, view](auto entity) { - return view.get(entity).Id == id; - }); + RequestFactionsChanges message; - if (actorIt == std::end(view)) - return; + auto factionedActors = m_world.view(); + for (auto entity : factionedActors) + { + auto& formIdComponent = factionedActors.get(entity); + auto& localComponent = factionedActors.get(entity); + auto& cacheComponent = factionedActors.get(entity); - const entt::entity cActorEntity = *actorIt; + const auto* pForm = TESForm::GetById(formIdComponent.Id); + const auto* pActor = RTTI_CAST(pForm, TESForm, Actor); + if (!pActor) + continue; - std::optional actorServerIdRes = Utils::GetServerId(cActorEntity); - if (!actorServerIdRes.has_value()) - return; + // Check if cached factions and current factions are identical + auto factions = pActor->GetFactions(); - NewPackageRequest request; - request.ActorId = actorServerIdRes.value(); - if (!m_world.GetModSystem().GetServerModId(acEvent.PackageId, request.PackageId.ModId, request.PackageId.BaseId)) - return; + if (cacheComponent.FactionsContent == factions) + continue; - m_transport.Send(request); + cacheComponent.FactionsContent = factions; + + // If not send the current factions and replace the cached factions + message.Changes[localComponent.Id] = factions; + } + + if(!message.Changes.empty()) + m_transport.Send(message); } -void CharacterService::OnNotifyNewPackage(const NotifyNewPackage& acMessage) const noexcept +void CharacterService::RunSpawnUpdates() const noexcept { - auto remoteView = m_world.view(); - const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ActorId](auto entity) - { - return remoteView.get(entity).Id == Id; - }); + auto invisibleView = m_world.view(entt::exclude); - if (remoteIt == std::end(remoteView)) + for (auto entity : invisibleView) { - spdlog::warn("Actor for package with remote id {:X} not found.", acMessage.ActorId); - return; - } - - auto formIdComponent = remoteView.get(*remoteIt); + auto& remoteComponent = invisibleView.get(entity); + auto& interpolationComponent = invisibleView.get(entity); - const TESForm* pForm = TESForm::GetById(formIdComponent.Id); - Actor* pActor = RTTI_CAST(pForm, TESForm, Actor); + if (const auto pWorldSpace = PlayerCharacter::Get()->GetWorldSpace()) + { + float characterX = interpolationComponent.Position.x; + float characterY = interpolationComponent.Position.y; + const auto characterCoords = GridCellCoords::CalculateGridCellCoords(characterX, characterY); + const TES* pTES = TES::Get(); + const auto playerCoords = GridCellCoords::GridCellCoords(pTES->centerGridX, pTES->centerGridY); - const uint32_t cPackageFormId = World::Get().GetModSystem().GetGameId(acMessage.PackageId); - const TESForm* pPackageForm = TESForm::GetById(cPackageFormId); - if (!pPackageForm) - { - spdlog::warn("Actor package not found, base id: {:X}, mod id: {:X}", acMessage.PackageId.BaseId, - acMessage.PackageId.ModId); - return; - } + if (GridCellCoords::IsCellInGridCell(characterCoords, playerCoords)) + { + auto* pActor = RTTI_CAST(TESForm::GetById(remoteComponent.CachedRefId), TESForm, Actor); + if (!pActor) + { + pActor = CreateCharacterForEntity(entity); + if (!pActor) + { + continue; + } - TESPackage* pPackage = RTTI_CAST(pPackageForm, TESForm, TESPackage); + remoteComponent.CachedRefId = pActor->formID; + } - pActor->SetPackage(pPackage); + pActor->MoveTo(PlayerCharacter::Get()->parentCell, interpolationComponent.Position); + } + } + } } - diff --git a/Code/client/Services/Generic/EntityService.cpp b/Code/client/Services/Generic/EntityService.cpp index 5252e3676..e84bc6aa5 100644 --- a/Code/client/Services/Generic/EntityService.cpp +++ b/Code/client/Services/Generic/EntityService.cpp @@ -33,7 +33,6 @@ void EntityService::OnReferenceAdded(const ReferenceAddedEvent& acEvent) noexcep entt::entity entity; - // TODO: why would a reference already have a remote component? const auto view = m_world.view(); const auto it = std::find_if(std::begin(view), std::end(view), [&acEvent, view](entt::entity entity) { auto& remoteComponent = view.get(entity); diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index de4f6dcac..41f1eb78b 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -441,6 +441,79 @@ void CharacterService::OnFactionsChanges(const PacketEvent& acMessage) const noexcept +{ + auto packet = acMessage.Packet; + + NotifyProjectileLaunch notify{}; + + notify.ShooterID = packet.ShooterID; + + notify.OriginX = packet.OriginX; + notify.OriginY = packet.OriginY; + notify.OriginZ = packet.OriginZ; + + notify.ProjectileBaseID = packet.ProjectileBaseID; + notify.WeaponID = packet.WeaponID; + notify.AmmoID = packet.AmmoID; + + notify.ZAngle = packet.ZAngle; + notify.XAngle = packet.XAngle; + notify.YAngle = packet.YAngle; + + notify.ParentCellID = packet.ParentCellID; + + notify.SpellID = packet.SpellID; + notify.CastingSource = packet.CastingSource; + + notify.Area = packet.Area; + notify.Power = packet.Power; + notify.Scale = packet.Scale; + + notify.AlwaysHit = packet.AlwaysHit; + notify.NoDamageOutsideCombat = packet.NoDamageOutsideCombat; + notify.AutoAim = packet.AutoAim; + notify.DeferInitialization = packet.DeferInitialization; + notify.ForceConeOfFire = packet.ForceConeOfFire; + + notify.UnkBool1 = packet.UnkBool1; + notify.UnkBool2 = packet.UnkBool2; + + notify.ConeOfFireRadiusMult = packet.ConeOfFireRadiusMult; + notify.Tracer = packet.Tracer; + notify.IntentionalMiss = packet.IntentionalMiss; + notify.Allow3D = packet.Allow3D; + notify.Penetrates = packet.Penetrates; + notify.IgnoreNearCollisions = packet.IgnoreNearCollisions; + + const auto cShooterEntity = static_cast(packet.ShooterID); + GameServer::Get()->SendToPlayersInRange(notify, cShooterEntity, acMessage.GetSender()); +} + +void CharacterService::OnMountRequest(const PacketEvent& acMessage) const noexcept +{ + auto& message = acMessage.Packet; + + NotifyMount notify; + notify.RiderId = message.RiderId; + notify.MountId = message.MountId; + + const entt::entity cEntity = static_cast(message.MountId); + GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()); +} + +void CharacterService::OnNewPackageRequest(const PacketEvent& acMessage) const noexcept +{ + auto& message = acMessage.Packet; + + NotifyNewPackage notify; + notify.ActorId = message.ActorId; + notify.PackageId = message.PackageId; + + const entt::entity cEntity = static_cast(message.ActorId); + GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()); +} + void CharacterService::CreateCharacter(const PacketEvent& acMessage) const noexcept { auto& message = acMessage.Packet; @@ -655,75 +728,3 @@ void CharacterService::ProcessMovementChanges() const noexcept } } -void CharacterService::OnProjectileLaunchRequest(const PacketEvent& acMessage) const noexcept -{ - auto packet = acMessage.Packet; - - NotifyProjectileLaunch notify{}; - - notify.ShooterID = packet.ShooterID; - - notify.OriginX = packet.OriginX; - notify.OriginY = packet.OriginY; - notify.OriginZ = packet.OriginZ; - - notify.ProjectileBaseID = packet.ProjectileBaseID; - notify.WeaponID = packet.WeaponID; - notify.AmmoID = packet.AmmoID; - - notify.ZAngle = packet.ZAngle; - notify.XAngle = packet.XAngle; - notify.YAngle = packet.YAngle; - - notify.ParentCellID = packet.ParentCellID; - - notify.SpellID = packet.SpellID; - notify.CastingSource = packet.CastingSource; - - notify.Area = packet.Area; - notify.Power = packet.Power; - notify.Scale = packet.Scale; - - notify.AlwaysHit = packet.AlwaysHit; - notify.NoDamageOutsideCombat = packet.NoDamageOutsideCombat; - notify.AutoAim = packet.AutoAim; - notify.DeferInitialization = packet.DeferInitialization; - notify.ForceConeOfFire = packet.ForceConeOfFire; - - notify.UnkBool1 = packet.UnkBool1; - notify.UnkBool2 = packet.UnkBool2; - - notify.ConeOfFireRadiusMult = packet.ConeOfFireRadiusMult; - notify.Tracer = packet.Tracer; - notify.IntentionalMiss = packet.IntentionalMiss; - notify.Allow3D = packet.Allow3D; - notify.Penetrates = packet.Penetrates; - notify.IgnoreNearCollisions = packet.IgnoreNearCollisions; - - const auto cShooterEntity = static_cast(packet.ShooterID); - GameServer::Get()->SendToPlayersInRange(notify, cShooterEntity, acMessage.GetSender()); -} - -void CharacterService::OnMountRequest(const PacketEvent& acMessage) const noexcept -{ - auto& message = acMessage.Packet; - - NotifyMount notify; - notify.RiderId = message.RiderId; - notify.MountId = message.MountId; - - const entt::entity cEntity = static_cast(message.MountId); - GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()); -} - -void CharacterService::OnNewPackageRequest(const PacketEvent& acMessage) const noexcept -{ - auto& message = acMessage.Packet; - - NotifyNewPackage notify; - notify.ActorId = message.ActorId; - notify.PackageId = message.PackageId; - - const entt::entity cEntity = static_cast(message.ActorId); - GameServer::Get()->SendToPlayersInRange(notify, cEntity, acMessage.GetSender()); -} From 6539d279d9b6dc4d00c369325a0f76549d0f0039 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Mar 2022 17:43:24 +0100 Subject: [PATCH 46/48] WIP: RemoveItem() --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 10 +++++----- Code/client/Games/Skyrim/TESObjectREFR.h | 12 +++++++++++- Code/client/Services/Debug/TestService.cpp | 6 +++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 33641765a..6e67b3a97 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -24,7 +24,7 @@ TP_THIS_FUNCTION(TActivate, void, TESObjectREFR, TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing); TP_THIS_FUNCTION(TAddInventoryItem, void*, TESObjectREFR, TESBoundObject* apItem, ExtraDataList* apExtraData, uint32_t aCount, TESObjectREFR* apOldOwner); -TP_THIS_FUNCTION(TRemoveInventoryItem, void*, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, ExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3); +TP_THIS_FUNCTION(TRemoveInventoryItem, BSPointerHandle*, TESObjectREFR, BSPointerHandle* apResult, TESBoundObject* apItem, int32_t aCount, ITEM_REMOVE_REASON aReason, ExtraDataList* apExtraList, TESObjectREFR* apMoveToRef, const NiPoint3* apDropLoc, const NiPoint3* apRotate); TP_THIS_FUNCTION(TPlayAnimationAndWait, bool, void, uint32_t auiStackID, TESObjectREFR* apSelf, BSFixedString* apAnimation, BSFixedString* apEventName); TP_THIS_FUNCTION(TPlayAnimation, bool, void, uint32_t auiStackID, TESObjectREFR* apSelf, BSFixedString* apEventName); @@ -73,8 +73,8 @@ void TESObjectREFR::Save_Reversed(const uint32_t aChangeFlags, Buffer::Writer& a if (aChangeFlags & CHANGE_REFR_ANIMATION) { - // do something with animations - // get extradata 0x41 + // do something with animations + // get extradata 0x41 } @@ -536,11 +536,11 @@ void* TP_MAKE_THISCALL(HookAddInventoryItem, TESObjectREFR, TESBoundObject* apIt return ThisCall(RealAddInventoryItem, apThis, apItem, apExtraData, aCount, apOldOwner); } -void* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObjectREFR, float* apUnk0, TESBoundObject* apItem, uint32_t aCount, uint32_t aUnk1, ExtraDataList* apExtraData, TESObjectREFR* apNewOwner, NiPoint3* apUnk2, NiPoint3* apUnk3) +BSPointerHandle* TP_MAKE_THISCALL(HookRemoveInventoryItem, TESObjectREFR, BSPointerHandle* apResult, TESBoundObject* apItem, int32_t aCount, ITEM_REMOVE_REASON aReason, ExtraDataList* apExtraList, TESObjectREFR* apMoveToRef, const NiPoint3* apDropLoc, const NiPoint3* apRotate) { if (!g_modifyingInventory) World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID)); - return ThisCall(RealRemoveInventoryItem, apThis, apUnk0, apItem, aCount, aUnk1, apExtraData, apNewOwner, apUnk2, apUnk3); + return ThisCall(RealRemoveInventoryItem, apThis, apResult, apItem, aCount, aReason, apExtraList, apMoveToRef, apDropLoc, apRotate); } static TiltedPhoques::Initializer s_objectReferencesHooks([]() { diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 3a7761687..20491c8a2 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -17,6 +17,16 @@ struct TESWorldSpace; struct TESBoundObject; struct TESContainer; +enum class ITEM_REMOVE_REASON +{ + kRemove, + kSteal, + kSelling, + kDropping, + kStoreInContainer, + kStoreInTeammate +}; + struct TESObjectREFR : TESForm { enum ChangeFlags : uint32_t @@ -69,7 +79,7 @@ struct TESObjectREFR : TESForm virtual void sub_53(); virtual void sub_54(); virtual void sub_55(); - virtual void sub_56(); + virtual BSPointerHandle RemoveItem(TESBoundObject* apItem, int32_t aCount, ITEM_REMOVE_REASON aReason, ExtraDataList* apExtraList, TESObjectREFR* apMoveToRef, const NiPoint3* apDropLoc = nullptr, const NiPoint3* apRotate = nullptr); virtual void sub_57(); virtual void sub_58(); virtual void sub_59(); diff --git a/Code/client/Services/Debug/TestService.cpp b/Code/client/Services/Debug/TestService.cpp index a4c5827f2..607a15c2c 100644 --- a/Code/client/Services/Debug/TestService.cpp +++ b/Code/client/Services/Debug/TestService.cpp @@ -116,7 +116,11 @@ void TestService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - PlaceActorInWorld(); + //PlaceActorInWorld(); + + auto* pItem = RTTI_CAST(TESForm::GetById(0x135B8), TESForm, TESBoundObject); + PlayerCharacter::Get()->RemoveItem(pItem, 1, ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); + //EquipManager::Get()->UnEquip(PlayerCharacter::Get(), pItem, nullptr, 1, DefaultObjectManager::Get().rightEquipSlot, true, false, true, false, nullptr); } } else From f3c74ae68de8e2f84be70aaf77a10e6cce5ddd50 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Mar 2022 18:01:44 +0100 Subject: [PATCH 47/48] tweak: GetItemExtraData() --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 141 +++++++++++---------- Code/client/Games/Skyrim/TESObjectREFR.h | 1 + 2 files changed, 76 insertions(+), 66 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 6e67b3a97..3546ad685 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -152,6 +152,80 @@ int64_t TESObjectREFR::GetItemCountInInventory(TESForm* apItem) const noexcept return count; } +void TESObjectREFR::GetItemExtraData(Inventory::Entry& arEntry, ExtraDataList* apExtraDataList) const noexcept +{ + auto& modSystem = World::Get().GetModSystem(); + + if (ExtraCount* pExtraCount = (ExtraCount*)apExtraDataList->GetByType(ExtraData::Count)) + { + arEntry.Count = pExtraCount->count; + } + + if (ExtraCharge* pExtraCharge = (ExtraCharge*)apExtraDataList->GetByType(ExtraData::Charge)) + { + arEntry.ExtraCharge = pExtraCharge->fCharge; + } + + if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)apExtraDataList->GetByType(ExtraData::Enchantment)) + { + TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); + + modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, arEntry.ExtraEnchantId); + + if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) + { + for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) + { + TP_ASSERT(pEffectItem, "pEffectItem is null."); + if (!pEffectItem) + continue; + + Inventory::EffectItem effect; + effect.Magnitude = pEffectItem->data.fMagnitude; + effect.Area = pEffectItem->data.iArea; + effect.Duration = pEffectItem->data.iDuration; + effect.RawCost = pEffectItem->fRawCost; + modSystem.GetServerModId(pEffectItem->pEffectSetting->formID, effect.EffectId); + arEntry.EnchantData.Effects.push_back(effect); + } + + uint32_t objectId = modSystem.GetGameId(arEntry.BaseId); + arEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; + } + + arEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; + arEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; + } + + if (ExtraHealth* pExtraHealth = (ExtraHealth*)apExtraDataList->GetByType(ExtraData::Health)) + { + arEntry.ExtraHealth = pExtraHealth->fHealth; + } + + if (ExtraPoison* pExtraPoison = (ExtraPoison*)apExtraDataList->GetByType(ExtraData::Poison)) + { + TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); + modSystem.GetServerModId(pExtraPoison->pPoison->formID, arEntry.ExtraPoisonId); + arEntry.ExtraPoisonCount = pExtraPoison->uiCount; + } + + if (ExtraSoul* pExtraSoul = (ExtraSoul*)apExtraDataList->GetByType(ExtraData::Soul)) + { + arEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; + } + + if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)apExtraDataList->GetByType(ExtraData::TextDisplayData)) + { + if (pExtraTextDisplayData->DisplayName) + arEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; + else + arEntry.ExtraTextDisplayName = ""; + } + + arEntry.ExtraWorn = apExtraDataList->Contains(ExtraData::Worn); + arEntry.ExtraWornLeft = apExtraDataList->Contains(ExtraData::WornLeft); +} + Inventory TESObjectREFR::GetInventory() const noexcept { auto& modSystem = World::Get().GetModSystem(); @@ -197,72 +271,7 @@ Inventory TESObjectREFR::GetInventory() const noexcept innerEntry.BaseId = entry.BaseId; innerEntry.Count = 1; - if (ExtraCount* pExtraCount = (ExtraCount*)pExtraDataList->GetByType(ExtraData::Count)) - { - innerEntry.Count = pExtraCount->count; - } - - if (ExtraCharge* pExtraCharge = (ExtraCharge*)pExtraDataList->GetByType(ExtraData::Charge)) - { - innerEntry.ExtraCharge = pExtraCharge->fCharge; - } - - if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)pExtraDataList->GetByType(ExtraData::Enchantment)) - { - TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); - - modSystem.GetServerModId(pExtraEnchantment->pEnchantment->formID, innerEntry.ExtraEnchantId); - - if (pExtraEnchantment->pEnchantment->formID & 0xFF000000) - { - for (EffectItem* pEffectItem : pExtraEnchantment->pEnchantment->listOfEffects) - { - TP_ASSERT(pEffectItem, "pEffectItem is null."); - - Inventory::EffectItem effect; - effect.Magnitude = pEffectItem->data.fMagnitude; - effect.Area = pEffectItem->data.iArea; - effect.Duration = pEffectItem->data.iDuration; - effect.RawCost = pEffectItem->fRawCost; - modSystem.GetServerModId(pEffectItem->pEffectSetting->formID, effect.EffectId); - innerEntry.EnchantData.Effects.push_back(effect); - } - - uint32_t objectId = modSystem.GetGameId(innerEntry.BaseId); - innerEntry.EnchantData.IsWeapon = TESForm::GetById(objectId)->formType == FormType::Weapon; - } - - innerEntry.ExtraEnchantCharge = pExtraEnchantment->usCharge; - innerEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; - } - - if (ExtraHealth* pExtraHealth = (ExtraHealth*)pExtraDataList->GetByType(ExtraData::Health)) - { - innerEntry.ExtraHealth = pExtraHealth->fHealth; - } - - if (ExtraPoison* pExtraPoison = (ExtraPoison*)pExtraDataList->GetByType(ExtraData::Poison)) - { - TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); - modSystem.GetServerModId(pExtraPoison->pPoison->formID, innerEntry.ExtraPoisonId); - innerEntry.ExtraPoisonCount = pExtraPoison->uiCount; - } - - if (ExtraSoul* pExtraSoul = (ExtraSoul*)pExtraDataList->GetByType(ExtraData::Soul)) - { - innerEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; - } - - if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)pExtraDataList->GetByType(ExtraData::TextDisplayData)) - { - if (pExtraTextDisplayData->DisplayName) - innerEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; - else - innerEntry.ExtraTextDisplayName = ""; - } - - innerEntry.ExtraWorn = pExtraDataList->Contains(ExtraData::Worn); - innerEntry.ExtraWornLeft = pExtraDataList->Contains(ExtraData::WornLeft); + GetItemExtraData(innerEntry, pExtraDataList); entry.Count -= innerEntry.Count; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 20491c8a2..1dafd670d 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -159,6 +159,7 @@ struct TESObjectREFR : TESForm Lock* GetLock() noexcept; TESContainer* GetContainer() const noexcept; int64_t GetItemCountInInventory(TESForm* apItem) const noexcept; + void GetItemExtraData(Inventory::Entry& arEntry, ExtraDataList* apExtraDataList) const noexcept; void SaveInventory(BGSSaveFormBuffer* apBuffer) const noexcept; void SaveAnimationVariables(AnimationVariables& aWriter) const noexcept; From b9a850bd00646101a5590af0b141ff74d3de0675 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 14 Mar 2022 18:13:35 +0100 Subject: [PATCH 48/48] tweak: refactored ugly c style casts --- Code/client/Games/Skyrim/TESObjectREFR.cpp | 23 ++++++++++++---------- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 3546ad685..f993e253c 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -156,17 +156,17 @@ void TESObjectREFR::GetItemExtraData(Inventory::Entry& arEntry, ExtraDataList* a { auto& modSystem = World::Get().GetModSystem(); - if (ExtraCount* pExtraCount = (ExtraCount*)apExtraDataList->GetByType(ExtraData::Count)) + if (ExtraCount* pExtraCount = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Count), BSExtraData, ExtraCount)) { arEntry.Count = pExtraCount->count; } - if (ExtraCharge* pExtraCharge = (ExtraCharge*)apExtraDataList->GetByType(ExtraData::Charge)) + if (ExtraCharge* pExtraCharge = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Charge), BSExtraData, ExtraCharge)) { arEntry.ExtraCharge = pExtraCharge->fCharge; } - if (ExtraEnchantment* pExtraEnchantment = (ExtraEnchantment*)apExtraDataList->GetByType(ExtraData::Enchantment)) + if (ExtraEnchantment* pExtraEnchantment = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Enchantment), BSExtraData, ExtraEnchantment)) { TP_ASSERT(pExtraEnchantment->pEnchantment, "Null enchantment in ExtraEnchantment"); @@ -197,24 +197,27 @@ void TESObjectREFR::GetItemExtraData(Inventory::Entry& arEntry, ExtraDataList* a arEntry.ExtraEnchantRemoveUnequip = pExtraEnchantment->bRemoveOnUnequip; } - if (ExtraHealth* pExtraHealth = (ExtraHealth*)apExtraDataList->GetByType(ExtraData::Health)) + if (ExtraHealth* pExtraHealth = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Health), BSExtraData, ExtraHealth)) { arEntry.ExtraHealth = pExtraHealth->fHealth; } - if (ExtraPoison* pExtraPoison = (ExtraPoison*)apExtraDataList->GetByType(ExtraData::Poison)) + if (ExtraPoison* pExtraPoison = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Poison), BSExtraData, ExtraPoison)) { TP_ASSERT(pExtraPoison->pPoison, "Null poison in ExtraPoison"); - modSystem.GetServerModId(pExtraPoison->pPoison->formID, arEntry.ExtraPoisonId); - arEntry.ExtraPoisonCount = pExtraPoison->uiCount; + if (pExtraPoison) + { + modSystem.GetServerModId(pExtraPoison->pPoison->formID, arEntry.ExtraPoisonId); + arEntry.ExtraPoisonCount = pExtraPoison->uiCount; + } } - if (ExtraSoul* pExtraSoul = (ExtraSoul*)apExtraDataList->GetByType(ExtraData::Soul)) + if (ExtraSoul* pExtraSoul = RTTI_CAST(apExtraDataList->GetByType(ExtraData::Soul), BSExtraData, ExtraSoul)) { arEntry.ExtraSoulLevel = (int32_t)pExtraSoul->cSoul; } - if (ExtraTextDisplayData* pExtraTextDisplayData = (ExtraTextDisplayData*)apExtraDataList->GetByType(ExtraData::TextDisplayData)) + if (ExtraTextDisplayData* pExtraTextDisplayData = RTTI_CAST(apExtraDataList->GetByType(ExtraData::TextDisplayData), BSExtraData, ExtraTextDisplayData)) { if (pExtraTextDisplayData->DisplayName) arEntry.ExtraTextDisplayName = pExtraTextDisplayData->DisplayName; @@ -337,7 +340,7 @@ Inventory TESObjectREFR::GetInventory() const noexcept thread_local bool g_modifyingInventory = false; -void TESObjectREFR::SetInventory(Inventory& aInventory) noexcept +void TESObjectREFR::SetInventory(const Inventory& aInventory) noexcept { spdlog::info("Setting inventory for {:X}", formID); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 1dafd670d..a62e630a2 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -187,7 +187,7 @@ struct TESObjectREFR : TESForm void EnableImpl() noexcept; Inventory GetInventory() const noexcept; - void SetInventory(Inventory& acContainer) noexcept; + void SetInventory(const Inventory& acContainer) noexcept; void AddItem(const Inventory::Entry& arEntry) noexcept;