From 01a04ef159db13c4a13f41a0506dad552a3afda7 Mon Sep 17 00:00:00 2001 From: Ege Yilmaz <47591838+Quantumyilmaz@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:37:00 +0100 Subject: [PATCH 1/4] Refactor Manager class and update method organization Reorganized and grouped methods in Manager.h for clarity, moving serialization, Papyrus, hooks, and event methods into logical sections. Updated method signatures and moved some method implementations in Manager.cpp for better structure. Replaced OnLongPressEquip with OnOpen in Hooks.cpp and Manager.cpp. No functional logic was changed, but code readability and maintainability are improved. --- include/Manager.h | 75 ++- src/Hooks.cpp | 2 +- src/Manager.cpp | 1159 ++++++++++++++++++++++----------------------- src/SkyPrompt.cpp | 12 +- 4 files changed, 611 insertions(+), 637 deletions(-) diff --git a/include/Manager.h b/include/Manager.h index 43219fa..2703fd3 100644 --- a/include/Manager.h +++ b/include/Manager.h @@ -147,73 +147,64 @@ class Manager final : public SaveLoadData, void TransferOnUse(RefID a_chestID) const; + [[nodiscard]] bool IsARegistry(RefID registry) const; + void Gateway(int result, const RE::ObjectRefHandle& a_current_container); + public: - std::atomic isUninstalled = false; const char* GetType() override { return "Manager"; } - void Init(); - void Gateway(int result, const RE::ObjectRefHandle& a_current_container); + std::atomic isUninstalled = false; + void Init(); + + // Serialization + void Reset(); + void SendData(); + void ReceiveData(); + + // Papyrus + void RenameContainer(const std::string& new_name, RE::TESBoundObject* a_fake); + // Hooks void UpdateLoc(FormID fakeID, RefID loc_id); - void OnLongPressEquip(const RE::TESBoundObject* a_fake, int delay = 0); Count CanBeAdded(const RE::TESBoundObject* a_item, Count a_count, RefID a_chestID); - [[nodiscard]] RE::TESBoundObject* FakeToRealContainer(FormID fake) const; - - void OnActivateContainer(RE::TESObjectREFR* a_container, int msgbox_action, int a_delay = 0); - - // places fake objects in external containers after load game - void HandleFakePlacement(RE::TESObjectREFR* external_cont); - - [[nodiscard]] bool IsFakeContainer(FormID formid) const; - - // Checks if realcontainer_formid is in the sources - [[nodiscard]] bool IsRealContainer(FormID formid) const; - // Checks if ref has formid in the sources - [[nodiscard]] bool IsRealContainer(const RE::TESObjectREFR* ref) const; - - void RenameContainer(const std::string& new_name, RE::TESBoundObject* a_fake); - void OnChestExit(RE::TESObjectREFR* a_chest); void OnChestEnter(RE::TESObjectREFR* a_chest); - - [[nodiscard]] bool IsARegistry(RefID registry) const; - - void HandleCraftingEnter(RefID a_furn) const; - void HandleCraftingExit(); - void HandleDrop(RE::TESObjectREFR* fake_object); void BeforePickup(RE::TESObjectREFR* picked_up_by, RE::TESObjectREFR* a_object); void OnConsume(FormID fake_formid, RE::TESObjectREFR* consumed_by); void HandleSell(FormID a_fake, RE::TESObjectREFR* sell_ref); - - void HandleFormDelete(RefID refid); - - // checks if the refid is in the ChestToFakeContainer, i.e. if it is an unownedchest [[nodiscard]] bool IsChest(RefID a_refid) const; + [[nodiscard]] bool IsRealContainer(FormID formid) const; + [[nodiscard]] bool IsFakeContainer(FormID formid) const; + RE::TESBoundObject* GetFakeBound(const RE::TESObjectREFR* a_loc) const; - void Reset(); - - void Print(); - - void SendData(); - - void ReceiveData(); + // Events + void HandleFakePlacement(RE::TESObjectREFR* external_cont); + void HandleCraftingEnter(RefID a_furn) const; + void HandleCraftingExit(); + void HandleFormDelete(RefID refid); + [[nodiscard]] bool IsRealContainer(const RE::TESObjectREFR* ref) const; + bool IsInChestMenu() const { return !reals_to_takeback.empty(); } + bool IsChestMenuQueued() const { return !queued_chests.empty(); } + // MCP std::vector GetSources() const; - void Uninstall(); - RE::TESBoundObject* GetFakeBound(const RE::TESObjectREFR* a_loc) const; + // SkyPrompt + [[nodiscard]] RE::TESBoundObject* FakeToRealContainer(FormID fake) const; + void OnPromptAccept(RE::TESObjectREFR* a_container, int msgbox_action, int a_delay = 0); std::string GetWeightText(RE::TESObjectREFR* a_container); std::string GetWeightText(const RE::TESBoundObject* fake_or_real); std::string GetValueText(RE::TESObjectREFR* a_loc); - void CloseMenu(); RE::TESBoundObject* RegisterFromMenu(RE::InventoryEntryData* a_real_entry, RE::TESObjectREFR* a_owner); - bool IsInChestMenu() const { return !reals_to_takeback.empty(); } - bool IsChestMenuQueued() const { return !queued_chests.empty(); } static void RenameCallback(RE::TESBoundObject* a_fake); + // Hooks + SkyPrompt + void OnOpen(const RE::TESBoundObject* a_fake, int delay = 0); + void CloseMenu(); + template static void Rename(const std::string& new_name, T item) { if (!item) logger::warn("Item not found"); diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 61e2001..b60fbf4 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -51,7 +51,7 @@ bool Hooks::HandleEquip(RE::InputEvent* event) { } if (button_event->HeldDuration() > 0.25f) { Manager::GetSingleton()->CloseMenu(); - Manager::GetSingleton()->OnLongPressEquip(selected_item); + Manager::GetSingleton()->OnOpen(selected_item); } else if (button_event->IsUp()) { const auto player = RE::PlayerCharacter::GetSingleton(); Inventory::ToggleEquip(selected_item); diff --git a/src/Manager.cpp b/src/Manager.cpp index ee787b6..d7ba4a3 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -88,57 +88,6 @@ namespace { // Avoid Windows GetObject macro conflicts in this file #undef GetObject -// ================= NoLock helper implementations ================= -const Source* Manager::GetContainerSource_NoLock(const FormID real_id) const noexcept { - for (auto& src : sources) { - if (src.formid == real_id) return &src; - } - return nullptr; -} - -Source* Manager::GetContainerSource_NoLock(const FormID real_id) noexcept { - for (auto& src : sources) { - if (src.formid == real_id) return &src; - } - return nullptr; -} - -FormID Manager::GetRealID_NoLock(const RefID chest_id) const noexcept { - if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) - return it->second. - outerKey; - return 0; -} - -FormID Manager::GetFakeID_NoLock(const RefID chest_id) const noexcept { - if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) - return it->second. - innerKey; - return 0; -} - -RefID Manager::GetFakeContainerChestID_NoLock(const FormID fake_id) const noexcept { - for (auto& [chest_ref, cont_forms] : ChestToFakeContainer) { - if (cont_forms.innerKey == fake_id) return chest_ref; - } - return 0; -} - -RE::TESBoundObject* Manager::GetFakeBound_NoLock(const RefID chest_id) const noexcept { - if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) { - return RE::TESForm::LookupByID(it->second.innerKey); - } - return nullptr; -} - -bool Manager::IsChest_NoLock(const RefID a_refid) const noexcept { - return ChestToFakeContainer.contains(a_refid); -} - -Source* Manager::GetChestSource_NoLock(const RefID a_chestID) { - return GetContainerSource_NoLock(GetRealID_NoLock(a_chestID)); -} - void Manager::TakeBackReal(RE::TESBoundObject* real_obj, RE::TESObjectREFR* chest) { const auto unownedChestOG = RE::TESForm::LookupByID(UnownedStuff::unownedChestOGRefID); if (!unownedChestOG) return RaiseMngrErr("MsgBoxCallback unownedChestOG is null"); @@ -320,6 +269,57 @@ Source* Manager::GetChestSource(const RefID a_chestID) { return GetContainerSource(GetRealID(a_chestID)); } +// ================= NoLock helper implementations ================= +const Source* Manager::GetContainerSource_NoLock(const FormID real_id) const noexcept { + for (auto& src : sources) { + if (src.formid == real_id) return &src; + } + return nullptr; +} + +Source* Manager::GetContainerSource_NoLock(const FormID real_id) noexcept { + for (auto& src : sources) { + if (src.formid == real_id) return &src; + } + return nullptr; +} + +FormID Manager::GetRealID_NoLock(const RefID chest_id) const noexcept { + if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) + return it->second. + outerKey; + return 0; +} + +FormID Manager::GetFakeID_NoLock(const RefID chest_id) const noexcept { + if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) + return it->second. + innerKey; + return 0; +} + +RefID Manager::GetFakeContainerChestID_NoLock(const FormID fake_id) const noexcept { + for (auto& [chest_ref, cont_forms] : ChestToFakeContainer) { + if (cont_forms.innerKey == fake_id) return chest_ref; + } + return 0; +} + +RE::TESBoundObject* Manager::GetFakeBound_NoLock(const RefID chest_id) const noexcept { + if (const auto it = ChestToFakeContainer.find(chest_id); it != ChestToFakeContainer.end()) { + return RE::TESForm::LookupByID(it->second.innerKey); + } + return nullptr; +} + +bool Manager::IsChest_NoLock(const RefID a_refid) const noexcept { + return ChestToFakeContainer.contains(a_refid); +} + +Source* Manager::GetChestSource_NoLock(const RefID a_chestID) { + return GetContainerSource_NoLock(GetRealID_NoLock(a_chestID)); +} + bool Manager::HasItemPlusCleanUp(RE::TESBoundObject* item, RE::TESObjectREFR* item_owner) { const auto inventory = item_owner->GetInventory(); if (const auto entry = inventory.find(item); entry == inventory.end()) return false; @@ -360,6 +360,21 @@ void Manager::UpdateFakeWV(RE::TESBoundObject* fake_form, RE::TESObjectREFR* che } +void Manager::UpdateFakeWV(RE::TESBoundObject* fake_form) { + const auto chestID = GetFakeContainerChestID(fake_form->GetFormID()); + const auto chestRef = RE::TESForm::LookupByID(chestID); + if (!chestRef) { + logger::error("Chest ref not found."); + return; + } + const auto src = GetChestSource(chestID); + if (!src) { + logger::error("Source not found."); + return; + } + UpdateFakeWV(fake_form, chestRef, src->weight_ratio); +} + void Manager::HandleFormDelete_(const RefID chest_refid) { auto real_formid = GetRealID(chest_refid); if (const auto real_item = RE::TESForm::LookupByID(real_formid)) { @@ -483,39 +498,6 @@ void Manager::FakePlacement_Sub(RE::TESObjectREFR* chest, RE::TESObjectREFR* sav saved_loc->AddObjectToContainer(fake_bound_new, xList_fake, 1, nullptr); } -void Manager::FakePlacementCeption(const RefID chest_ref, std::vector& ha) { - if (std::ranges::find(ha, chest_ref) != ha.end()) return; - ha.push_back(chest_ref); - logger::info("-------------------chest_ref: {:x} -------------------", chest_ref); - for (const auto& connected_chest : GetChildChests(chest_ref, nullptr)) { - logger::info("Connected chest: {:x}", connected_chest); - FakePlacementCeption(connected_chest, ha); - } - RefID saved_loc = 0; - bool error = false; - { - SHARED_GUARD; - const auto src = GetChestSource_NoLock(chest_ref); - if (!src) { - logger::error("Could not find source for container {:x}", chest_ref); - error = true; - } else { - const auto it = src->data.find(chest_ref); - if (it == src->data.end()) { - logger::error("Source data missing chest_ref {:x}", chest_ref); - error = true; - } else { - saved_loc = it->second; - } - } - } - - if (error) return RaiseMngrErr("Error in FakePlacementCeption"); - - FakePlacement(saved_loc, chest_ref); - logger::info("-------------------chest_ref: {:x} DONE -------------------", chest_ref); -} - void Manager::FakePlacement(RefID saved_loc, const RefID chest_refID, RE::TESObjectREFR* external_cont) { if (Settings::is_pre_0_10_0) { if (chest_refID == saved_loc) saved_loc = player_refid; @@ -565,6 +547,39 @@ void Manager::FakePlacement(RefID saved_loc, const RefID chest_refID, RE::TESObj if (Settings::other_settings[Settings::otherstuffKeys[1]]) RemoveCarryWeightBoost(fakeid, saved_loc_ref); } +void Manager::FakePlacementCeption(const RefID chest_ref, std::vector& ha) { + if (std::ranges::find(ha, chest_ref) != ha.end()) return; + ha.push_back(chest_ref); + logger::info("-------------------chest_ref: {:x} -------------------", chest_ref); + for (const auto& connected_chest : GetChildChests(chest_ref, nullptr)) { + logger::info("Connected chest: {:x}", connected_chest); + FakePlacementCeption(connected_chest, ha); + } + RefID saved_loc = 0; + bool error = false; + { + SHARED_GUARD; + const auto src = GetChestSource_NoLock(chest_ref); + if (!src) { + logger::error("Could not find source for container {:x}", chest_ref); + error = true; + } else { + const auto it = src->data.find(chest_ref); + if (it == src->data.end()) { + logger::error("Source data missing chest_ref {:x}", chest_ref); + error = true; + } else { + saved_loc = it->second; + } + } + } + + if (error) return RaiseMngrErr("Error in FakePlacementCeption"); + + FakePlacement(saved_loc, chest_ref); + logger::info("-------------------chest_ref: {:x} DONE -------------------", chest_ref); +} + void Manager::RemoveCarryWeightBoost(const FormID item_formid, RE::TESObjectREFR* inventory_owner) { if (!inventory_owner) return; const auto item_obj = RE::TESForm::LookupByID(item_formid); @@ -710,74 +725,14 @@ bool Manager::DeRegister_Sub(const FormID master_formID, const RefID chest_refID return true; } -void Manager::Init() { - player_ref = RE::PlayerCharacter::GetSingleton()->As(); - bool init_failed = false; - sources = Settings::LoadSources(); - if (sources.empty()) { - logger::error("No sources found."); - return InitFailed(); - } - - std::unordered_set encounteredFormIDs; - - for (auto& src : sources) { - const auto form_ = FormReader::GetFormByID(src.formid, src.editorid); - if (const auto bound_ = src.GetBoundObject(); !form_ || !bound_) { - init_failed = true; - logger::error("Missing source: {:x}, {}", src.formid, src.editorid); - break; - } - auto formtype_ = RE::FormTypeToString(form_->GetFormType()); - if (std::string formtypeString(formtype_); !Settings::AllowedFormTypes.contains(formtypeString)) { - init_failed = true; - MsgBoxesNotifs::InGame::FormTypeErr(form_->GetFormID()); - logger::error("Invalid source type: {}", formtype_); - break; - } - if (!encounteredFormIDs.insert(src.formid).second) { - logger::error("Duplicate formid found: {}", src.formid); - init_failed = true; - } - } - - const auto unownedChestOG = RE::TESForm::LookupByID(0x000EA29A); - unownedChest = RE::TESForm::LookupByID(UnownedStuff::unownedChestFormID); - unownedCell = RE::TESForm::LookupByID(0x000EA28B); - if (!unownedChestOG || unownedChestOG->GetBaseObject()->GetFormID() != unownedChest->GetFormID() || !unownedCell || - !unownedChest || !unownedChest->As()) { - logger::error("Missing unowned chest/cell"); - init_failed = true; - } - if (Settings::is_pre_0_7_1 && unownedChestOG) { - for (auto& [fst,snd] : unownedChestOG->GetInventory()) { - unownedChestOG->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, player_ref); - if (fst->IsDynamicForm()) - player_ref->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, - nullptr); - } - } - if (init_failed) return InitFailed(); - logger::info("Manager initialized."); +FormID Manager::GetFakeID(const RefID chest_id) const { + SHARED_GUARD; + return GetFakeID_NoLock(chest_id); } -void Manager::Gateway(const int result, const RE::ObjectRefHandle& a_current_container) { - if (result != 0 && result != 1) { - logger::error("Unexpected callback integer!"); - return; - } - const auto a_chest_handle = a_current_container.get(); - const auto a_chest = a_chest_handle ? GetContainerChest(a_chest_handle.get()) : nullptr; - if (!a_chest) { - logger::warn("Current container is null."); - } else if (result) { - RenameCallback(GetFakeBound(a_chest->GetFormID())); - } else if (!ActivateChest(a_chest)) { - reals_to_takeback.clear(); - queued_chests.clear(); - Animations::SendAnimEvent(3, nullptr); - logger::warn("Chest not found."); - } +FormID Manager::GetRealID(const RefID chest_id) const { + SHARED_GUARD; + return GetRealID_NoLock(chest_id); } RefID Manager::GetContainerChestID(const RefID a_loc_refid) const { @@ -804,71 +759,6 @@ RE::TESBoundObject* Manager::GetRealBound(const RefID chest_id) const { return RE::TESForm::LookupByID(GetRealID(chest_id)); } -FormID Manager::GetFakeID(const RefID chest_id) const { - SHARED_GUARD; - return GetFakeID_NoLock(chest_id); -} - -FormID Manager::GetRealID(const RefID chest_id) const { - SHARED_GUARD; - return GetRealID_NoLock(chest_id); -} - -void Manager::BeforePickup(RE::TESObjectREFR* picked_up_by, RE::TESObjectREFR* a_object) { - if (const auto chest = GetContainerChest(a_object)) { - const auto chest_refid = chest->GetFormID(); - - RE::TESBoundObject* fake_bound = nullptr; - float weight_ratio = 0.f; - { - UNIQUE_GUARD; - if (const auto src = GetContainerSource_NoLock(GetRealID_NoLock(chest_refid))) { - weight_ratio = src->weight_ratio; - if (fake_bound = GetFakeBound_NoLock(chest_refid); fake_bound && src->data.contains(chest_refid)) { - src->data.at(chest_refid) = picked_up_by->GetFormID(); - } else { - logger::critical("Fake bound not found."); - } - } - } - - if (fake_bound) { - WorldObject::SwapObjects(a_object, fake_bound, false); - UpdateFakeWV(fake_bound, chest, weight_ratio); - if (Settings::other_settings[Settings::otherstuffKeys[1]]) { - auto ref_handle = picked_up_by->GetHandle(); - const auto fake_id = fake_bound->GetFormID(); - SKSE::GetTaskInterface()->AddTask([this, fake_id, ref_handle]() { - if (const auto ref = ref_handle.get()) { - RemoveCarryWeightBoost(fake_id, ref.get()); - } - }); - } - } - } -} - -void Manager::HandleDrop(RE::TESObjectREFR* fake_object) { - const auto fake_id = fake_object->GetBaseObject()->GetFormID(); - if (IsFakeContainer(fake_id)) { - const auto chestID = GetFakeContainerChestID(fake_id); - const auto real_bound = GetRealBound(chestID); - WorldObject::SwapObjects(fake_object, real_bound, false); - UpdateLoc_Private(chestID, fake_object->GetFormID()); - } else { - logger::warn("Fake object not found in ChestToFakeContainer."); - if (const auto baseform = DynamicFormTracker::GetSingleton()->GetOGFormOfDynamic( - fake_object->GetBaseObject()->GetFormID())) { - WorldObject::SwapObjects(fake_object, skyrim_cast(baseform), false); - } - } -} - -void Manager::UpdateLoc(const FormID fakeID, const RefID loc_id) { - const auto chestID = GetFakeContainerChestID(fakeID); - UpdateLoc_Private(chestID, loc_id); -} - void Manager::UpdateLoc_Private(const RefID chestID, const RefID loc_id) { if (!IsChest(chestID)) { logger::error("Chest ID not found in ChestToFakeContainer."); @@ -911,139 +801,284 @@ void Manager::TransferOnUse(const RefID a_chestID) const { } } -void Manager::OnLongPressEquip(const RE::TESBoundObject* a_fake, const int delay) { - auto chest = GetFakeContainerChest(a_fake); - if (!chest) { - logger::error("OnLongPressEquip: chest null"); - return; - } - queued_chests.insert(chest->GetFormID()); - if (delay > 0) { - clib_utilsQTR::Tasker::GetSingleton()->PushTask([this,chest] { - SKSE::GetTaskInterface()->AddUITask([this,chest] { OpenChestFromMenu(chest); }); - }, delay); - } else { - SKSE::GetTaskInterface()->AddUITask([this,chest] { OpenChestFromMenu(chest); }); +bool Manager::IsARegistry(const RefID registry) const { + SHARED_GUARD; + for (const auto& src : sources) { + for (const auto& cont_ref : src.data | std::views::values) { + if (cont_ref == registry) return true; + } } + return false; } -void Manager::UpdateFakeWV(RE::TESBoundObject* fake_form) { - const auto chestID = GetFakeContainerChestID(fake_form->GetFormID()); - const auto chestRef = RE::TESForm::LookupByID(chestID); - if (!chestRef) { - logger::error("Chest ref not found."); +void Manager::Gateway(const int result, const RE::ObjectRefHandle& a_current_container) { + if (result != 0 && result != 1) { + logger::error("Unexpected callback integer!"); return; } - const auto src = GetChestSource(chestID); - if (!src) { - logger::error("Source not found."); - return; + const auto a_chest_handle = a_current_container.get(); + const auto a_chest = a_chest_handle ? GetContainerChest(a_chest_handle.get()) : nullptr; + if (!a_chest) { + logger::warn("Current container is null."); + } else if (result) { + RenameCallback(GetFakeBound(a_chest->GetFormID())); + } else if (!ActivateChest(a_chest)) { + reals_to_takeback.clear(); + queued_chests.clear(); + Animations::SendAnimEvent(3, nullptr); + logger::warn("Chest not found."); } - UpdateFakeWV(fake_form, chestRef, src->weight_ratio); } -Count Manager::CanBeAdded(const RE::TESBoundObject* a_item, const Count a_count, RefID a_chestID) { - if (!a_item) return 0; - if (bypass_CanBeAdded.contains({a_chestID, a_item->GetFormID()})) { - return a_count; +void Manager::Init() { + player_ref = RE::PlayerCharacter::GetSingleton()->As(); + bool init_failed = false; + sources = Settings::LoadSources(); + if (sources.empty()) { + logger::error("No sources found."); + return InitFailed(); } - if (const auto item_id = a_item->GetFormID(); IsFakeContainer(item_id)) { - if (a_chestID == GetFakeContainerChestID(item_id)) { - logger::warn("Avoided transferring fake container into its own chest."); - return 0; + + std::unordered_set encounteredFormIDs; + + for (auto& src : sources) { + const auto form_ = FormReader::GetFormByID(src.formid, src.editorid); + if (const auto bound_ = src.GetBoundObject(); !form_ || !bound_) { + init_failed = true; + logger::error("Missing source: {:x}, {}", src.formid, src.editorid); + break; } - if (const auto other_chest = GetFakeContainerChest(a_item)) { - std::unordered_set visited; - for (const auto& a_child_chest : GetChildChests(other_chest->GetFormID(), &visited)) { - if (a_child_chest == a_chestID) { - return 0; - } - } + auto formtype_ = RE::FormTypeToString(form_->GetFormType()); + if (std::string formtypeString(formtype_); !Settings::AllowedFormTypes.contains(formtypeString)) { + init_failed = true; + MsgBoxesNotifs::InGame::FormTypeErr(form_->GetFormID()); + logger::error("Invalid source type: {}", formtype_); + break; + } + if (!encounteredFormIDs.insert(src.formid).second) { + logger::error("Duplicate formid found: {}", src.formid); + init_failed = true; } } - if (a_item->GetWeight() < 0.001f) return a_count; - const auto chestRef = RE::TESForm::LookupByID(a_chestID); - if (!chestRef) { - logger::error("Chest ref not found."); - return 0; + + const auto unownedChestOG = RE::TESForm::LookupByID(0x000EA29A); + unownedChest = RE::TESForm::LookupByID(UnownedStuff::unownedChestFormID); + unownedCell = RE::TESForm::LookupByID(0x000EA28B); + if (!unownedChestOG || unownedChestOG->GetBaseObject()->GetFormID() != unownedChest->GetFormID() || !unownedCell || + !unownedChest || !unownedChest->As()) { + logger::error("Missing unowned chest/cell"); + init_failed = true; } - SHARED_GUARD; - const auto itChest = ChestToFakeContainer.find(a_chestID); - if (itChest == ChestToFakeContainer.end()) return 0; - const auto src = GetContainerSource_NoLock(itChest->second.outerKey); - if (!src) { - logger::error("Source not found."); - return 0; + if (Settings::is_pre_0_7_1 && unownedChestOG) { + for (auto& [fst,snd] : unownedChestOG->GetInventory()) { + unownedChestOG->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, player_ref); + if (fst->IsDynamicForm()) + player_ref->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, + nullptr); + } } - if (src->weight_ratio < 0.00001f) return a_count; - if (src->capacity == 0.f) return a_count; - const auto remaining_capacity = src->capacity - chestRef->GetWeightInContainer() * src->weight_ratio; - const auto item_weight = a_item->GetWeight() * src->weight_ratio; - const auto can_be_added = static_cast(remaining_capacity / (item_weight + EPSILON)); - return std::max(0, std::min(can_be_added, a_count)); -} - -RE::TESBoundObject* Manager::FakeToRealContainer(const FormID fake) const { - return GetRealBound(GetFakeContainerChestID(fake)); + if (init_failed) return InitFailed(); + logger::info("Manager initialized."); } -void Manager::OnActivateContainer(RE::TESObjectREFR* a_container, const int msgbox_action, const int a_delay) { - if (!HandleRegistration(a_container)) return; - if (msgbox_action == 0) { - if (const auto chest = GetContainerChest(a_container)) queued_chests.insert(chest->GetFormID()); - } - auto a_handle = a_container->GetHandle(); - if (a_delay > 0) { - clib_utilsQTR::Tasker::GetSingleton()->PushTask([this,msgbox_action,a_handle] { - Gateway(msgbox_action, a_handle); - }, a_delay); - } else { - return Gateway(msgbox_action, a_handle); +void Manager::Reset() { + logger::info("Resetting manager..."); + { + UNIQUE_GUARD; + for (auto& src : sources) src.data.clear(); + ChestToFakeContainer.clear(); } + external_favs.clear(); + renames.clear(); + handled_external_conts.clear(); + Clear(); + isUninstalled.store(false); + logger::info("Manager reset."); } -void Manager::HandleFakePlacement(RE::TESObjectREFR* external_cont) { - if (std::ranges::find(handled_external_conts, external_cont->GetFormID()) != handled_external_conts.end()) return; - if (!external_cont->HasContainer()) return; - if (IsUnownedChest(external_cont->GetFormID())) return; - const auto external_cont_refid = external_cont->GetFormID(); - if (!IsARegistry(external_cont_refid)) return; - std::vector> chest_locs; +void Manager::SendData() { + logger::info("--------Sending data---------"); + Clear(); + int no_of_container = 0; + bool error = false; { SHARED_GUARD; - for (const auto& src : sources) { - for (const auto& [chest_ref, loc] : src.data) { - if (external_cont_refid == loc) { - chest_locs.push_back({chest_ref, loc}); + for (auto& src : sources) { + if (error) break; + for (const auto& [chest_ref, cont_ref] : src.data) { + no_of_container++; + if (!chest_ref) { + logger::error("Chest refid is null"); + error = true; + break; } + auto itChest = ChestToFakeContainer.find(chest_ref); + if (itChest == ChestToFakeContainer.end()) continue; + auto fake_formid = itChest->second.innerKey; + bool is_equipped_x = false; + bool is_favorited_x = false; + if (cont_ref == player_refid) { + const auto fake_bound = RE::TESForm::LookupByID(fake_formid); + is_equipped_x = Inventory::IsEquipped(fake_bound); + is_favorited_x = Inventory::IsFavorited(fake_bound, player_ref); + if (const auto chest = RE::TESForm::LookupByID(chest_ref); !chest) { + logger::error("Chest not found"); + error = true; + break; + } + } else if (std::ranges::find(external_favs, fake_formid) != external_favs.end()) { + is_favorited_x = true; + } + const auto rename_ = renames.contains(fake_formid) ? renames.at(fake_formid) : ""; + FormIDX fake_container_x(itChest->second.innerKey, is_equipped_x, is_favorited_x, rename_); + SetData({src.formid, chest_ref}, {fake_container_x, cont_ref}); } } } - for (auto& [chest_ref,loc] : chest_locs) { - FakePlacement(loc, chest_ref, external_cont); + + if (error) { + return RaiseMngrErr(); } - handled_external_conts.push_back(external_cont_refid); -} -bool Manager::IsFakeContainer(const FormID formid) const { - SHARED_GUARD; - return std::ranges::any_of(ChestToFakeContainer, [formid](const auto& pair) { - return pair.second.innerKey == formid; - }); + logger::info("Data sent. Number of containers: {}", no_of_container); } -bool Manager::IsRealContainer(const FormID formid) const { - SHARED_GUARD; - return std::ranges::any_of(sources, [formid](const Source& src) { return src.formid == formid; }); -} +void Manager::ReceiveData() { + logger::info("--------Receiving data---------"); + std::map> chest_equipped_fav; + std::map unmathced_chests; + for (const auto& [realcontForm_chestRef, fakecontForm_contRef] : m_Data) { + auto [realcontFormID, chestRefID] = realcontForm_chestRef; + auto [fakecontForm_info, locRefID] = fakecontForm_contRef; + if (Settings::is_pre_0_10_0 && locRefID == chestRefID) locRefID = player_refid; + if (Register_Sub(realcontFormID, fakecontForm_info.id, chestRefID, locRefID)) { + if (!fakecontForm_info.name.empty()) renames[fakecontForm_info.id] = fakecontForm_info.name; + if (locRefID == player_refid) + chest_equipped_fav[chestRefID] = { + fakecontForm_info.equipped, fakecontForm_info.favorited}; + else if (fakecontForm_info.favorited) external_favs.push_back(fakecontForm_info.id); + } else { + unmathced_chests[chestRefID] = {.outerKey = realcontFormID, .innerKey = fakecontForm_info.id}; + } + } + for (const auto& [chestRef_, RealFakeForm_] : unmathced_chests) { + auto [realcontFormID, fakecontFormID] = RealFakeForm_; + logger::warn("FormID {:x} not found in sources.", realcontFormID); + if (Settings::other_settings[Settings::otherstuffKeys[0]]) { + MsgBoxesNotifs::InGame::ProblemWithContainer(realcontFormID); + } + if (const auto fake_bound = RE::TESForm::LookupByID(fakecontFormID)) { + player_ref->RemoveItem(fake_bound, 1, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); + } + logger::info("Trying to retrieve items from chest"); + if (const auto chest = RE::TESForm::LookupByID(chestRef_)) { + for (auto& [fst,snd] : chest->GetInventory()) { + chest->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, player_ref); + if (fst->GetFormID() == fakecontFormID) + player_ref->RemoveItem( + fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); + } + if (!chest->GetInventory().empty()) { + logger::critical("Chest still has items in it. Degistering failed"); + MsgBoxesNotifs::InGame::CustomMsg("Items might not have been retrieved successfully."); + } + } + m_Data.erase({RealFakeForm_.outerKey, chestRef_}); + } + #ifndef NDEBUG + Print(); + #endif + auto* DFT = DynamicFormTracker::GetSingleton(); + std::vector handled_already; + std::vector all_chestIDs; + { + SHARED_GUARD; + for (const auto& chest_ref : ChestToFakeContainer | std::views::keys) all_chestIDs.push_back(chest_ref); + } + for (const auto& a_chestID : all_chestIDs) { + if (std::ranges::find(handled_already, a_chestID) != handled_already.end()) continue; + FakePlacementCeption(a_chestID, handled_already); + auto a_realID = GetRealID(a_chestID); + const auto real_editorid = FormReader::GetEditorID(a_realID); + if (real_editorid.empty()) { + logger::critical("Real container with formid {:x} has no editorid.", a_realID); + return RaiseMngrErr("Real container has no editorid."); + } + const auto a_fakeID = GetFakeID(a_chestID); + DFT->Reserve(a_realID, real_editorid, a_fakeID); + } + all_chestIDs.clear(); + handled_already.clear(); + const auto inventory_changes = player_ref->GetInventoryChanges(); + const auto entries = inventory_changes->entryList; + for (auto it = entries->begin(); it != entries->end(); ++it) { + if (const auto a_entry = *it; a_entry && a_entry->object) { + auto fake_formid = a_entry->object->GetFormID(); + if (IsFakeContainer(fake_formid)) { + const auto a_chestID = GetFakeContainerChestID(fake_formid); + if (chest_equipped_fav.contains(a_chestID)) { + const auto& [is_equipped_x,is_faved_x] = chest_equipped_fav.at(a_chestID); + if (a_entry->IsWorn()) { + RE::ActorEquipManager::GetSingleton()->UnequipObject( + RE::PlayerCharacter::GetSingleton(), a_entry->object, + a_entry->extraLists && !a_entry->extraLists->empty() + ? a_entry->extraLists->front() + : nullptr, 1, + nullptr, false, false, false, false); + } + if (is_equipped_x) { + Inventory::EquipItem(a_entry); + } + if (is_faved_x && !a_entry->IsFavorited()) { + Inventory::FavoriteItem(a_entry, inventory_changes); + } + } + } + } else logger::error("Entry or object null in ReceiveData fave-equip."); + } + + std::vector> pendingWV; + { + SHARED_GUARD; + for (auto& source : sources) { + for (auto dyn_formid : DFT->GetFormSet(source.formid, source.editorid)) { + const auto editorid = source.editorid.empty() + ? FormReader::GetEditorID(source.formid) + : source.editorid; + DFT->Reserve(source.formid, editorid, dyn_formid); + } + for (const auto& chest_refid : source.data | std::views::keys) { + if (const auto fake_formid = GetFakeID_NoLock(chest_refid)) { + pendingWV.emplace_back(fake_formid, chest_refid, source.weight_ratio); + } + } + } + } + + for (const auto& [fake_formid, chest_refid, weight_ratio] : pendingWV) { + const auto fake_bound = RE::TESForm::LookupByID(fake_formid); + if (const auto chest_ref = RE::TESForm::LookupByID(chest_refid); fake_bound && chest_ref) { + UpdateFakeWV(fake_bound, chest_ref, weight_ratio); + } else { + logger::error("ReceiveData: missing fake_bound or chest_ref for chest {:x}", chest_refid); + } + } -bool Manager::IsRealContainer(const RE::TESObjectREFR* ref) const { - if (!ref) return false; - if (ref->IsDisabled()) return false; - if (ref->IsDeleted()) return false; - const auto base = ref->GetBaseObject(); - if (!base) return false; - return IsRealContainer(base->GetFormID()); + logger::info("Deleting unused fake forms from bank."); + DFT->DeleteInactives(); + if (DFT->GetNDeleted() > 0) { + logger::warn("ReceiveData: Deleted forms exist. User is required to restart."); + MsgBoxesNotifs::InGame::CustomMsg( + "It seems the configuration has changed from your previous session" + " that requires you to restart the game." + "DO NOT IGNORE THIS:" + "1. Save your game." + "2. Exit the game." + "3. Restart the game." + "4. Load the saved game." + "JUST DO IT! NOW! BEFORE DOING ANYTHING ELSE!"); + } + logger::info("--------Receiving data done---------"); } void Manager::RenameContainer(const std::string& new_name, RE::TESBoundObject* a_fake) { @@ -1086,6 +1121,52 @@ void Manager::RenameContainer(const std::string& new_name, RE::TESBoundObject* a } } +void Manager::UpdateLoc(const FormID fakeID, const RefID loc_id) { + const auto chestID = GetFakeContainerChestID(fakeID); + UpdateLoc_Private(chestID, loc_id); +} + +Count Manager::CanBeAdded(const RE::TESBoundObject* a_item, const Count a_count, RefID a_chestID) { + if (!a_item) return 0; + if (bypass_CanBeAdded.contains({a_chestID, a_item->GetFormID()})) { + return a_count; + } + if (const auto item_id = a_item->GetFormID(); IsFakeContainer(item_id)) { + if (a_chestID == GetFakeContainerChestID(item_id)) { + logger::warn("Avoided transferring fake container into its own chest."); + return 0; + } + if (const auto other_chest = GetFakeContainerChest(a_item)) { + std::unordered_set visited; + for (const auto& a_child_chest : GetChildChests(other_chest->GetFormID(), &visited)) { + if (a_child_chest == a_chestID) { + return 0; + } + } + } + } + if (a_item->GetWeight() < 0.001f) return a_count; + const auto chestRef = RE::TESForm::LookupByID(a_chestID); + if (!chestRef) { + logger::error("Chest ref not found."); + return 0; + } + SHARED_GUARD; + const auto itChest = ChestToFakeContainer.find(a_chestID); + if (itChest == ChestToFakeContainer.end()) return 0; + const auto src = GetContainerSource_NoLock(itChest->second.outerKey); + if (!src) { + logger::error("Source not found."); + return 0; + } + if (src->weight_ratio < 0.00001f) return a_count; + if (src->capacity == 0.f) return a_count; + const auto remaining_capacity = src->capacity - chestRef->GetWeightInContainer() * src->weight_ratio; + const auto item_weight = a_item->GetWeight() * src->weight_ratio; + const auto can_be_added = static_cast(remaining_capacity / (item_weight + EPSILON)); + return std::max(0, std::min(can_be_added, a_count)); +} + void Manager::OnChestExit(RE::TESObjectREFR* a_chest) { const auto chest_id = a_chest->GetFormID(); const bool next_menu_is_chest = containermenu_owner && IsChest(containermenu_owner->GetFormID()); @@ -1140,75 +1221,54 @@ void Manager::OnChestEnter(RE::TESObjectREFR* a_chest) { } } -bool Manager::IsARegistry(const RefID registry) const { - SHARED_GUARD; - for (const auto& src : sources) { - for (const auto& cont_ref : src.data | std::views::values) { - if (cont_ref == registry) return true; +void Manager::HandleDrop(RE::TESObjectREFR* fake_object) { + const auto fake_id = fake_object->GetBaseObject()->GetFormID(); + if (IsFakeContainer(fake_id)) { + const auto chestID = GetFakeContainerChestID(fake_id); + const auto real_bound = GetRealBound(chestID); + WorldObject::SwapObjects(fake_object, real_bound, false); + UpdateLoc_Private(chestID, fake_object->GetFormID()); + } else { + logger::warn("Fake object not found in ChestToFakeContainer."); + if (const auto baseform = DynamicFormTracker::GetSingleton()->GetOGFormOfDynamic( + fake_object->GetBaseObject()->GetFormID())) { + WorldObject::SwapObjects(fake_object, skyrim_cast(baseform), false); } } - return false; } -void Manager::HandleCraftingEnter(const RefID a_furn) const { - if (const auto a_chestID = GetContainerChestID(a_furn); a_chestID > 0) { - const auto a_real_id = GetRealID(a_chestID); - if (const auto src = GetContainerSource(a_real_id); src && src->transfer_on_use) { - TransferOnUse(a_chestID); - } - } -} +void Manager::BeforePickup(RE::TESObjectREFR* picked_up_by, RE::TESObjectREFR* a_object) { + if (const auto chest = GetContainerChest(a_object)) { + const auto chest_refid = chest->GetFormID(); -void Manager::HandleCraftingExit() { - for (auto& [a_chestID,a_cache] : transfer_cache) { - if (!a_cache.empty()) { - if (const auto chest = RE::TESForm::LookupByID(a_chestID)) { - for (const auto& [item_formid, count] : a_cache) { - if (const auto item = RE::TESForm::LookupByID(item_formid)) { - player_ref->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, chest); - } + RE::TESBoundObject* fake_bound = nullptr; + float weight_ratio = 0.f; + { + UNIQUE_GUARD; + if (const auto src = GetContainerSource_NoLock(GetRealID_NoLock(chest_refid))) { + weight_ratio = src->weight_ratio; + if (fake_bound = GetFakeBound_NoLock(chest_refid); fake_bound && src->data.contains(chest_refid)) { + src->data.at(chest_refid) = picked_up_by->GetFormID(); + } else { + logger::critical("Fake bound not found."); } } } - } - transfer_cache.clear(); - bool error = false; - { - SHARED_GUARD; - for (const auto& src : sources) { - if (error) { - break; - } - for (const auto& [chest_refid, loc_refid] : src.data) { - if (loc_refid != player_refid) continue; - auto it = ChestToFakeContainer.find(chest_refid); - if (it == ChestToFakeContainer.end()) continue; - const auto fake_formid = it->second.innerKey; - const auto fake_bound = RE::TESForm::LookupByID(fake_formid); - if (!fake_bound) { - logger::error("Fake bound not found for chest_refid: {:x}", chest_refid); - error = true; - break; - } - const auto chest = RE::TESForm::LookupByID(chest_refid); - if (!chest) { - logger::error("Chest not found for chest_refid: {:x}", chest_refid); - error = true; - break; - } - if (Inventory::HasItem(fake_bound, player_ref)) { - if (!xData::UpdateExtrasInInventory(player_ref, fake_formid, chest, src.formid)) { - logger::error("Failed to update extras in player's inventory."); + if (fake_bound) { + WorldObject::SwapObjects(a_object, fake_bound, false); + UpdateFakeWV(fake_bound, chest, weight_ratio); + if (Settings::other_settings[Settings::otherstuffKeys[1]]) { + auto ref_handle = picked_up_by->GetHandle(); + const auto fake_id = fake_bound->GetFormID(); + SKSE::GetTaskInterface()->AddTask([this, fake_id, ref_handle]() { + if (const auto ref = ref_handle.get()) { + RemoveCarryWeightBoost(fake_id, ref.get()); } - } + }); } } } - - if (error) { - RaiseMngrErr("Error in HandleCraftingExit"); - } } void Manager::OnConsume(const FormID fake_formid, RE::TESObjectREFR* consumed_by) { @@ -1254,243 +1314,140 @@ void Manager::HandleSell(const FormID a_fake, RE::TESObjectREFR* sell_ref) { } } -void Manager::HandleFormDelete(const RefID refid) { - if (IsChest(refid)) return HandleFormDelete_(refid); - // Find chest_ref without holding the lock during callback - RefID targetChest = 0; - { - SHARED_GUARD; - for (auto& src : sources) { - for (const auto& [chest_ref, cont_ref] : src.data) { - if (cont_ref == refid) { - targetChest = chest_ref; - break; - } - } - if (targetChest) break; - } - } - if (targetChest) HandleFormDelete_(targetChest); -} - bool Manager::IsChest(const RefID a_refid) const { SHARED_GUARD; return IsChest_NoLock(a_refid); } -void Manager::Reset() { - logger::info("Resetting manager..."); - { - UNIQUE_GUARD; - for (auto& src : sources) src.data.clear(); - ChestToFakeContainer.clear(); - } - external_favs.clear(); - renames.clear(); - handled_external_conts.clear(); - Clear(); - isUninstalled.store(false); - logger::info("Manager reset."); +bool Manager::IsRealContainer(const FormID formid) const { + SHARED_GUARD; + return std::ranges::any_of(sources, [formid](const Source& src) { return src.formid == formid; }); } -void Manager::Print() { - for (const auto& src : sources) { - if (!src.data.empty()) { - logger::trace("Printing............Source formid: {:x}", src.formid); - Functions::printMap(src.data); - } - } - for (const auto& [chest_ref, cont_ref] : ChestToFakeContainer) { - logger::trace("Chest refid: {:x}, Real container formid: {:x}, Fake container formid: {:x}", chest_ref, - cont_ref.outerKey, cont_ref.innerKey); +bool Manager::IsFakeContainer(const FormID formid) const { + SHARED_GUARD; + return std::ranges::any_of(ChestToFakeContainer, [formid](const auto& pair) { + return pair.second.innerKey == formid; + }); +} + +RE::TESBoundObject* Manager::GetFakeBound(const RE::TESObjectREFR* a_loc) const { + if (const auto chest = GetContainerChest(a_loc)) { + return GetFakeBound(chest->GetFormID()); } + return nullptr; } -void Manager::SendData() { - logger::info("--------Sending data---------"); - Print(); - Clear(); - int no_of_container = 0; - bool error = false; - { - SHARED_GUARD; - for (auto& src : sources) { - if (error) break; - for (const auto& [chest_ref, cont_ref] : src.data) { - no_of_container++; - if (!chest_ref) { - logger::error("Chest refid is null"); - error = true; - break; - } - auto itChest = ChestToFakeContainer.find(chest_ref); - if (itChest == ChestToFakeContainer.end()) continue; - auto fake_formid = itChest->second.innerKey; - bool is_equipped_x = false; - bool is_favorited_x = false; - if (cont_ref == player_refid) { - const auto fake_bound = RE::TESForm::LookupByID(fake_formid); - is_equipped_x = Inventory::IsEquipped(fake_bound); - is_favorited_x = Inventory::IsFavorited(fake_bound, player_ref); - if (const auto chest = RE::TESForm::LookupByID(chest_ref); !chest) { - logger::error("Chest not found"); - error = true; - break; - } - } else if (std::ranges::find(external_favs, fake_formid) != external_favs.end()) { - is_favorited_x = true; +void Manager::HandleFakePlacement(RE::TESObjectREFR* external_cont) { + if (std::ranges::find(handled_external_conts, external_cont->GetFormID()) != handled_external_conts.end()) return; + if (!external_cont->HasContainer()) return; + if (IsUnownedChest(external_cont->GetFormID())) return; + const auto external_cont_refid = external_cont->GetFormID(); + if (!IsARegistry(external_cont_refid)) return; + std::vector> chest_locs; + { + SHARED_GUARD; + for (const auto& src : sources) { + for (const auto& [chest_ref, loc] : src.data) { + if (external_cont_refid == loc) { + chest_locs.push_back({chest_ref, loc}); } - const auto rename_ = renames.contains(fake_formid) ? renames.at(fake_formid) : ""; - FormIDX fake_container_x(itChest->second.innerKey, is_equipped_x, is_favorited_x, rename_); - SetData({src.formid, chest_ref}, {fake_container_x, cont_ref}); } } } - - if (error) { - return RaiseMngrErr(); + for (auto& [chest_ref,loc] : chest_locs) { + FakePlacement(loc, chest_ref, external_cont); } - - logger::info("Data sent. Number of containers: {}", no_of_container); + handled_external_conts.push_back(external_cont_refid); } -void Manager::ReceiveData() { - logger::info("--------Receiving data---------"); - std::map> chest_equipped_fav; - std::map unmathced_chests; - for (const auto& [realcontForm_chestRef, fakecontForm_contRef] : m_Data) { - auto [realcontFormID, chestRefID] = realcontForm_chestRef; - auto [fakecontForm_info, locRefID] = fakecontForm_contRef; - if (Settings::is_pre_0_10_0 && locRefID == chestRefID) locRefID = player_refid; - if (Register_Sub(realcontFormID, fakecontForm_info.id, chestRefID, locRefID)) { - if (!fakecontForm_info.name.empty()) renames[fakecontForm_info.id] = fakecontForm_info.name; - if (locRefID == player_refid) - chest_equipped_fav[chestRefID] = { - fakecontForm_info.equipped, fakecontForm_info.favorited}; - else if (fakecontForm_info.favorited) external_favs.push_back(fakecontForm_info.id); - } else { - unmathced_chests[chestRefID] = {.outerKey = realcontFormID, .innerKey = fakecontForm_info.id}; +void Manager::HandleCraftingEnter(const RefID a_furn) const { + if (const auto a_chestID = GetContainerChestID(a_furn); a_chestID > 0) { + const auto a_real_id = GetRealID(a_chestID); + if (const auto src = GetContainerSource(a_real_id); src && src->transfer_on_use) { + TransferOnUse(a_chestID); } } - for (const auto& [chestRef_, RealFakeForm_] : unmathced_chests) { - auto [realcontFormID, fakecontFormID] = RealFakeForm_; - logger::warn("FormID {:x} not found in sources.", realcontFormID); - if (Settings::other_settings[Settings::otherstuffKeys[0]]) { - MsgBoxesNotifs::InGame::ProblemWithContainer(realcontFormID); - } - if (const auto fake_bound = RE::TESForm::LookupByID(fakecontFormID)) { - player_ref->RemoveItem(fake_bound, 1, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); - } - logger::info("Trying to retrieve items from chest"); - if (const auto chest = RE::TESForm::LookupByID(chestRef_)) { - for (auto& [fst,snd] : chest->GetInventory()) { - chest->RemoveItem(fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, player_ref); - if (fst->GetFormID() == fakecontFormID) - player_ref->RemoveItem( - fst, snd.first, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); - } - if (!chest->GetInventory().empty()) { - logger::critical("Chest still has items in it. Degistering failed"); - MsgBoxesNotifs::InGame::CustomMsg("Items might not have been retrieved successfully."); +} + +void Manager::HandleCraftingExit() { + for (auto& [a_chestID,a_cache] : transfer_cache) { + if (!a_cache.empty()) { + if (const auto chest = RE::TESForm::LookupByID(a_chestID)) { + for (const auto& [item_formid, count] : a_cache) { + if (const auto item = RE::TESForm::LookupByID(item_formid)) { + player_ref->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, chest); + } + } } } - m_Data.erase({RealFakeForm_.outerKey, chestRef_}); } - #ifndef NDEBUG - Print(); - #endif - auto* DFT = DynamicFormTracker::GetSingleton(); - std::vector handled_already; - std::vector all_chestIDs; + transfer_cache.clear(); + + bool error = false; { SHARED_GUARD; - for (const auto& chest_ref : ChestToFakeContainer | std::views::keys) all_chestIDs.push_back(chest_ref); - } - for (const auto& a_chestID : all_chestIDs) { - if (std::ranges::find(handled_already, a_chestID) != handled_already.end()) continue; - FakePlacementCeption(a_chestID, handled_already); - auto a_realID = GetRealID(a_chestID); - const auto real_editorid = FormReader::GetEditorID(a_realID); - if (real_editorid.empty()) { - logger::critical("Real container with formid {:x} has no editorid.", a_realID); - return RaiseMngrErr("Real container has no editorid."); - } - const auto a_fakeID = GetFakeID(a_chestID); - DFT->Reserve(a_realID, real_editorid, a_fakeID); - } - all_chestIDs.clear(); - handled_already.clear(); - const auto inventory_changes = player_ref->GetInventoryChanges(); - const auto entries = inventory_changes->entryList; - for (auto it = entries->begin(); it != entries->end(); ++it) { - if (const auto a_entry = *it; a_entry && a_entry->object) { - auto fake_formid = a_entry->object->GetFormID(); - if (IsFakeContainer(fake_formid)) { - const auto a_chestID = GetFakeContainerChestID(fake_formid); - if (chest_equipped_fav.contains(a_chestID)) { - const auto& [is_equipped_x,is_faved_x] = chest_equipped_fav.at(a_chestID); - if (a_entry->IsWorn()) { - RE::ActorEquipManager::GetSingleton()->UnequipObject( - RE::PlayerCharacter::GetSingleton(), a_entry->object, - a_entry->extraLists && !a_entry->extraLists->empty() - ? a_entry->extraLists->front() - : nullptr, 1, - nullptr, false, false, false, false); - } - if (is_equipped_x) { - Inventory::EquipItem(a_entry); - } - if (is_faved_x && !a_entry->IsFavorited()) { - Inventory::FavoriteItem(a_entry, inventory_changes); + for (const auto& src : sources) { + if (error) { + break; + } + for (const auto& [chest_refid, loc_refid] : src.data) { + if (loc_refid != player_refid) continue; + auto it = ChestToFakeContainer.find(chest_refid); + if (it == ChestToFakeContainer.end()) continue; + const auto fake_formid = it->second.innerKey; + const auto fake_bound = RE::TESForm::LookupByID(fake_formid); + if (!fake_bound) { + logger::error("Fake bound not found for chest_refid: {:x}", chest_refid); + error = true; + break; + } + const auto chest = RE::TESForm::LookupByID(chest_refid); + if (!chest) { + logger::error("Chest not found for chest_refid: {:x}", chest_refid); + error = true; + break; + } + if (Inventory::HasItem(fake_bound, player_ref)) { + if (!xData::UpdateExtrasInInventory(player_ref, fake_formid, chest, src.formid)) { + logger::error("Failed to update extras in player's inventory."); } } } - } else logger::error("Entry or object null in ReceiveData fave-equip."); + } } - std::vector> pendingWV; + if (error) { + RaiseMngrErr("Error in HandleCraftingExit"); + } +} + +void Manager::HandleFormDelete(const RefID refid) { + if (IsChest(refid)) return HandleFormDelete_(refid); + // Find chest_ref without holding the lock during callback + RefID targetChest = 0; { SHARED_GUARD; - for (auto& source : sources) { - for (auto dyn_formid : DFT->GetFormSet(source.formid, source.editorid)) { - const auto editorid = source.editorid.empty() - ? FormReader::GetEditorID(source.formid) - : source.editorid; - DFT->Reserve(source.formid, editorid, dyn_formid); - } - for (const auto& chest_refid : source.data | std::views::keys) { - if (const auto fake_formid = GetFakeID_NoLock(chest_refid)) { - pendingWV.emplace_back(fake_formid, chest_refid, source.weight_ratio); + for (auto& src : sources) { + for (const auto& [chest_ref, cont_ref] : src.data) { + if (cont_ref == refid) { + targetChest = chest_ref; + break; } } + if (targetChest) break; } } + if (targetChest) HandleFormDelete_(targetChest); +} - for (const auto& [fake_formid, chest_refid, weight_ratio] : pendingWV) { - const auto fake_bound = RE::TESForm::LookupByID(fake_formid); - if (const auto chest_ref = RE::TESForm::LookupByID(chest_refid); fake_bound && chest_ref) { - UpdateFakeWV(fake_bound, chest_ref, weight_ratio); - } else { - logger::error("ReceiveData: missing fake_bound or chest_ref for chest {:x}", chest_refid); - } - } - - logger::info("Deleting unused fake forms from bank."); - DFT->DeleteInactives(); - if (DFT->GetNDeleted() > 0) { - logger::warn("ReceiveData: Deleted forms exist. User is required to restart."); - MsgBoxesNotifs::InGame::CustomMsg( - "It seems the configuration has changed from your previous session" - " that requires you to restart the game." - "DO NOT IGNORE THIS:" - "1. Save your game." - "2. Exit the game." - "3. Restart the game." - "4. Load the saved game." - "JUST DO IT! NOW! BEFORE DOING ANYTHING ELSE!"); - } - logger::info("--------Receiving data done---------"); - Print(); +bool Manager::IsRealContainer(const RE::TESObjectREFR* ref) const { + if (!ref) return false; + if (ref->IsDisabled()) return false; + if (ref->IsDeleted()) return false; + const auto base = ref->GetBaseObject(); + if (!base) return false; + return IsRealContainer(base->GetFormID()); } std::vector Manager::GetSources() const { @@ -1553,11 +1510,23 @@ void Manager::Uninstall() { isUninstalled.store(true); } -RE::TESBoundObject* Manager::GetFakeBound(const RE::TESObjectREFR* a_loc) const { - if (const auto chest = GetContainerChest(a_loc)) { - return GetFakeBound(chest->GetFormID()); +RE::TESBoundObject* Manager::FakeToRealContainer(const FormID fake) const { + return GetRealBound(GetFakeContainerChestID(fake)); +} + +void Manager::OnPromptAccept(RE::TESObjectREFR* a_container, const int msgbox_action, const int a_delay) { + if (!HandleRegistration(a_container)) return; + if (msgbox_action == 0) { + if (const auto chest = GetContainerChest(a_container)) queued_chests.insert(chest->GetFormID()); + } + auto a_handle = a_container->GetHandle(); + if (a_delay > 0) { + clib_utilsQTR::Tasker::GetSingleton()->PushTask([this,msgbox_action,a_handle] { + Gateway(msgbox_action, a_handle); + }, a_delay); + } else { + return Gateway(msgbox_action, a_handle); } - return nullptr; } std::string Manager::GetWeightText(RE::TESObjectREFR* a_container) { @@ -1600,14 +1569,6 @@ std::string Manager::GetValueText(RE::TESObjectREFR* a_loc) { return ""; } -void Manager::CloseMenu() { - containermenu_owner.reset(); - if (!Menu::GetContainerMenuOwner(containermenu_owner)) { - containermenu_owner.reset(); - } - closed_menu = Menu::CloseMenu(); -} - RE::TESBoundObject* Manager::RegisterFromMenu(RE::InventoryEntryData* a_real_entry, RE::TESObjectREFR* a_owner) { a_owner = a_owner ? a_owner : player_ref; if (!a_real_entry) return nullptr; @@ -1674,4 +1635,28 @@ void Manager::RenameCallback(RE::TESBoundObject* a_fake) { } else { logger::error("Failed to call UIExtensions functions."); } +} + +void Manager::OnOpen(const RE::TESBoundObject* a_fake, const int delay) { + auto chest = GetFakeContainerChest(a_fake); + if (!chest) { + logger::error("OnOpen: chest null"); + return; + } + queued_chests.insert(chest->GetFormID()); + if (delay > 0) { + clib_utilsQTR::Tasker::GetSingleton()->PushTask([this,chest] { + SKSE::GetTaskInterface()->AddUITask([this,chest] { OpenChestFromMenu(chest); }); + }, delay); + } else { + SKSE::GetTaskInterface()->AddUITask([this,chest] { OpenChestFromMenu(chest); }); + } +} + +void Manager::CloseMenu() { + containermenu_owner.reset(); + if (!Menu::GetContainerMenuOwner(containermenu_owner)) { + containermenu_owner.reset(); + } + closed_menu = Menu::CloseMenu(); } \ No newline at end of file diff --git a/src/SkyPrompt.cpp b/src/SkyPrompt.cpp index 2a889d6..f6049dc 100644 --- a/src/SkyPrompt.cpp +++ b/src/SkyPrompt.cpp @@ -41,9 +41,9 @@ void RefPromptSink::ProcessEvent(const SkyPromptAPI::PromptEvent event) const { if (const auto a_ref = crosshairref->get().get()) { if (const auto prompt_eventid = event.prompt.eventID; prompt_eventid == 0) { const auto duration = Animations::SetUpAnimationOnOpen(a_ref, true); - Manager::GetSingleton()->OnActivateContainer(a_ref, 0, duration); + Manager::GetSingleton()->OnPromptAccept(a_ref, 0, duration); } else if (prompt_eventid == 1) { - Manager::GetSingleton()->OnActivateContainer(a_ref, 1); + Manager::GetSingleton()->OnPromptAccept(a_ref, 1); } } } @@ -126,7 +126,7 @@ void MenuPromptSink::OnOpen(RE::TESBoundObject* a_fake, const bool is_worn) { manager->CloseMenu(); - Manager::GetSingleton()->OnLongPressEquip(a_fake, Animations::SetUpAnimationOnOpen(a_real, is_worn)); + Manager::GetSingleton()->OnOpen(a_fake, Animations::SetUpAnimationOnOpen(a_real, is_worn)); } void RegistrationPromptSink::ProcessEvent(const SkyPromptAPI::PromptEvent event) const { @@ -145,12 +145,11 @@ void RegistrationPromptSink::ProcessEvent(const SkyPromptAPI::PromptEvent event) if (is_in_inventory_menu || !RE::LookupReferenceByHandle(owner_handle, a_owner)) { a_owner.reset(); } - if (const auto a_fake = Manager::GetSingleton()->RegisterFromMenu(a_entry, a_owner.get()); - a_fake && Manager::GetSingleton()->IsFakeContainer(a_fake->GetFormID())) { + if (const auto a_fake = Manager::GetSingleton()->RegisterFromMenu(a_entry, a_owner.get())) { + if (is_worn) { RE::ActorEquipManager::GetSingleton()->EquipObject(RE::PlayerCharacter::GetSingleton(), a_fake); } - if (const auto ui = RE::UI::GetSingleton(); ui->IsMenuOpen(RE::InventoryMenu::MENU_NAME) || ui->IsMenuOpen(RE::ContainerMenu::MENU_NAME)) { RE::SendUIMessage::SendInventoryUpdateMessage(RE::PlayerCharacter::GetSingleton(), nullptr); @@ -158,7 +157,6 @@ void RegistrationPromptSink::ProcessEvent(const SkyPromptAPI::PromptEvent event) RE::SendUIMessage::SendInventoryUpdateMessage(a_owner.get(), nullptr); } } - if (event.prompt.eventID == 4) { Manager::RenameCallback(a_fake); return; From 6895e0ba695ee0a5b611f4016157e9696c0bb2fa Mon Sep 17 00:00:00 2001 From: Ege Yilmaz <47591838+Quantumyilmaz@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:01:22 +0100 Subject: [PATCH 2/4] Update Manager.h --- include/Manager.h | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/include/Manager.h b/include/Manager.h index 2703fd3..9e89664 100644 --- a/include/Manager.h +++ b/include/Manager.h @@ -10,10 +10,6 @@ class Manager final : public SaveLoadData, RE::TESObjectREFR* player_ref = nullptr; //RE::EffectSetting* empty_mgeff = nullptr; - // runtime specific - std::map ChestToFakeContainer; - // chest refid -> {real container formid (outerKey), fake container formid (innerKey)} - // unowned stuff RE::TESObjectCELL* unownedCell = nullptr; RE::TESObjectCONT* unownedChest = nullptr; @@ -90,8 +86,6 @@ class Manager final : public SaveLoadData, void HandleFormDelete_(RefID chest_refid); - std::vector sources; - void RaiseMngrErr(const std::string& err_msg_ = "Error"); void InitFailed(); @@ -121,7 +115,6 @@ class Manager final : public SaveLoadData, [[nodiscard]] bool DeRegister(RE::TESObjectREFR* chest, RE::TESObjectREFR* transfer_dest); std::string GetWeightText_(RE::TESObjectREFR* a_chest); - static std::string GetWeightText(float weight, float capacity); // [locks source_mutex_ and chest2fake_mutex_ (unique)] @@ -150,6 +143,10 @@ class Manager final : public SaveLoadData, [[nodiscard]] bool IsARegistry(RefID registry) const; void Gateway(int result, const RE::ObjectRefHandle& a_current_container); + // chest refid -> {real container formid (outerKey), fake container formid (innerKey)} + std::map ChestToFakeContainer; + std::vector sources; + public: const char* GetType() override { return "Manager"; } @@ -293,12 +290,9 @@ void Manager::UpdateFakeWV(T* fake_form, RE::TESObjectREFR* chest_linked, const while (static_cast(std::abs(f_search - target_value)) > tolerance_val && curr_iter > 0) { FunctionsSkyrim::FormTraits::SetValue(fake_form, x_search); - logger::trace("Setting fake value to: {}", x_search); f_search = fake_bound->GetGoldValue() + extracost; //player_has_item ? Inventory::GetItemValue(fake_bound, player_ref->GetInventory()) : container_location->GetGoldValue(); - logger::trace("x_search: {}, f_search: {}", x_search, f_search); - if (f_search > target_value) upper_bound = x_search; else lower_bound = x_search; From ec60d0283e3fbfe161846b073177997b43b1056a Mon Sep 17 00:00:00 2001 From: Ege Yilmaz <47591838+Quantumyilmaz@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:50:42 +0100 Subject: [PATCH 3/4] Refactor rename lookup logic in Manager Introduced GetRename() helper to centralize and simplify rename lookups for fake IDs. Updated usages in Manager.cpp and Manager.h to use the new method, improving code clarity and reducing duplication. --- include/Manager.h | 7 ++++++- src/Manager.cpp | 15 ++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/Manager.h b/include/Manager.h index 9e89664..332a672 100644 --- a/include/Manager.h +++ b/include/Manager.h @@ -140,6 +140,8 @@ class Manager final : public SaveLoadData, void TransferOnUse(RefID a_chestID) const; + std::string GetRename(const FormID a_fakeID) const { return renames.contains(a_fakeID) ? renames.at(a_fakeID) : ""; } + [[nodiscard]] bool IsARegistry(RefID registry) const; void Gateway(int result, const RE::ObjectRefHandle& a_current_container); @@ -247,7 +249,10 @@ void Manager::UpdateFakeWV(T* fake_form, RE::TESObjectREFR* chest_linked, const auto real_container = FakeToRealContainer(fake_formid); // ReSharper disable once CppDependentTemplateWithoutTemplateKeyword fake_form->Copy(real_container->As()); // NOLINT(clang-diagnostic-warning) - if (renames.contains(fake_formid)) fake_form->fullName = renames.at(fake_form->GetFormID()); + + if (auto a_rename = GetRename(fake_formid); !a_rename.empty()) { + fake_form->fullName = a_rename; + } FunctionsSkyrim::FormTraits::SetWeight( fake_form, diff --git a/src/Manager.cpp b/src/Manager.cpp index d7ba4a3..b849b93 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -105,7 +105,8 @@ std::string Manager::GetChestName(const RE::TESObjectREFR* chest) const { auto chest_id = chest->GetFormID(); const auto fake_id = GetFakeID(chest_id); if (const auto real_bound = FakeToRealContainer(fake_id)) { - return renames.contains(fake_id) ? renames.at(fake_id) : real_bound->GetName(); + auto a_rename = GetRename(fake_id); + return !a_rename.empty() ? a_rename : real_bound->GetName(); } logger::error("Fake to real container failed for chest ID: {:x}", chest_id); return ""; @@ -230,8 +231,8 @@ RE::TESObjectREFR* Manager::FindNotMatchedChest() const { RE::BSSpinLockGuard locker(runtimeData.spinLock); for (const auto& ref : runtimeData.references) { if (!ref) continue; - if (ref->GetFormID() == UnownedStuff::unownedChestOGRefID) continue; if (ref->GetBaseObject()->GetFormID() != unownedChest->GetFormID()) continue; + if (ref->GetFormID() == UnownedStuff::unownedChestOGRefID) continue; if (!IsChest(ref->GetFormID()) && ref->GetInventory().empty()) { return ref.get(); } @@ -456,9 +457,9 @@ RE::TESBoundObject* Manager::FakePlacement_Sub_Sub(const RefID chestID) { external_favs.erase(itFav); external_favs.push_back(fakeid_new); } - if (renames.contains(fakeid_old) && fakeid_new != fakeid_old) { - renames[fakeid_new] = renames.at(fakeid_old); - renames.erase(fakeid_old); + if (const auto it = renames.find(fakeid_old); it != renames.end() && fakeid_new != fakeid_old) { + renames[fakeid_new] = it->second; + renames.erase(it); } const auto DFT = DynamicFormTracker::GetSingleton(); @@ -929,8 +930,8 @@ void Manager::SendData() { } else if (std::ranges::find(external_favs, fake_formid) != external_favs.end()) { is_favorited_x = true; } - const auto rename_ = renames.contains(fake_formid) ? renames.at(fake_formid) : ""; - FormIDX fake_container_x(itChest->second.innerKey, is_equipped_x, is_favorited_x, rename_); + const auto a_rename = GetRename(fake_formid); + FormIDX fake_container_x(itChest->second.innerKey, is_equipped_x, is_favorited_x, a_rename); SetData({src.formid, chest_ref}, {fake_container_x, cont_ref}); } } From d20888216561ed9e0416fac8b9228f0c43aa5282 Mon Sep 17 00:00:00 2001 From: Ege Yilmaz <47591838+Quantumyilmaz@users.noreply.github.com> Date: Thu, 18 Dec 2025 19:42:06 +0100 Subject: [PATCH 4/4] Remove excessive trace logging from Serialization.cpp Eliminated redundant logger::trace statements to reduce log verbosity and improve performance. Also added ChestManager::Reset() to LoadCallback for proper state reset during data loading. --- src/Serialization.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/Serialization.cpp b/src/Serialization.cpp index 4a3bae8..2a9dcd8 100644 --- a/src/Serialization.cpp +++ b/src/Serialization.cpp @@ -1,4 +1,6 @@ #include "Serialization.h" + +#include "Chest.h" #include "Events.h" #include "Manager.h" @@ -53,7 +55,6 @@ bool SaveLoadData::Load(SKSE::SerializationInterface* serializationInterface, std::size_t recordDataSize; serializationInterface->ReadRecordData(recordDataSize); - logger::trace("Loading data from serialization interface with size: {}", recordDataSize); Locker locker(m_Lock); m_Data.clear(); @@ -61,7 +62,6 @@ bool SaveLoadData::Load(SKSE::SerializationInterface* serializationInterface, for (size_t i = 0; i < recordDataSize; i++) { SaveDataLHS formId; SaveDataRHS value; - logger::trace("Loading data from serialization interface."); logger::trace("FormID: ({},{}) serializationInterface->ReadRecordData:{}", formId.outerKey, formId.innerKey, serializationInterface->ReadRecordData(formId)); @@ -76,7 +76,6 @@ bool SaveLoadData::Load(SKSE::SerializationInterface* serializationInterface, return false; } SaveDataRHS2 saveDataRHS; - logger::trace("Reading value..."); if (!serializationInterface->ReadRecordData(saveDataRHS)) { logger::error("Failed to load value data for FormRefID: ({},{})", formId.outerKey, formId.innerKey); return false; @@ -110,14 +109,12 @@ bool DFSaveLoadData::Save(SKSE::SerializationInterface* serializationInterface) for (const auto& [lhs, rhs] : m_Data) { // we serialize formid, editorid, and refid separately std::uint32_t formid = lhs.first; - logger::trace("Formid:{}", formid); if (!serializationInterface->WriteRecordData(formid)) { logger::error("Failed to save formid"); return false; } const std::string editorid = lhs.second; - logger::trace("Editorid:{}", editorid); write_string(serializationInterface, editorid); // save the number of rhs records @@ -128,7 +125,6 @@ bool DFSaveLoadData::Save(SKSE::SerializationInterface* serializationInterface) } for (const auto& rhs_ : rhs) { - logger::trace("size of rhs_: {}", sizeof(rhs_)); if (!serializationInterface->WriteRecordData(rhs_)) { logger::error("Failed to save data"); return false; @@ -175,23 +171,14 @@ bool DFSaveLoadData::Load(SKSE::SerializationInterface* serializationInterface, return false; } - logger::trace("Formid:{:x}", formid); - logger::trace("Editorid:{}", editorid); - DFSaveDataLHS lhs({formid, editorid}); - logger::trace("Reading value..."); std::size_t rhsSize = 0; logger::trace("ReadRecordData: {}", serializationInterface->ReadRecordData(rhsSize)); - logger::trace("rhsSize: {}", rhsSize); for (size_t j = 0; j < rhsSize; j++) { DFSaveData rhs_; logger::trace("ReadRecordData: {}", serializationInterface->ReadRecordData(rhs_)); - logger::trace( - "rhs_ content: dyn_formid: {:x}, customid_bool: {}," - "customid: {}, acteff_elapsed: {}", - rhs_.dyn_formid, rhs_.custom_id.first, rhs_.custom_id.second, rhs_.acteff_elapsed); rhs.push_back(rhs_); } @@ -225,6 +212,7 @@ void Serialization::LoadCallback(SKSE::SerializationInterface* serializationInte logger::info("Loading Data from skse co-save."); + ChestManager::GetSingleton()->Reset(); EventSink::GetSingleton()->Reset(); Manager::GetSingleton()->Reset(); auto* DFT = DynamicFormTracker::GetSingleton(); @@ -302,7 +290,6 @@ void Serialization::InitializeSerialization() { serialization->SetUniqueID(Settings::kDataKey); serialization->SetSaveCallback(SaveCallback); serialization->SetLoadCallback(LoadCallback); - SKSE::log::trace("Cosave serialization initialized."); } #undef DISABLE_IF_UNINSTALLED \ No newline at end of file