diff --git a/include/Manager.h b/include/Manager.h index 43219fa..332a672 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)] @@ -147,73 +140,70 @@ class Manager final : public SaveLoadData, void TransferOnUse(RefID a_chestID) const; -public: - std::atomic isUninstalled = false; - - const char* GetType() override { return "Manager"; } - void Init(); + 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); - 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); + // chest refid -> {real container formid (outerKey), fake container formid (innerKey)} + std::map ChestToFakeContainer; + std::vector sources; - [[nodiscard]] bool IsFakeContainer(FormID formid) const; +public: - // 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; + const char* GetType() override { return "Manager"; } + 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); + Count CanBeAdded(const RE::TESBoundObject* a_item, Count a_count, RefID a_chestID); 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"); @@ -259,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, @@ -302,12 +295,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; 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..b849b93 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"); @@ -156,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 ""; @@ -281,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(); } @@ -320,6 +270,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 +361,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)) { @@ -441,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(); @@ -483,39 +499,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 +548,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,89 +726,29 @@ 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(); - } +FormID Manager::GetFakeID(const RefID chest_id) const { + SHARED_GUARD; + return GetFakeID_NoLock(chest_id); +} - std::unordered_set encounteredFormIDs; +FormID Manager::GetRealID(const RefID chest_id) const { + SHARED_GUARD; + return GetRealID_NoLock(chest_id); +} - 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; +RefID Manager::GetContainerChestID(const RefID a_loc_refid) const { + SHARED_GUARD; + for (const auto& src : sources) { + for (const auto& [chest_refid, cont_refid] : src.data) { + if (cont_refid == a_loc_refid) return chest_refid; } } + 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; - } - 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."); -} - -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."); - } -} - -RefID Manager::GetContainerChestID(const RefID a_loc_refid) const { - SHARED_GUARD; - for (const auto& src : sources) { - for (const auto& [chest_refid, cont_refid] : src.data) { - if (cont_refid == a_loc_refid) return chest_refid; - } - } - return 0; -} - -RefID Manager::GetFakeContainerChestID(const FormID fake_id) const { - SHARED_GUARD; - return GetFakeContainerChestID_NoLock(fake_id); +RefID Manager::GetFakeContainerChestID(const FormID fake_id) const { + SHARED_GUARD; + return GetFakeContainerChestID_NoLock(fake_id); } RE::TESBoundObject* Manager::GetFakeBound(const RefID chest_id) const { @@ -804,71 +760,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 +802,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 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}); } } } - 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 +1122,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 +1222,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 +1315,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 +1511,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 +1570,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 +1636,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/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 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;