diff --git a/Artifact Tracker.esp b/Artifact Tracker.esp index 9284361..459740f 100644 Binary files a/Artifact Tracker.esp and b/Artifact Tracker.esp differ diff --git a/SKSE/Plugins/ArtifactTracker.dll b/SKSE/Plugins/ArtifactTracker.dll new file mode 100644 index 0000000..7c34b9c Binary files /dev/null and b/SKSE/Plugins/ArtifactTracker.dll differ diff --git a/SKSE/Plugins/ArtifactTrackerFunctions.dll b/SKSE/Plugins/ArtifactTrackerFunctions.dll deleted file mode 100644 index ac40201..0000000 Binary files a/SKSE/Plugins/ArtifactTrackerFunctions.dll and /dev/null differ diff --git a/Scripts/ArtifactTrackerPlayer.pex b/Scripts/ArtifactTrackerPlayer.pex new file mode 100644 index 0000000..82be0f1 Binary files /dev/null and b/Scripts/ArtifactTrackerPlayer.pex differ diff --git a/Scripts/ETR_Functions.pex b/Scripts/ETR_Functions.pex deleted file mode 100644 index b4f5e98..0000000 Binary files a/Scripts/ETR_Functions.pex and /dev/null differ diff --git a/Scripts/ETR_TrackStoredItems.pex b/Scripts/ETR_TrackStoredItems.pex deleted file mode 100644 index 5f9775e..0000000 Binary files a/Scripts/ETR_TrackStoredItems.pex and /dev/null differ diff --git a/Scripts/PlayerBookShelfContainerScript.pex b/Scripts/PlayerBookShelfContainerScript.pex new file mode 100644 index 0000000..592e870 Binary files /dev/null and b/Scripts/PlayerBookShelfContainerScript.pex differ diff --git a/Source/ArtifactTrackerDLL/CMakeLists.txt b/Source/ArtifactTrackerDLL/CMakeLists.txt index 3d87346..38be4c8 100644 --- a/Source/ArtifactTrackerDLL/CMakeLists.txt +++ b/Source/ArtifactTrackerDLL/CMakeLists.txt @@ -5,7 +5,7 @@ message("Using toolchain file ${CMAKE_TOOLCHAIN_FILE}.") ## Define project ######################################################################################################################## project( - ArtifactTrackerFunctions + ArtifactTracker VERSION 1.0.0 DESCRIPTION "Eddoursul's Artifact Tracker" LANGUAGES CXX) @@ -51,7 +51,7 @@ target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME} PUBLIC - $) + $) target_precompile_headers(${PROJECT_NAME} PRIVATE diff --git a/Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp b/Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp index 985007d..5231a19 100644 --- a/Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp +++ b/Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp @@ -1,321 +1,506 @@ -#include "ArtifactTracker.h" -#include "BookCheck.h" -#include "EventListener.h" -#include "Util.h" - -namespace ArtifactTracker -{ - RE::TESGlobal* notifyNewArtifact; - RE::TESBoundObject* cellContainer; - RE::BGSListForm* listNew; - RE::BGSListForm* listStored; - RE::BGSListForm* listFound; - RE::BGSListForm* persistentStorage; - RE::BGSKeyword* homeKeyword; - bool bAtHome; - std::unordered_map validMisc; - std::unordered_map validBooks; - - void Init() - { - EventListener::Install(); - - const auto dataHandler = RE::TESDataHandler::GetSingleton(); - - notifyNewArtifact = dataHandler->LookupForm(0x809, "Artifact Tracker.esp"); - cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As(); - - listNew = dataHandler->LookupForm(0x803, "Artifact Tracker.esp"); - listStored = dataHandler->LookupForm(0x805, "Artifact Tracker.esp"); - listFound = dataHandler->LookupForm(0x806, "Artifact Tracker.esp"); - persistentStorage = dataHandler->LookupForm(0x807, "Artifact Tracker.esp"); - - homeKeyword = dataHandler->LookupForm(0xFC1A3, "Skyrim.esm"); - - if (!cellContainer) { - SKSE::log::warn("cellContainer is empty"); - } - - if (!persistentStorage) { - SKSE::log::warn("persistentStorage is empty"); - } - - // Preloading item lists - const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe - - for (const auto& form : dataHandler->GetFormArray()) { - if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { - validBooks[form->formID] = form; - } - } - - RE::BGSListForm* excludeKeywords = dataHandler->LookupForm(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords - - for (const auto& form : dataHandler->GetFormArray()) { - if (form->GetPlayable() && !form->IsGold() && !form->IsLockpick() && !form->HasKeywordInList(excludeKeywords, false)) { - validMisc[form->formID] = form; - } - } - } - - bool IsArtifact(RE::TESForm* a_form) - { - switch (a_form->GetFormType()) { - case RE::FormType::Armor: - return a_form->GetPlayable(); - case RE::FormType::Weapon: - return a_form->GetPlayable() && a_form->formID != 0x000001F4; - case RE::FormType::Book: - return validBooks.contains(a_form->formID); - case RE::FormType::Misc: - return validMisc.contains(a_form->formID); - default: - return false; - } - } - - bool SetHomeLocation(RE::BGSLocation* a_location = NULL) - { - bAtHome = a_location && a_location->HasKeyword(homeKeyword); - return bAtHome; - } - - RE::TESObjectREFR* GetCellStorage( - RE::TESObjectREFR* a_ref, - RE::BGSListForm* a_refList, - RE::TESBoundObject* a_objectToCreate, - bool a_autoCreate) - { - RE::TESObjectREFR* result = NULL; - - if (!a_ref || !a_refList || !a_objectToCreate) { - SKSE::log::warn("Invalid arguments in GetCellStorage"); - return result; - } - - RE::TESObjectCELL* cell = a_ref->GetParentCell(); - - a_refList->ForEachForm([&result, &cell, &a_objectToCreate](RE::TESForm& a_exform) { - const auto ref = a_exform.As(); - - if (ref && ref->GetParentCell() == cell && ref->GetBaseObject()->formID == a_objectToCreate->formID) { - result = ref; - return false; - } - - return true; - }); - - if (!result && a_autoCreate) { - result = a_ref->PlaceObjectAtMe(a_objectToCreate, true).get(); - result->Disable(); - a_refList->AddForm(result); - } - - if (!result) { - SKSE::log::warn("Failed to find or create cell storage in GetCellStorage"); - } - - return result; - } - - void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers) - { - if (!a_cellStorage || !a_excludeContainers) { - SKSE::log::warn("Invalid arguments in SyncCellStorage"); - return; - } - - std::unordered_map cellItems; - - const auto cell = a_cellStorage->GetParentCell(); - const auto inv = a_cellStorage->GetInventory(); - - for (const RE::NiPointer a_ref : cell->references) { - const auto baseObj = a_ref->GetBaseObject(); - - if (baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As()->GetRace()->formID == 0x0010760A)) { - if (a_excludeContainers->HasForm(a_ref->formID) || baseObj->formID == 0xDC9E7) { // skip persistent and PlayerBookShelfContainer - continue; - } - - const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool { - return !cellItems.contains(a_object.formID); - }); - - for (const auto& [item, data] : contInv) { - if (data.first > 0) { - cellItems[item->formID] = true; - if (inv.find(item) == inv.end()) { - if (IsArtifact(item)) { - a_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr); - } - } - } - } - - continue; - } - - if (a_ref->IsMarkedForDeletion()) { - SKSE::log::info("found marked for deletion"); - } - - if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) { - continue; - } - - if (cellItems.contains(baseObj->formID)) { - continue; - } - - cellItems[baseObj->formID] = true; - - if (!IsArtifact(baseObj)) { - continue; - } - - if (inv.find(baseObj) == inv.end()) { - a_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr); - } - } - - for (const auto& [item, data] : inv) { - const auto& [count, entry] = data; - if (count > 0 && !cellItems.contains(item->formID)) { - a_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); - } - } - - cellItems.clear(); - } - - void OnItemPickup(RE::TESBoundObject* form) - { - RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); - - SyncCellStorage(cellStorage, persistentStorage); - - if (!RefHasItem(cellStorage, form) && !GetItemCountInList(persistentStorage, form)) { - RemoveListItem(listStored, form); - listFound->AddForm(form); - } - } - - void OnContainerChanged(const RE::TESContainerChangedEvent* a_event) - { - if (a_event->newContainer == 0x14) { - return; - RE::TESBoundObject* form = RE::TESForm::LookupByID(a_event->baseObj); - - if (!form || !IsArtifact(form)) { - return; - } - - if (listFound->HasForm(form) || listStored->HasForm(form)) { - return; - } - - if (listNew->HasForm(form)) { - - RemoveListItem(listNew, form); - - listFound->AddForm(form); - - if (notifyNewArtifact->value) { - const auto itemName = form->GetName(); - char* notificationText = new char[strlen("New artifact acquired: ") + strlen(itemName) + 1]; - strcpy(notificationText, "New artifact acquired: "); - strcat(notificationText, itemName); - RE::DebugNotification(notificationText); - delete[] notificationText; - } - - } - - } else if (a_event->oldContainer == 0x14) { - - RE::TESBoundObject* form = RE::TESForm::LookupByID(a_event->baseObj); - - SKSE::log::info("{}", form->GetName()); - if (!form || !IsArtifact(form)) { - return; - } - - if (!a_event->newContainer) { - if (bAtHome && a_event->reference) { // dropped at home - RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); - if (!RefHasItem(cellStorage, form)) { - cellStorage->AddObjectToContainer(form, nullptr, 1, nullptr); - } - if (listFound->HasForm(form)) { - RemoveListItem(listFound, form); - } - listStored->AddForm(form); - RE::DebugNotification("added to stored"); - return; - } - - if (listStored->HasForm(form)) { - return; - } - - if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form)) { - if (listFound->HasForm(form)) { - RemoveListItem(listFound, form); - } - listNew->AddForm(form); - RE::DebugNotification("added to new"); - } - - return; - } - - bool bPersistent = false; - for (const auto& ref : persistentStorage->forms) { - if (ref && ref->formID == a_event->newContainer) { - bPersistent = true; - break; - } - } - - if (!bPersistent && !bAtHome) { - if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form->As())) { - if (listFound->HasForm(form)) { - RemoveListItem(listFound, form); - } - listNew->AddForm(form); - RE::DebugNotification("added to new"); - } - return; - } - - if (listStored->HasForm(form)) { - return; - } - - if (!bPersistent && bAtHome) { - RE::TESObjectREFR* targetContainer = RE::TESForm::LookupByID(a_event->newContainer); - - if (!targetContainer || targetContainer->GetParentCell() != RE::PlayerCharacter::GetSingleton()->GetParentCell()) { - return; - } - - if (targetContainer->GetBaseObject()->Is(RE::FormType::NPC) && targetContainer->GetBaseObject()->As()->GetRace()->formID != 0x0010760A) { - return; - } - - RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); - cellStorage->AddObjectToContainer(form->As(), nullptr, 1, nullptr); - RE::DebugNotification("updated cellStorage"); - } - - if (listFound->HasForm(form)) { - RemoveListItem(listFound, form); - } - - listStored->AddForm(form); - RE::DebugNotification("added to stored"); - } - } -} +#include "ArtifactTracker.h" +#include "BookCheck.h" +#include "EventListener.h" +#include "Util.h" + +namespace ArtifactTracker +{ + bool g_bLoaded; + bool g_bHomeContainer; + RE::TESBoundObject* g_cellContainer; + RE::BGSListForm* g_listNew; + RE::BGSListForm* g_listStored; + RE::BGSListForm* g_listFound; + RE::BGSListForm* g_persistentStorage; + RE::BGSKeyword* g_homeKeyword; + std::unordered_map g_artifactMap; + std::unordered_map g_persistentMap; + RE::TESObjectREFR* g_cellStorage; + + void Init() + { + g_bLoaded = false; + + const auto dataHandler = RE::TESDataHandler::GetSingleton(); + + if (!dataHandler) { + SKSE::log::warn("Failed to call RE::TESDataHandler::GetSingleton()"); + return; + } + + g_cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As(); + + g_listNew = dataHandler->LookupForm(0x803, "Artifact Tracker.esp"); + g_listStored = dataHandler->LookupForm(0x805, "Artifact Tracker.esp"); + g_listFound = dataHandler->LookupForm(0x806, "Artifact Tracker.esp"); + g_persistentStorage = dataHandler->LookupForm(0x807, "Artifact Tracker.esp"); + + g_homeKeyword = dataHandler->LookupForm(0xFC1A3, "Skyrim.esm"); + + const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe + RE::BGSListForm* excludeKeywords = dataHandler->LookupForm(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords + + if (!g_cellContainer || !g_listNew || !g_listStored || !g_listFound || !g_persistentStorage || !g_homeKeyword || !recipeKeyword || !excludeKeywords) { + SKSE::log::warn("Failed to load data from Artifact Tracker.esp"); + RE::DebugMessageBox("Failed to load data from Artifact Tracker.esp, the mod is disabled."); + return; + } + + // Preloading item lists + + for (const auto& form : dataHandler->GetFormArray()) { + if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { + g_artifactMap[form->formID] = form; + } + } + + for (const auto& form : dataHandler->GetFormArray(RE::FormType::Misc)) { + if (form->GetPlayable() && !form->HasKeywordInList(excludeKeywords, false)) { + g_artifactMap[form->formID] = form; + } + } + g_artifactMap.erase(0xA); // Lockpick + g_artifactMap.erase(0xF); // Gold + + for (const auto& form : dataHandler->GetFormArray(RE::FormType::Weapon)) { + if (form->GetPlayable()) { + g_artifactMap[form->formID] = form; + } + } + g_artifactMap.erase(0x1F4); // Unarmed + + for (const auto& form : dataHandler->GetFormArray(RE::FormType::Armor)) { + if (form->GetPlayable()) { + g_artifactMap[form->formID] = form; + } + } + + OnGameLoad(); + EventListener::Install(); + + g_bLoaded = true; + } + + bool IsArtifact(RE::TESForm* a_form) + { + if (!a_form) { + return false; + } + + const auto formType = a_form->GetFormType(); + + if (formType == RE::FormType::Armor || formType == RE::FormType::Weapon || formType == RE::FormType::Book || formType == RE::FormType::Misc) { + return g_artifactMap.contains(a_form->formID); + } + + return false; + } + + RE::TESForm* GetArtifactByID(RE::FormID a_formID) + { + if (!a_formID) { + return nullptr; + } + + const auto it = g_artifactMap.find(a_formID); + return it != g_artifactMap.end() ? it->second : nullptr; + } + + void OnGameLoad() + { + #ifdef _DEBUG + SKSE::log::info("OnGameLoad"); + #endif + + g_persistentMap.clear(); + g_persistentStorage->ForEachForm([&](RE::TESForm& a_exform) { + if (&a_exform) { + g_persistentMap[a_exform.formID] = a_exform.As(); + } + return true; + }); + } + + void SetContainerMode(bool bOpening) + { + if (bOpening) { + + const auto refr = RE::TESObjectREFR::LookupByHandle(RE::ContainerMenu::GetTargetRefHandle()); + + g_bHomeContainer = IsHome() + && refr + && refr.get()->GetParentCell() == RE::PlayerCharacter::GetSingleton()->GetParentCell() + && !g_persistentStorage->HasForm(refr.get()); + + #ifdef _DEBUG + if (g_bHomeContainer) { + RE::DebugNotification("Delayed processing enabled"); + } + #endif + + } else if (g_bHomeContainer) { + g_bHomeContainer = false; + SyncCellStorage(); + } + } + + bool IsHome() + { + return (bool)g_cellStorage; + } + + bool ToggleHomeMode(RE::TESObjectREFR* cellStorage) + { + if (cellStorage) { + g_bHomeContainer = false; + g_cellStorage = cellStorage; + RE::UI::GetSingleton()->AddEventSink(EventListener::GetSingleton()); + #ifdef _DEBUG + SKSE::log::info("Home mode ON"); + #endif + return true; + } else if (g_cellStorage) { + g_bHomeContainer = false; + g_cellStorage = nullptr; + RE::UI::GetSingleton()->RemoveEventSink(EventListener::GetSingleton()); + #ifdef _DEBUG + SKSE::log::info("Home mode OFF"); + #endif + } + return false; + } + + bool IsValidContainer(RE::TESObjectREFR* a_ref) + { + if (!a_ref) { + return false; + } + + const auto baseObj = a_ref->GetBaseObject(); + + return baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As()->GetRace()->formID == 0x0010760A); + } + + void OnCellEnter(RE::FormID a_formID) + { + RE::TESObjectCELL* cell = RE::TESForm::LookupByID(a_formID); + + if (!cell || !cell->IsInteriorCell()) { + if (IsHome()) { + RE::ScriptEventSourceHolder::GetSingleton()->RemoveEventSink(EventListener::GetSingleton()); + ToggleHomeMode(nullptr); + } + return; + } + + RE::TESObjectREFR* cellStorage = nullptr; + + g_persistentStorage->ForEachForm([&](RE::TESForm& a_form) { + const auto refr = a_form.As(); + if (refr && refr->GetParentCell()->formID == a_formID) { + cellStorage = refr; + return false; + } + return true; + }); + + ToggleHomeMode(cellStorage); + + if (!cellStorage) { + RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink(EventListener::GetSingleton()); + } + } + + void OnCellEnter(RE::BGSLocation* location, RE::TESObjectCELL* cell) + { + if (!location || !cell->IsInteriorCell() || cell != RE::PlayerCharacter::GetSingleton()->GetParentCell() || !location->HasKeyword(g_homeKeyword)) { + ToggleHomeMode(nullptr); + return; + } + + RE::TESObjectREFR* cellStorage = nullptr; + + for (const auto& a_ref : cell->references) { + if (a_ref.get()->GetBaseObject()->formID == g_cellContainer->formID) { + cellStorage = a_ref.get(); + break; + } + } + + if (cellStorage) { + if (!g_persistentStorage->HasForm(cellStorage)) { + g_persistentStorage->AddForm(cellStorage); + g_persistentMap[cellStorage->formID] = cellStorage; + } + ToggleHomeMode(cellStorage); + return; + } + + #ifdef _DEBUG + SKSE::log::info("Adding new storage in {}", cell->GetName()); + #endif + + cellStorage = RE::PlayerCharacter::GetSingleton()->PlaceObjectAtMe(g_cellContainer, true).get(); + + if (cellStorage) { + cellStorage->Disable(); + g_persistentStorage->AddForm(cellStorage); + g_persistentMap[cellStorage->formID] = cellStorage; + ToggleHomeMode(cellStorage); + } else { + SKSE::log::warn("Failed to create cell storage in OnCellEnter"); + ToggleHomeMode(nullptr); + } + } + + void SyncCellStorage(RE::FormID skipRefID) + { + if (!IsHome()) { + #ifdef _DEBUG + SKSE::log::info("SyncCellStorage called while not at home"); + #endif + return; + } + + #ifdef _DEBUG + SKSE::log::info("Running SyncCellStorage"); + #endif + + std::unordered_set cellItems; + + const auto cell = g_cellStorage->GetParentCell(); + const auto inv = g_cellStorage->GetInventory(); + + for (const auto& a_ref : cell->references) { + const auto baseObj = a_ref->GetBaseObject(); + + if (IsValidContainer(a_ref.get())) { + if (g_cellContainer->formID == baseObj->formID || baseObj->formID == 0xDC9E7 || g_persistentMap.contains(a_ref->formID)) { // skip persistent and PlayerBookShelfContainer + continue; + } + + const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool { + return !cellItems.contains(a_object.formID); + }); + + for (const auto& [item, data] : contInv) { + if (data.first > 0) { + cellItems.insert(item->formID); + if (IsArtifact(item)) { + if (inv.find(item) == inv.end()) { + g_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr); + } + if (!g_listStored->HasForm(item)) { + ListRemoveItem(g_listNew, item); + ListRemoveItem(g_listFound, item); + g_listStored->AddForm(item); + } + } + } + } + + continue; + } + + if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion() || a_ref->formID == skipRefID) { + continue; + } + + if (cellItems.contains(baseObj->formID)) { + continue; + } + + cellItems.insert(baseObj->formID); + + if (!IsArtifact(baseObj)) { + continue; + } + + if (inv.find(baseObj) == inv.end()) { + g_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr); + } + + if (!g_listStored->HasForm(baseObj)) { + ListRemoveItem(g_listNew, baseObj); + ListRemoveItem(g_listFound, baseObj); + g_listStored->AddForm(baseObj); + } + } + + for (const auto& [item, data] : inv) { + const auto& [count, entry] = data; + if (count > 0 && !cellItems.contains(item->formID)) { + g_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); + if (!RefHasItem(g_persistentStorage, item->formID)) { + ListRemoveItem(g_listStored, item); + g_listFound->AddForm(item); + } + } + } + + cellItems.clear(); + } + + void OnContainerChanged(const RE::TESContainerChangedEvent* a_event, RE::TESForm* form) + { + if (a_event->newContainer == 0x14) { + + if (a_event->oldContainer) { + + if (g_persistentMap.contains(a_event->oldContainer)) { + + // Items in persistent containers are marked as stored by definition, no need to check the list + if (!RefHasItem(g_persistentStorage, a_event->baseObj)) { + ListRemoveItem(g_listStored, form); + g_listFound->AddForm(form); + } + + return; + + } else if (g_cellStorage) { // non-persistent container at home + + // g_bContainerMode is expected to be true, enabling processing after closing container. + // Reachable by Loot Menu or a mod, retrieving items via a spell of after scanning containers in cell. + // In extreme cases of mass retrieval, this can result in a few frames lag. + + #ifdef _DEBUG + SKSE::log::info("Synchronous processing of a non-persistent container (moved to player)"); + #endif + + SyncCellStorage(); + + return; + } + + } else if (g_cellStorage) { // probably, picked up at home (can also be crafted, but oh well) + + // OnContainerChanged fires before a picked item gets marked for deletion + RE::FormID lastPickupFormID = NULL; + const auto crosshairObj = RE::CrosshairPickData::GetSingleton()->target; + if (crosshairObj && crosshairObj.get() && crosshairObj.get().get()->GetBaseObject()->formID == a_event->baseObj) { + lastPickupFormID = crosshairObj.get().get()->formID; + } + + SyncCellStorage(lastPickupFormID); + return; + } + + // Instead of looking up in huge g_listNew, we check smaller g_listStored and g_listFound. + // Mass retrieval of found items is rare, so we check the stored list first. + if (g_listStored->HasForm(form) || g_listFound->HasForm(form)) { + return; + } + + // It's a new item, move it to found + ListRemoveItem(g_listNew, form); + g_listFound->AddForm(form); + + return; + } + + if (a_event->oldContainer != 0x14) { + return; + } + + // Items moved from player's inventory + + if (!a_event->newContainer) { // no destination container + + if (g_cellStorage && a_event->reference) { // dropped or placed on rack at home + if (!RefHasItem(g_cellStorage, form->formID)) { + #ifdef _DEBUG + SKSE::log::info("Added dropped {} to cell storage", form->GetName()); + #endif + RE::DebugNotification("adding to cell storage"); + g_cellStorage->AddObjectToContainer(form->As(), nullptr, 1, nullptr); + } + ListRemoveItem(g_listFound, form); + ListRemoveItem(g_listNew, form); + g_listStored->AddForm(form); + return; + } + + if (g_listStored->HasForm(form)) { + return; + } + + if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form->formID) && !FollowersHaveItem(form)) { + ListRemoveItem(g_listFound, form); + ListRemoveItem(g_listNew, form); + g_listNew->AddForm(form); + } + + return; + } + + if (g_persistentMap.contains(a_event->newContainer)) { // moved to a persistent container + + if (!g_listStored->HasForm(form)) { + ListRemoveItem(g_listFound, form); + g_listStored->AddForm(form); + } + + } else if (g_cellStorage) { // stored at home in a non-persistent/non-registered container + + // g_bContainerMode is expected to be true, enabling processing after closing container. + // Can be hit by autosorting mods. Most of them work with persistent containers, which should be added to the list of persistent containers. + + #ifdef _DEBUG + SKSE::log::info("Synchronous processing of a non-persistent container (moved from player)"); + #endif + + const auto targetContainer = RE::TESForm::LookupByID(a_event->newContainer); + + if (IsValidContainer(targetContainer)) { + if (!RefHasItem(g_cellStorage, form->formID)) { + g_cellStorage->AddObjectToContainer(form->As(), nullptr, 1, nullptr); + } + + if (!g_listStored->HasForm(form)) { + ListRemoveItem(g_listFound, form); + g_listStored->AddForm(form); + } + } + + } else if (g_listFound->HasForm(form) && !RefHasItem(RE::PlayerCharacter::GetSingleton(), form->formID) && !FollowersHaveItem(form)) { + ListRemoveItem(g_listFound, form); + g_listNew->AddForm(form); + } + } + + void AddRefArtifactsToList(RE::TESForm* a_refOrList, RE::BGSListForm* a_targetList, RE::BGSListForm* a_excludeList) + { + if (!a_refOrList || !a_targetList) { + SKSE::log::warn("Invalid arguments in AddRefArtifactsToList"); + return; + } + + if (a_refOrList->Is(RE::FormType::FormList)) { + a_refOrList->As()->ForEachForm([&](RE::TESForm& a_exform) { + const auto refrItem = a_exform.As(); + if (refrItem) { + AddRefArtifactsToList(refrItem, a_targetList, a_excludeList); + } + return true; + }); + return; + } + + const auto containerRef = a_refOrList->As(); + + if (!containerRef) { + SKSE::log::warn("containerRef in AddRefArtifactsToList is not a reference"); + return; + } + + const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) { + return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); + }); + + for (const auto& item : inv) { + if (item.second.first > 0) { + a_targetList->AddForm(item.first); + } + } + } +} diff --git a/Source/ArtifactTrackerDLL/src/ArtifactTracker.h b/Source/ArtifactTrackerDLL/src/ArtifactTracker.h index 35c2322..423fc54 100644 --- a/Source/ArtifactTrackerDLL/src/ArtifactTracker.h +++ b/Source/ArtifactTrackerDLL/src/ArtifactTracker.h @@ -2,28 +2,37 @@ namespace ArtifactTracker { - extern RE::TESGlobal* notifyNewArtifact; - extern RE::TESBoundObject* cellContainer; - extern RE::BGSListForm* listNew; - extern RE::BGSListForm* listStored; - extern RE::BGSListForm* listFound; - extern RE::BGSListForm* persistentStorage; - extern RE::BGSKeyword* homeKeyword; - extern bool bAtHome; - extern std::unordered_map validMisc; - extern std::unordered_map validBooks; + extern bool g_bLoaded; + extern bool g_bHomeContainer; + extern RE::TESBoundObject* g_cellContainer; + extern RE::BGSListForm* g_listNew; + extern RE::BGSListForm* g_listStored; + extern RE::BGSListForm* g_listFound; + extern RE::BGSListForm* g_persistentStorage; + extern RE::BGSKeyword* g_homeKeyword; + extern std::unordered_map g_artifactMap; + extern std::unordered_map g_persistentMap; + extern RE::TESObjectREFR* g_cellStorage; void Init(); bool IsArtifact(RE::TESForm* a_item); - bool SetHomeLocation(RE::BGSLocation* a_location); + RE::TESForm* GetArtifactByID(RE::FormID a_formID); - RE::TESObjectREFR* GetCellStorage(RE::TESObjectREFR* a_ref, RE::BGSListForm* a_refList, RE::TESBoundObject* a_objectToCreate, bool a_autoCreate = true); + bool IsHome(); - void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers); + void OnGameLoad(); + + void SetContainerMode(bool bOpening); - void OnItemPickup(RE::TESBoundObject* form); + void OnCellEnter(RE::FormID a_formID); - void OnContainerChanged(const RE::TESContainerChangedEvent* a_event); + void OnCellEnter(RE::BGSLocation* location, RE::TESObjectCELL* cell); + + void SyncCellStorage(RE::FormID skipRefID = NULL); + + void OnContainerChanged(const RE::TESContainerChangedEvent* a_event, RE::TESForm* form); + + void AddRefArtifactsToList(RE::TESForm* a_refOrList, RE::BGSListForm* a_targetList, RE::BGSListForm* a_excludeList = NULL); } diff --git a/Source/ArtifactTrackerDLL/src/EventListener.cpp b/Source/ArtifactTrackerDLL/src/EventListener.cpp index 961a59a..2b8c8e5 100644 --- a/Source/ArtifactTrackerDLL/src/EventListener.cpp +++ b/Source/ArtifactTrackerDLL/src/EventListener.cpp @@ -1,7 +1,6 @@ #include "EventListener.h" #include "ArtifactTracker.h" - auto EventListener::GetSingleton() -> EventListener* { static EventListener singleton{}; @@ -10,29 +9,59 @@ auto EventListener::GetSingleton() -> EventListener* void EventListener::Install() { - const auto eventSource = RE::ScriptEventSourceHolder::GetSingleton(); - eventSource->AddEventSink(EventListener::GetSingleton()); - eventSource->AddEventSink(EventListener::GetSingleton()); + RE::PlayerCharacter::GetSingleton()->AddEventSink(EventListener::GetSingleton()); + RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink(EventListener::GetSingleton()); } auto EventListener::ProcessEvent( - const RE::TESActorLocationChangeEvent* a_event, - RE::BSTEventSource* a_eventSource) + const RE::TESContainerChangedEvent* a_event, + RE::BSTEventSource* a_eventSource) -> RE::BSEventNotifyControl { - if (a_event->actor && a_event->actor->IsPlayerRef()) { - ArtifactTracker::SetHomeLocation(a_event->newLoc); + if (!ArtifactTracker::g_bHomeContainer && (a_event->newContainer == 0x14 || a_event->oldContainer == 0x14)) { + const auto form = ArtifactTracker::GetArtifactByID(a_event->baseObj); + if (form) { + ArtifactTracker::OnContainerChanged(a_event, form); + } } return RE::BSEventNotifyControl::kContinue; } auto EventListener::ProcessEvent( - const RE::TESContainerChangedEvent* a_event, - RE::BSTEventSource* a_eventSource) + const RE::BGSActorCellEvent* a_event, + RE::BSTEventSource* a_eventSource) -> RE::BSEventNotifyControl { - ArtifactTracker::OnContainerChanged(a_event); + if (a_event->flags == RE::BGSActorCellEvent::CellFlag::kEnter) { + ArtifactTracker::OnCellEnter(a_event->cellID); + } return RE::BSEventNotifyControl::kContinue; -} \ No newline at end of file +} + +auto EventListener::ProcessEvent( + const RE::TESCellFullyLoadedEvent* a_event, + RE::BSTEventSource* a_eventSource) + -> RE::BSEventNotifyControl +{ + #ifdef _DEBUG + SKSE::log::info("TESCellFullyLoadedEvent"); + #endif + + ArtifactTracker::OnCellEnter(a_event->cell->GetLocation(), a_event->cell); + + return RE::BSEventNotifyControl::kContinue; +} + +auto EventListener::ProcessEvent( + const RE::MenuOpenCloseEvent* a_event, + RE::BSTEventSource* a_eventSource) + -> RE::BSEventNotifyControl +{ + if (ArtifactTracker::IsHome() && a_event->menuName == "ContainerMenu") { + ArtifactTracker::SetContainerMode(a_event->opening); + } + + return RE::BSEventNotifyControl::kContinue; +} diff --git a/Source/ArtifactTrackerDLL/src/EventListener.h b/Source/ArtifactTrackerDLL/src/EventListener.h index fce8778..8b1f0a4 100644 --- a/Source/ArtifactTrackerDLL/src/EventListener.h +++ b/Source/ArtifactTrackerDLL/src/EventListener.h @@ -1,8 +1,10 @@ #pragma once class EventListener : - public RE::BSTEventSink, - public RE::BSTEventSink + public RE::BSTEventSink, + public RE::BSTEventSink, + public RE::BSTEventSink, + public RE::BSTEventSink { public: ~EventListener() = default; @@ -15,13 +17,23 @@ public: static void Install(); auto ProcessEvent( - const RE::TESActorLocationChangeEvent* a_event, - RE::BSTEventSource* a_eventSource) + const RE::TESContainerChangedEvent* a_event, + RE::BSTEventSource* a_eventSource) -> RE::BSEventNotifyControl override; auto ProcessEvent( - const RE::TESContainerChangedEvent* a_event, - RE::BSTEventSource* a_eventSource) + const RE::TESCellFullyLoadedEvent* a_event, + RE::BSTEventSource* a_eventSource) + -> RE::BSEventNotifyControl override; + + auto ProcessEvent( + const RE::BGSActorCellEvent* a_event, + RE::BSTEventSource* a_eventSource) + -> RE::BSEventNotifyControl override; + + auto ProcessEvent( + const RE::MenuOpenCloseEvent* a_event, + RE::BSTEventSource* a_eventSource) -> RE::BSEventNotifyControl override; private: diff --git a/Source/ArtifactTrackerDLL/src/Main.cpp b/Source/ArtifactTrackerDLL/src/Main.cpp index 87b3795..02d22c2 100644 --- a/Source/ArtifactTrackerDLL/src/Main.cpp +++ b/Source/ArtifactTrackerDLL/src/Main.cpp @@ -15,7 +15,7 @@ namespace { stl::report_and_fail("Failed to find standard logging directory"sv); } - *path /= "ArtifactTrackerFunctions.log"sv; + *path /= "ArtifactTracker.log"sv; auto sink = std::make_shared(path->string(), true); auto log = std::make_shared("global log"s, std::move(sink)); @@ -32,6 +32,8 @@ namespace { GetMessagingInterface()->RegisterListener([](MessagingInterface::Message* message) { if (message->type == MessagingInterface::kDataLoaded) { ArtifactTracker::Init(); + } else if (message->type == MessagingInterface::kPostLoadGame) { + ArtifactTracker::OnGameLoad(); } }); } diff --git a/Source/ArtifactTrackerDLL/src/Papyrus.h b/Source/ArtifactTrackerDLL/src/Papyrus.h index cf5ad93..06a235f 100644 --- a/Source/ArtifactTrackerDLL/src/Papyrus.h +++ b/Source/ArtifactTrackerDLL/src/Papyrus.h @@ -9,7 +9,7 @@ namespace Papyrus { using StackID = RE::VMStackID; using Severity = RE::BSScript::ErrorLogger::Severity; - inline constexpr auto script = "ETR_Functions"sv; + inline constexpr auto script = "ArtifactTrackerPlayer"sv; bool Bind(VM* a_vm); } diff --git a/Source/ArtifactTrackerDLL/src/PapyrusFunctions.h b/Source/ArtifactTrackerDLL/src/PapyrusFunctions.h index 4159bb2..9a770f2 100644 --- a/Source/ArtifactTrackerDLL/src/PapyrusFunctions.h +++ b/Source/ArtifactTrackerDLL/src/PapyrusFunctions.h @@ -1,164 +1,91 @@ #pragma once #include "ArtifactTracker.h" +#include "Util.h" namespace Papyrus::PapyrusFunctions { - inline std::int32_t AddAllFormsToList(RE::StaticFunctionTag*, - RE::BGSListForm* a_targetList, - short a_formType, - RE::BGSListForm* a_storedList, - RE::BGSListForm* a_foundList) + constexpr bool IsLoaded(RE::StaticFunctionTag*) { - const auto formType = static_cast(a_formType); - - switch (formType) { - - case RE::FormType::Book: - for (auto const& item : ArtifactTracker::validBooks) { - if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) { - a_targetList->AddForm(item.second); - } - } - break; - - case RE::FormType::Misc: - for (auto const& item : ArtifactTracker::validMisc) { - if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) { - a_targetList->AddForm(item.second); - } - } - break; - - case RE::FormType::None: - break; - - default: - const auto dataHandler = RE::TESDataHandler::GetSingleton(); - - if (!dataHandler) { - return a_targetList->forms.size(); - } + return ArtifactTracker::g_bLoaded; + } - for (const auto& form : dataHandler->GetFormArray(formType)) { - if (!form || !form->GetPlayable()) { - continue; - } - if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) { - continue; - } - a_targetList->AddForm(form); - } - } + inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*) + { + return ArtifactTracker::g_cellStorage; + } - return a_targetList->forms.size(); + inline void SyncCellStorage(RE::StaticFunctionTag*, RE::FormID skipRefID = NULL) + { + ArtifactTracker::SyncCellStorage(skipRefID); } - inline std::int32_t AddArtifactsToList(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, - RE::TESForm* a_refOrList, - RE::BGSListForm* a_targetList, - RE::BGSListForm* a_excludeList = NULL) + // From po3's Papyrus Extender + inline std::vector GetPlayerFollowers(RE::StaticFunctionTag*) { - if (!a_refOrList) { - a_vm->TraceStack("a_refOrList in AddArtifactsToList is None", a_stackID); - return 0; - } - if (!a_targetList) { - a_vm->TraceStack("a_targetList in AddArtifactsToList is None", a_stackID); - return 0; - } + std::vector result; - if (a_refOrList->Is(RE::FormType::FormList)) { - a_refOrList->As()->ForEachForm([&](RE::TESForm& a_exform) { - const auto refrItem = a_exform.As(); - if (refrItem) { - AddArtifactsToList(a_vm, a_stackID, nullptr, refrItem, a_targetList, a_excludeList); + if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { + for (auto& actorHandle : processLists->highActorHandles) { + if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { + result.push_back(actor.get()); } - return true; - }); - return a_targetList->forms.size(); - } - - const auto containerRef = a_refOrList->As(); - - if (!containerRef) { - a_vm->TraceStack("containerRef in AddArtifactsToList is not a reference", a_stackID); - return 0; - } - - const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) { - return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); - }); - - for (const auto& item : inv) { - if (item.second.first > 0) { - a_targetList->AddForm(item.first); } } - return a_targetList->forms.size(); + return result; } - inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*, - RE::TESObjectREFR* a_ref, - RE::BGSListForm* a_refList, - RE::TESBoundObject* a_objectToCreate, - bool a_autoCreate = true) + inline RE::TESObjectREFR* GetCurrentContainer(RE::StaticFunctionTag*) { - return ArtifactTracker::GetCellStorage(a_ref, a_refList, a_objectToCreate, a_autoCreate); + const auto handle = RE::ContainerMenu::GetTargetRefHandle(); + const auto refr = RE::TESObjectREFR::LookupByHandle(handle); + return refr ? refr.get() : nullptr; } - inline void SyncCellStorage(RE::StaticFunctionTag*, - RE::TESObjectREFR* a_cellStorage, - RE::BGSListForm* a_excludeContainers) + inline void RescanStoredArtifacts(RE::StaticFunctionTag*) { - ArtifactTracker::SyncCellStorage(a_cellStorage, a_excludeContainers); + ListRevert(ArtifactTracker::g_listStored); + ArtifactTracker::AddRefArtifactsToList(ArtifactTracker::g_persistentStorage, ArtifactTracker::g_listStored); } - - inline std::int32_t AddArtifactsFromFollowersToList(RE::StaticFunctionTag*, - RE::BGSListForm* a_targetList, - RE::BGSListForm* a_excludeList = NULL) - { - if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { - for (auto& actorHandle : processLists->highActorHandles) { - if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { - const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_exform) { - return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); - }); - - for (const auto& item : inv) { - if (item.second.first > 0) { - a_targetList->AddForm(item.first); - } - } + inline void RescanFoundArtifacts(RE::StaticFunctionTag*) + { + ListRevert(ArtifactTracker::g_listFound); + ArtifactTracker::AddRefArtifactsToList(RE::PlayerCharacter::GetSingleton(), ArtifactTracker::g_listFound, ArtifactTracker::g_listStored); - } - } + for (const auto& ref : GetPlayerFollowers(nullptr)) { + ArtifactTracker::AddRefArtifactsToList(ref, ArtifactTracker::g_listFound, ArtifactTracker::g_listStored); } - - return a_targetList->forms.size(); } - inline void OnItemPickup(RE::StaticFunctionTag*, - RE::TESBoundObject* a_item) + inline void RescanNewArtifacts(RE::StaticFunctionTag*) { - ArtifactTracker::OnItemPickup(a_item); + ListRevert(ArtifactTracker::g_listNew); + for (auto const& item : ArtifactTracker::g_artifactMap) { + if (!ArtifactTracker::g_listStored->HasForm(item.second) && !ArtifactTracker::g_listFound->HasForm(item.second)) { + ArtifactTracker::g_listNew->AddForm(item.second); + } + } } inline void Bind(VM& a_vm) { - BIND(AddAllFormsToList); - logger::info("Registered AddAllFormsToList"sv); - BIND(AddArtifactsToList); - logger::info("Registered AddArtifactsToList"sv); - BIND(AddArtifactsFromFollowersToList); - logger::info("Registered AddArtifactsFromFollowersToList"sv); + BIND(IsLoaded); + logger::info("Registered IsLoaded"sv); + BIND(RescanStoredArtifacts); + logger::info("Registered RescanStoredArtifacts"sv); + BIND(RescanFoundArtifacts); + logger::info("Registered RescanFoundArtifacts"sv); + BIND(RescanNewArtifacts); + logger::info("Registered RescanNewArtifacts"sv); BIND(GetCellStorage); logger::info("Registered GetCellStorage"sv); BIND(SyncCellStorage); logger::info("Registered SyncCellStorage"sv); - BIND(OnItemPickup); - logger::info("Registered OnItemPickup"sv); + BIND(GetPlayerFollowers); + logger::info("Registered GetPlayerFollowers"sv); + BIND(GetCurrentContainer); + logger::info("Registered GetCurrentContainer"sv); } } diff --git a/Source/ArtifactTrackerDLL/src/Util.h b/Source/ArtifactTrackerDLL/src/Util.h index 63adbef..fa13069 100644 --- a/Source/ArtifactTrackerDLL/src/Util.h +++ b/Source/ArtifactTrackerDLL/src/Util.h @@ -1,78 +1,66 @@ -#pragma once - -void RemoveListItem(RE::BGSListForm* a_List, RE::TESForm* a_form) -{ - using func_t = decltype(&RemoveListItem); - REL::Relocation func{ REL::RelocationID(20471, 20914) }; - return func(a_List, a_form); -} - -void RevertList(RE::TESForm* a_form) -{ - using func_t = decltype(&RevertList); - REL::Relocation func{ REL::RelocationID(20469, 20912) }; - return func(a_form); -} - -inline bool RefHasItem(RE::TESObjectREFR* a_ref, RE::TESForm* a_item) -{ - if (!a_ref || !a_item) { - SKSE::log::warn("Invalid arguments in RefHasItem"); - return false; - } - - auto invChanges = a_ref->GetInventoryChanges(); - if (invChanges && invChanges->entryList) { - for (auto& entry : *invChanges->entryList) { - if (entry && entry->object && entry->object->formID == a_item->formID) { - return entry->countDelta > 0; - } - } - } - return false; -} - -inline std::uint32_t GetItemCountInList(RE::BGSListForm* a_containerList, RE::TESBoundObject* a_form) -{ - if (!a_containerList || !a_form) { - SKSE::log::warn("Invalid arguments in GetItemCountInList"); - return 0; - } - - std::uint32_t iResult = 0; - - a_containerList->ForEachForm([&](RE::TESForm& a_container) { - const auto refrItem = a_container.As(); - if (refrItem) { - const auto inv = refrItem->GetInventory([&](RE::TESBoundObject& a_object) -> bool { - return a_form->formID == a_object.formID; - }); - const auto it = inv.find(a_form); - iResult += it != inv.end() ? it->second.first : 0; - } - return true; - }); - - return iResult; -} - -inline bool FollowersHaveItem(RE::TESBoundObject* a_form) -{ - if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { - for (auto& actorHandle : processLists->highActorHandles) { - if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { - const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_object) -> bool { - return a_form->formID == a_object.formID; - }); - const auto it = inv.find(a_form); - const auto iCount = it != inv.end() ? it->second.first : 0; - - if (iCount > 0) { - return true; - } - } - } - } - - return false; +#pragma once + +inline void ListRemoveItem(RE::BGSListForm* a_List, RE::TESForm* a_form) +{ + using func_t = decltype(&ListRemoveItem); + REL::Relocation func{ REL::RelocationID(20471, 20914) }; + return func(a_List, a_form); +} + +inline void ListRevert(RE::BGSListForm* a_form) +{ + using func_t = decltype(&ListRevert); + REL::Relocation func{ REL::RelocationID(20469, 20912) }; + return func(a_form); +} + +inline bool RefHasItem(RE::TESForm* a_refOrList, RE::FormID a_formID) +{ + if (!a_refOrList || !a_formID) { + SKSE::log::warn("Invalid arguments in RefHasItem"); + return false; + } + + const auto refr = a_refOrList->As(); + + if (refr) { + const auto invChanges = refr->GetInventoryChanges(); + if (invChanges && invChanges->entryList) { + for (auto& entry : *invChanges->entryList) { + if (entry && entry->object && entry->object->formID == a_formID) { + return entry->countDelta > 0; + } + } + } + return false; + } + + const auto list = a_refOrList->As(); + + if (list) { + for (const auto& ref : list->forms) { + if (ref && RefHasItem(ref, a_formID)) { + return true; + } + } + } + + return false; +} + +inline bool FollowersHaveItem(RE::TESForm* a_form) +{ + if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { + for (auto& actorHandle : processLists->highActorHandles) { + if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { + + if (RefHasItem(actor->As(), a_form->formID)) { + return true; + } + + } + } + } + + return false; } \ No newline at end of file diff --git a/Source/Scripts/ArtifactTrackerPlayer.psc b/Source/Scripts/ArtifactTrackerPlayer.psc new file mode 100644 index 0000000..744f076 --- /dev/null +++ b/Source/Scripts/ArtifactTrackerPlayer.psc @@ -0,0 +1,120 @@ +Scriptname ArtifactTrackerPlayer extends ReferenceAlias + +FormList Property ETR_ItemsNew Auto +FormList Property ETR_ItemsFound Auto +FormList Property ETR_ItemsStored Auto + +Keyword Property LocTypePlayerHouse Auto + +bool bAtHome = false +int iFollowerIndex + + +event OnInit() + OnPlayerLoadGame() +endevent + + +Event OnPlayerLoadGame() + + if ! IsLoaded() + ETR_ItemsNew.Revert() + ETR_ItemsFound.Revert() + ETR_ItemsStored.Revert() + UnregisterForUpdate() + Debug.Notification("Failed to initialize ArtifactTracker.dll") + return + endif + + if skse.GetPluginVersion("Ahzaab's moreHUD Plugin") >= 30800 + ahzmorehud.RegisterIconFormList("dbmNew", ETR_ItemsNew) + ahzmorehud.RegisterIconFormList("dbmFound", ETR_ItemsFound) + ahzmorehud.RegisterIconFormList("dbmDisp", ETR_ItemsStored) + endif + + if skse.GetPluginVersion("Ahzaab's moreHUD Inventory Plugin") >= 10017 + ahzmorehudie.RegisterIconFormList("dbmNew", ETR_ItemsNew) + ahzmorehudie.RegisterIconFormList("dbmFound", ETR_ItemsFound) + ahzmorehudie.RegisterIconFormList("dbmDisp", ETR_ItemsStored) + endif + + if SKSE.GetPluginVersion("QuickLootRE") >= 292 + QuickLootRE.RegisterNewItemsList(ETR_ItemsNew) + QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored) + QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound) + endif + + ; Rebuild all lists to avoid discrepancies, stale data, and broken records + RescanStoredArtifacts() + RescanFoundArtifacts() + RescanNewArtifacts() + + Location currentLocation = (GetReference() as ObjectReference).GetCurrentLocation() + bAtHome = currentLocation && currentLocation.HasKeyword(LocTypePlayerHouse) + + if bAtHome + RegisterForModEvent("AT_HomeInventoryUpdate", "OnHomeInventoryUpdate") + else + UnregisterForModEvent("AT_HomeInventoryUpdate") + endif + +EndEvent + + +Event OnLocationChange(Location akOldLoc, Location akNewLoc) + + bAtHome = akNewLoc && akNewLoc.HasKeyword(LocTypePlayerHouse) + + if bAtHome + RegisterForModEvent("AT_HomeInventoryUpdate", "OnHomeInventoryUpdate") + elseif akOldLoc && akOldLoc.HasKeyword(LocTypePlayerHouse) + UnregisterForModEvent("AT_HomeInventoryUpdate") + endif + + int iCurrentFollowers = 0; + Actor[] aFollowers = GetPlayerFollowers() + int i = aFollowers.length + while i > 0 + i -= 1 + iCurrentFollowers += aFollowers[i].GetFormID() + endwhile + + if iCurrentFollowers != iFollowerIndex + iFollowerIndex = iCurrentFollowers + RegisterForSingleUpdate(5.0) ; wait until followers load into the location + endif + +endEvent + + +Event OnUpdate() + + Debug.Notification("Team changed, updating ETR_ItemsFound") + RescanFoundArtifacts() + +EndEvent + + +event OnHomeInventoryUpdate() + if bAtHome + Debug.Notification("Triggered AT_HomeInventoryUpdate") + SyncCellStorage() + endif +endevent + + +; NATIVE FUNCTIONS + +bool function IsLoaded() native global + +function RescanStoredArtifacts() native global + +function RescanFoundArtifacts() native global + +function RescanNewArtifacts() native global + +ObjectReference function GetCellStorage() native global + +function SyncCellStorage(int FormID = 0) native global + +Actor[] function GetPlayerFollowers() native global diff --git a/Source/Scripts/ETR_Functions.psc b/Source/Scripts/ETR_Functions.psc deleted file mode 100644 index 97dc4b8..0000000 --- a/Source/Scripts/ETR_Functions.psc +++ /dev/null @@ -1,13 +0,0 @@ -Scriptname ETR_Functions Hidden - -int function AddAllFormsToList(FormList targetList, int formType, FormList storedList, FormList foundList) native global - -int function AddArtifactsToList(Form refOrList, FormList targetList, FormList excludeList = None) native global - -ObjectReference function GetCellStorage(ObjectReference ref, FormList refList, Form refToCreate, bool autoCreate = true) native global - -function SyncCellStorage(ObjectReference cellStorage, FormList excludeContainers) native global - -int function AddArtifactsFromFollowersToList(FormList targetList, FormList excludeList = None) native global - -function OnItemPickup(Form item) native global diff --git a/Source/Scripts/ETR_TrackStoredItems.psc b/Source/Scripts/ETR_TrackStoredItems.psc deleted file mode 100644 index ae1be62..0000000 --- a/Source/Scripts/ETR_TrackStoredItems.psc +++ /dev/null @@ -1,179 +0,0 @@ -Scriptname ETR_TrackStoredItems extends ReferenceAlias - -Actor Property PlayerRef Auto - -FormList Property ETR_ItemsNew Auto -FormList Property ETR_ItemsFound Auto -FormList Property ETR_ItemsStored Auto -FormList Property ETR_PersistentStorageList Auto - -Container Property ETR_CellStorageContainer Auto - -Keyword Property LocTypePlayerHouse Auto - -bool bBusy = false -bool bAtHome = false -bool bRescanHome = false -bool bRescanPersistent = false -ObjectReference lastDestContainer = None -bool lastDestIsPersistent = false -ObjectReference lastSourceContainer = None -bool lastSourceIsPersistent = false -int iUpdateCount - - -event OnInit() - OnPlayerLoadGame() -endevent - - -Event OnPlayerLoadGame() - AddInventoryEventFilter(ETR_ItemsStored) - - if skse.GetPluginVersion("Ahzaab's moreHUD Plugin") >= 30800 - ahzmorehud.RegisterIconFormList("dbmNew", ETR_ItemsNew) - ahzmorehud.RegisterIconFormList("dbmFound", ETR_ItemsFound) - ahzmorehud.RegisterIconFormList("dbmDisp", ETR_ItemsStored) - endif - - if skse.GetPluginVersion("Ahzaab's moreHUD Inventory Plugin") >= 10017 - ahzmorehudie.RegisterIconFormList("dbmNew", ETR_ItemsNew) - ahzmorehudie.RegisterIconFormList("dbmFound", ETR_ItemsFound) - ahzmorehudie.RegisterIconFormList("dbmDisp", ETR_ItemsStored) - endif - - if SKSE.GetPluginVersion("QuickLootRE") >= 292 - QuickLootRE.RegisterNewItemsList(ETR_ItemsNew) - QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored) - QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound) - endif - - ; Rebuild all lists to avoid discrepancies, stale data, and broken records - - ETR_ItemsStored.Revert() - ETR_Functions.AddArtifactsToList(ETR_PersistentStorageList, ETR_ItemsStored) - - ETR_ItemsFound.Revert() - ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored) - ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored) - - ETR_ItemsNew.Revert() - ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 41, ETR_ItemsStored, ETR_ItemsFound) - ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 32, ETR_ItemsStored, ETR_ItemsFound) - ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 27, ETR_ItemsStored, ETR_ItemsFound) - ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 26, ETR_ItemsStored, ETR_ItemsFound) - - Location currentLocation = PlayerRef.GetCurrentLocation() - bAtHome = currentLocation && currentLocation.HasKeyword(LocTypePlayerHouse) - if bAtHome - GotoState("AtHome") - else - GotoState("") - endif -EndEvent - - -Event OnLocationChange(Location akOldLoc, Location akNewLoc) - bAtHome = akNewLoc && akNewLoc.HasKeyword(LocTypePlayerHouse) - if bAtHome - GotoState("AtHome") - else - GotoState("") - endif -endEvent - - -Event OnMenuClose(String MenuName) - UnregisterForUpdate() - OnUpdate() -EndEvent - - -Event OnUpdate() - - if UI.IsMenuOpen("ContainerMenu") - RegisterForMenu("ContainerMenu") - return - endif - - while bBusy - Debug.Notification("Stored OnUpdate is busy") - Utility.wait(0.5) - endwhile - - bBusy = true - - iUpdateCount += 1 - Debug.Notification("Running Stored OnUpdate " + iUpdateCount) - - if bRescanHome - if lastSourceContainer && lastSourceContainer as Actor && (lastSourceContainer as Actor).IsPlayerTeammate() - lastSourceContainer = None - return - endif - bRescanHome = false - - ObjectReference bookShelf - if lastDestContainer - if lastDestContainer as PlayerBookShelfContainerScript - bookShelf = lastDestContainer - endif - elseif lastSourceContainer - if lastSourceContainer as PlayerBookShelfContainerScript - bookShelf = lastSourceContainer - endif - endif - - if bookShelf - int iLimit = 10 - while iLimit > 0 && (bookShelf as PlayerBookShelfContainerScript).GetState() == "PlacingBooks" - Debug.Notification("Waiting for shelf update") - iLimit -= 1 - Utility.wait(0.5) - endwhile - endif - - ObjectReference cellStorage = ETR_Functions.GetCellStorage(PlayerRef, ETR_PersistentStorageList, ETR_CellStorageContainer) - ETR_Functions.SyncCellStorage(cellStorage, ETR_PersistentStorageList) - endif - - if bRescanPersistent - bRescanPersistent = false - - ETR_ItemsStored.Revert() - Form[] aContainers = ETR_PersistentStorageList.ToArray() - - int n = aContainers.length - while n > 0 - n -= 1 - ETR_Functions.AddArtifactsToList(aContainers[n], ETR_ItemsStored) - endwhile - - ETR_ItemsFound.Revert() - ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored) - ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored) - endif - - bBusy = false - -EndEvent - - -; We acquired a stored item, and we want to find out if we just have taken the last stored item of its kind -event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) - - if akSourceContainer - if lastSourceContainer != akSourceContainer - lastSourceContainer = akSourceContainer - lastSourceIsPersistent = ETR_PersistentStorageList.HasForm(akSourceContainer) - endif - if bAtHome || lastSourceIsPersistent - bRescanHome = bAtHome - bRescanPersistent = true - RegisterForSingleUpdate(0.5) - endif - elseif bAtHome - ETR_Functions.OnItemPickup(akBaseItem) - endif - -endevent diff --git a/Source/Scripts/PlayerBookShelfContainerScript.psc b/Source/Scripts/PlayerBookShelfContainerScript.psc index a8beb03..74e9ca7 100644 --- a/Source/Scripts/PlayerBookShelfContainerScript.psc +++ b/Source/Scripts/PlayerBookShelfContainerScript.psc @@ -611,6 +611,9 @@ Function UpdateBooks() UnBlockActivate() ; Allow the player to mess with them GoToState("") ; Now allow books to be updated again + + ; Artifact Tracker + SendModEvent("AT_HomeInventoryUpdate") EndFunction State PlacingBooks