1
Fork 0

Moved almost everything to the DLL

ae-1.6.629
Eddoursul 2 years ago
parent a7229e0bea
commit f312441f13
  1. BIN
      Artifact Tracker.esp
  2. BIN
      SKSE/Plugins/ArtifactTracker.dll
  3. BIN
      SKSE/Plugins/ArtifactTrackerFunctions.dll
  4. BIN
      Scripts/ArtifactTrackerPlayer.pex
  5. BIN
      Scripts/ETR_Functions.pex
  6. BIN
      Scripts/ETR_TrackStoredItems.pex
  7. BIN
      Scripts/PlayerBookShelfContainerScript.pex
  8. 4
      Source/ArtifactTrackerDLL/CMakeLists.txt
  9. 827
      Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp
  10. 39
      Source/ArtifactTrackerDLL/src/ArtifactTracker.h
  11. 53
      Source/ArtifactTrackerDLL/src/EventListener.cpp
  12. 24
      Source/ArtifactTrackerDLL/src/EventListener.h
  13. 4
      Source/ArtifactTrackerDLL/src/Main.cpp
  14. 2
      Source/ArtifactTrackerDLL/src/Papyrus.h
  15. 175
      Source/ArtifactTrackerDLL/src/PapyrusFunctions.h
  16. 142
      Source/ArtifactTrackerDLL/src/Util.h
  17. 120
      Source/Scripts/ArtifactTrackerPlayer.psc
  18. 13
      Source/Scripts/ETR_Functions.psc
  19. 179
      Source/Scripts/ETR_TrackStoredItems.psc
  20. 3
      Source/Scripts/PlayerBookShelfContainerScript.psc

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -5,7 +5,7 @@ message("Using toolchain file ${CMAKE_TOOLCHAIN_FILE}.")
## Define project ## Define project
######################################################################################################################## ########################################################################################################################
project( project(
ArtifactTrackerFunctions ArtifactTracker
VERSION 1.0.0 VERSION 1.0.0
DESCRIPTION "Eddoursul's Artifact Tracker" DESCRIPTION "Eddoursul's Artifact Tracker"
LANGUAGES CXX) LANGUAGES CXX)
@ -51,7 +51,7 @@ target_include_directories(${PROJECT_NAME}
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME}
PUBLIC PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>) $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_precompile_headers(${PROJECT_NAME} target_precompile_headers(${PROJECT_NAME}
PRIVATE PRIVATE

@ -1,321 +1,506 @@
#include "ArtifactTracker.h" #include "ArtifactTracker.h"
#include "BookCheck.h" #include "BookCheck.h"
#include "EventListener.h" #include "EventListener.h"
#include "Util.h" #include "Util.h"
namespace ArtifactTracker namespace ArtifactTracker
{ {
RE::TESGlobal* notifyNewArtifact; bool g_bLoaded;
RE::TESBoundObject* cellContainer; bool g_bHomeContainer;
RE::BGSListForm* listNew; RE::TESBoundObject* g_cellContainer;
RE::BGSListForm* listStored; RE::BGSListForm* g_listNew;
RE::BGSListForm* listFound; RE::BGSListForm* g_listStored;
RE::BGSListForm* persistentStorage; RE::BGSListForm* g_listFound;
RE::BGSKeyword* homeKeyword; RE::BGSListForm* g_persistentStorage;
bool bAtHome; RE::BGSKeyword* g_homeKeyword;
std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc; std::unordered_map<RE::FormID, RE::TESForm*> g_artifactMap;
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks; std::unordered_map<RE::FormID, RE::TESObjectREFR*> g_persistentMap;
RE::TESObjectREFR* g_cellStorage;
void Init()
{ void Init()
EventListener::Install(); {
g_bLoaded = false;
const auto dataHandler = RE::TESDataHandler::GetSingleton();
const auto dataHandler = RE::TESDataHandler::GetSingleton();
notifyNewArtifact = dataHandler->LookupForm<RE::TESGlobal>(0x809, "Artifact Tracker.esp");
cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>(); if (!dataHandler) {
SKSE::log::warn("Failed to call RE::TESDataHandler::GetSingleton()");
listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp"); return;
listStored = dataHandler->LookupForm<RE::BGSListForm>(0x805, "Artifact Tracker.esp"); }
listFound = dataHandler->LookupForm<RE::BGSListForm>(0x806, "Artifact Tracker.esp");
persistentStorage = dataHandler->LookupForm<RE::BGSListForm>(0x807, "Artifact Tracker.esp"); g_cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>();
homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xFC1A3, "Skyrim.esm"); g_listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp");
g_listStored = dataHandler->LookupForm<RE::BGSListForm>(0x805, "Artifact Tracker.esp");
if (!cellContainer) { g_listFound = dataHandler->LookupForm<RE::BGSListForm>(0x806, "Artifact Tracker.esp");
SKSE::log::warn("cellContainer is empty"); g_persistentStorage = dataHandler->LookupForm<RE::BGSListForm>(0x807, "Artifact Tracker.esp");
}
g_homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xFC1A3, "Skyrim.esm");
if (!persistentStorage) {
SKSE::log::warn("persistentStorage is empty"); const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
} RE::BGSListForm* excludeKeywords = dataHandler->LookupForm<RE::BGSListForm>(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords
// Preloading item lists if (!g_cellContainer || !g_listNew || !g_listStored || !g_listFound || !g_persistentStorage || !g_homeKeyword || !recipeKeyword || !excludeKeywords) {
const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe 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.");
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) { return;
if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { }
validBooks[form->formID] = form;
} // Preloading item lists
}
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) {
RE::BGSListForm* excludeKeywords = dataHandler->LookupForm<RE::BGSListForm>(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) {
g_artifactMap[form->formID] = form;
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectMISC>()) { }
if (form->GetPlayable() && !form->IsGold() && !form->IsLockpick() && !form->HasKeywordInList(excludeKeywords, false)) { }
validMisc[form->formID] = form;
} for (const auto& form : dataHandler->GetFormArray(RE::FormType::Misc)) {
} if (form->GetPlayable() && !form->HasKeywordInList(excludeKeywords, false)) {
} g_artifactMap[form->formID] = form;
}
bool IsArtifact(RE::TESForm* a_form) }
{ g_artifactMap.erase(0xA); // Lockpick
switch (a_form->GetFormType()) { g_artifactMap.erase(0xF); // Gold
case RE::FormType::Armor:
return a_form->GetPlayable(); for (const auto& form : dataHandler->GetFormArray(RE::FormType::Weapon)) {
case RE::FormType::Weapon: if (form->GetPlayable()) {
return a_form->GetPlayable() && a_form->formID != 0x000001F4; g_artifactMap[form->formID] = form;
case RE::FormType::Book: }
return validBooks.contains(a_form->formID); }
case RE::FormType::Misc: g_artifactMap.erase(0x1F4); // Unarmed
return validMisc.contains(a_form->formID);
default: for (const auto& form : dataHandler->GetFormArray(RE::FormType::Armor)) {
return false; if (form->GetPlayable()) {
} g_artifactMap[form->formID] = form;
} }
}
bool SetHomeLocation(RE::BGSLocation* a_location = NULL)
{ OnGameLoad();
bAtHome = a_location && a_location->HasKeyword(homeKeyword); EventListener::Install();
return bAtHome;
} g_bLoaded = true;
}
RE::TESObjectREFR* GetCellStorage(
RE::TESObjectREFR* a_ref, bool IsArtifact(RE::TESForm* a_form)
RE::BGSListForm* a_refList, {
RE::TESBoundObject* a_objectToCreate, if (!a_form) {
bool a_autoCreate) return false;
{ }
RE::TESObjectREFR* result = NULL;
const auto formType = a_form->GetFormType();
if (!a_ref || !a_refList || !a_objectToCreate) {
SKSE::log::warn("Invalid arguments in GetCellStorage"); if (formType == RE::FormType::Armor || formType == RE::FormType::Weapon || formType == RE::FormType::Book || formType == RE::FormType::Misc) {
return result; return g_artifactMap.contains(a_form->formID);
} }
RE::TESObjectCELL* cell = a_ref->GetParentCell(); return false;
}
a_refList->ForEachForm([&result, &cell, &a_objectToCreate](RE::TESForm& a_exform) {
const auto ref = a_exform.As<RE::TESObjectREFR>(); RE::TESForm* GetArtifactByID(RE::FormID a_formID)
{
if (ref && ref->GetParentCell() == cell && ref->GetBaseObject()->formID == a_objectToCreate->formID) { if (!a_formID) {
result = ref; return nullptr;
return false; }
}
const auto it = g_artifactMap.find(a_formID);
return true; return it != g_artifactMap.end() ? it->second : nullptr;
}); }
if (!result && a_autoCreate) { void OnGameLoad()
result = a_ref->PlaceObjectAtMe(a_objectToCreate, true).get(); {
result->Disable(); #ifdef _DEBUG
a_refList->AddForm(result); SKSE::log::info("OnGameLoad");
} #endif
if (!result) { g_persistentMap.clear();
SKSE::log::warn("Failed to find or create cell storage in GetCellStorage"); g_persistentStorage->ForEachForm([&](RE::TESForm& a_exform) {
} if (&a_exform) {
g_persistentMap[a_exform.formID] = a_exform.As<RE::TESObjectREFR>();
return result; }
} return true;
});
void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers) }
{
if (!a_cellStorage || !a_excludeContainers) { void SetContainerMode(bool bOpening)
SKSE::log::warn("Invalid arguments in SyncCellStorage"); {
return; if (bOpening) {
}
const auto refr = RE::TESObjectREFR::LookupByHandle(RE::ContainerMenu::GetTargetRefHandle());
std::unordered_map<RE::FormID, bool> cellItems;
g_bHomeContainer = IsHome()
const auto cell = a_cellStorage->GetParentCell(); && refr
const auto inv = a_cellStorage->GetInventory(); && refr.get()->GetParentCell() == RE::PlayerCharacter::GetSingleton()->GetParentCell()
&& !g_persistentStorage->HasForm(refr.get());
for (const RE::NiPointer<RE::TESObjectREFR> a_ref : cell->references) {
const auto baseObj = a_ref->GetBaseObject(); #ifdef _DEBUG
if (g_bHomeContainer) {
if (baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As<RE::TESNPC>()->GetRace()->formID == 0x0010760A)) { RE::DebugNotification("Delayed processing enabled");
if (a_excludeContainers->HasForm(a_ref->formID) || baseObj->formID == 0xDC9E7) { // skip persistent and PlayerBookShelfContainer }
continue; #endif
}
} else if (g_bHomeContainer) {
const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool { g_bHomeContainer = false;
return !cellItems.contains(a_object.formID); SyncCellStorage();
}); }
}
for (const auto& [item, data] : contInv) {
if (data.first > 0) { bool IsHome()
cellItems[item->formID] = true; {
if (inv.find(item) == inv.end()) { return (bool)g_cellStorage;
if (IsArtifact(item)) { }
a_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr);
} bool ToggleHomeMode(RE::TESObjectREFR* cellStorage)
} {
} if (cellStorage) {
} g_bHomeContainer = false;
g_cellStorage = cellStorage;
continue; RE::UI::GetSingleton()->AddEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton());
} #ifdef _DEBUG
SKSE::log::info("Home mode ON");
if (a_ref->IsMarkedForDeletion()) { #endif
SKSE::log::info("found marked for deletion"); return true;
} } else if (g_cellStorage) {
g_bHomeContainer = false;
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) { g_cellStorage = nullptr;
continue; RE::UI::GetSingleton()->RemoveEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton());
} #ifdef _DEBUG
SKSE::log::info("Home mode OFF");
if (cellItems.contains(baseObj->formID)) { #endif
continue; }
} return false;
}
cellItems[baseObj->formID] = true;
bool IsValidContainer(RE::TESObjectREFR* a_ref)
if (!IsArtifact(baseObj)) { {
continue; if (!a_ref) {
} return false;
}
if (inv.find(baseObj) == inv.end()) {
a_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr); const auto baseObj = a_ref->GetBaseObject();
}
} return baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As<RE::TESNPC>()->GetRace()->formID == 0x0010760A);
}
for (const auto& [item, data] : inv) {
const auto& [count, entry] = data; void OnCellEnter(RE::FormID a_formID)
if (count > 0 && !cellItems.contains(item->formID)) { {
a_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); RE::TESObjectCELL* cell = RE::TESForm::LookupByID<RE::TESObjectCELL>(a_formID);
}
} if (!cell || !cell->IsInteriorCell()) {
if (IsHome()) {
cellItems.clear(); RE::ScriptEventSourceHolder::GetSingleton()->RemoveEventSink<RE::TESCellFullyLoadedEvent>(EventListener::GetSingleton());
} ToggleHomeMode(nullptr);
}
void OnItemPickup(RE::TESBoundObject* form) return;
{ }
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true);
RE::TESObjectREFR* cellStorage = nullptr;
SyncCellStorage(cellStorage, persistentStorage);
g_persistentStorage->ForEachForm([&](RE::TESForm& a_form) {
if (!RefHasItem(cellStorage, form) && !GetItemCountInList(persistentStorage, form)) { const auto refr = a_form.As<RE::TESObjectREFR>();
RemoveListItem(listStored, form); if (refr && refr->GetParentCell()->formID == a_formID) {
listFound->AddForm(form); cellStorage = refr;
} return false;
} }
return true;
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event) });
{
if (a_event->newContainer == 0x14) { ToggleHomeMode(cellStorage);
return;
RE::TESBoundObject* form = RE::TESForm::LookupByID<RE::TESBoundObject>(a_event->baseObj); if (!cellStorage) {
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESCellFullyLoadedEvent>(EventListener::GetSingleton());
if (!form || !IsArtifact(form)) { }
return; }
}
void OnCellEnter(RE::BGSLocation* location, RE::TESObjectCELL* cell)
if (listFound->HasForm(form) || listStored->HasForm(form)) { {
return; if (!location || !cell->IsInteriorCell() || cell != RE::PlayerCharacter::GetSingleton()->GetParentCell() || !location->HasKeyword(g_homeKeyword)) {
} ToggleHomeMode(nullptr);
return;
if (listNew->HasForm(form)) { }
RemoveListItem(listNew, form); RE::TESObjectREFR* cellStorage = nullptr;
listFound->AddForm(form); for (const auto& a_ref : cell->references) {
if (a_ref.get()->GetBaseObject()->formID == g_cellContainer->formID) {
if (notifyNewArtifact->value) { cellStorage = a_ref.get();
const auto itemName = form->GetName(); break;
char* notificationText = new char[strlen("New artifact acquired: ") + strlen(itemName) + 1]; }
strcpy(notificationText, "New artifact acquired: "); }
strcat(notificationText, itemName);
RE::DebugNotification(notificationText); if (cellStorage) {
delete[] notificationText; if (!g_persistentStorage->HasForm(cellStorage)) {
} g_persistentStorage->AddForm(cellStorage);
g_persistentMap[cellStorage->formID] = cellStorage;
} }
ToggleHomeMode(cellStorage);
} else if (a_event->oldContainer == 0x14) { return;
}
RE::TESBoundObject* form = RE::TESForm::LookupByID<RE::TESBoundObject>(a_event->baseObj);
#ifdef _DEBUG
SKSE::log::info("{}", form->GetName()); SKSE::log::info("Adding new storage in {}", cell->GetName());
if (!form || !IsArtifact(form)) { #endif
return;
} cellStorage = RE::PlayerCharacter::GetSingleton()->PlaceObjectAtMe(g_cellContainer, true).get();
if (!a_event->newContainer) { if (cellStorage) {
if (bAtHome && a_event->reference) { // dropped at home cellStorage->Disable();
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); g_persistentStorage->AddForm(cellStorage);
if (!RefHasItem(cellStorage, form)) { g_persistentMap[cellStorage->formID] = cellStorage;
cellStorage->AddObjectToContainer(form, nullptr, 1, nullptr); ToggleHomeMode(cellStorage);
} } else {
if (listFound->HasForm(form)) { SKSE::log::warn("Failed to create cell storage in OnCellEnter");
RemoveListItem(listFound, form); ToggleHomeMode(nullptr);
} }
listStored->AddForm(form); }
RE::DebugNotification("added to stored");
return; void SyncCellStorage(RE::FormID skipRefID)
} {
if (!IsHome()) {
if (listStored->HasForm(form)) { #ifdef _DEBUG
return; SKSE::log::info("SyncCellStorage called while not at home");
} #endif
return;
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form)) { }
if (listFound->HasForm(form)) {
RemoveListItem(listFound, form); #ifdef _DEBUG
} SKSE::log::info("Running SyncCellStorage");
listNew->AddForm(form); #endif
RE::DebugNotification("added to new");
} std::unordered_set<RE::FormID> cellItems;
return; const auto cell = g_cellStorage->GetParentCell();
} const auto inv = g_cellStorage->GetInventory();
bool bPersistent = false; for (const auto& a_ref : cell->references) {
for (const auto& ref : persistentStorage->forms) { const auto baseObj = a_ref->GetBaseObject();
if (ref && ref->formID == a_event->newContainer) {
bPersistent = true; if (IsValidContainer(a_ref.get())) {
break; if (g_cellContainer->formID == baseObj->formID || baseObj->formID == 0xDC9E7 || g_persistentMap.contains(a_ref->formID)) { // skip persistent and PlayerBookShelfContainer
} continue;
} }
if (!bPersistent && !bAtHome) { const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool {
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form->As<RE::TESBoundObject>())) { return !cellItems.contains(a_object.formID);
if (listFound->HasForm(form)) { });
RemoveListItem(listFound, form);
} for (const auto& [item, data] : contInv) {
listNew->AddForm(form); if (data.first > 0) {
RE::DebugNotification("added to new"); cellItems.insert(item->formID);
} if (IsArtifact(item)) {
return; if (inv.find(item) == inv.end()) {
} g_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr);
}
if (listStored->HasForm(form)) { if (!g_listStored->HasForm(item)) {
return; ListRemoveItem(g_listNew, item);
} ListRemoveItem(g_listFound, item);
g_listStored->AddForm(item);
if (!bPersistent && bAtHome) { }
RE::TESObjectREFR* targetContainer = RE::TESForm::LookupByID<RE::TESObjectREFR>(a_event->newContainer); }
}
if (!targetContainer || targetContainer->GetParentCell() != RE::PlayerCharacter::GetSingleton()->GetParentCell()) { }
return;
} continue;
}
if (targetContainer->GetBaseObject()->Is(RE::FormType::NPC) && targetContainer->GetBaseObject()->As<RE::TESNPC>()->GetRace()->formID != 0x0010760A) {
return; if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion() || a_ref->formID == skipRefID) {
} continue;
}
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true);
cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), nullptr, 1, nullptr); if (cellItems.contains(baseObj->formID)) {
RE::DebugNotification("updated cellStorage"); continue;
} }
if (listFound->HasForm(form)) { cellItems.insert(baseObj->formID);
RemoveListItem(listFound, form);
} if (!IsArtifact(baseObj)) {
continue;
listStored->AddForm(form); }
RE::DebugNotification("added to stored");
} 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<RE::TESBoundObject>(), 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<RE::TESObjectREFR>(a_event->newContainer);
if (IsValidContainer(targetContainer)) {
if (!RefHasItem(g_cellStorage, form->formID)) {
g_cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), 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<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) {
const auto refrItem = a_exform.As<RE::TESObjectREFR>();
if (refrItem) {
AddRefArtifactsToList(refrItem, a_targetList, a_excludeList);
}
return true;
});
return;
}
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>();
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);
}
}
}
}

@ -2,28 +2,37 @@
namespace ArtifactTracker namespace ArtifactTracker
{ {
extern RE::TESGlobal* notifyNewArtifact; extern bool g_bLoaded;
extern RE::TESBoundObject* cellContainer; extern bool g_bHomeContainer;
extern RE::BGSListForm* listNew; extern RE::TESBoundObject* g_cellContainer;
extern RE::BGSListForm* listStored; extern RE::BGSListForm* g_listNew;
extern RE::BGSListForm* listFound; extern RE::BGSListForm* g_listStored;
extern RE::BGSListForm* persistentStorage; extern RE::BGSListForm* g_listFound;
extern RE::BGSKeyword* homeKeyword; extern RE::BGSListForm* g_persistentStorage;
extern bool bAtHome; extern RE::BGSKeyword* g_homeKeyword;
extern std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc; extern std::unordered_map<RE::FormID, RE::TESForm*> g_artifactMap;
extern std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks; extern std::unordered_map<RE::FormID, RE::TESObjectREFR*> g_persistentMap;
extern RE::TESObjectREFR* g_cellStorage;
void Init(); void Init();
bool IsArtifact(RE::TESForm* a_item); 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);
} }

@ -1,7 +1,6 @@
#include "EventListener.h" #include "EventListener.h"
#include "ArtifactTracker.h" #include "ArtifactTracker.h"
auto EventListener::GetSingleton() -> EventListener* auto EventListener::GetSingleton() -> EventListener*
{ {
static EventListener singleton{}; static EventListener singleton{};
@ -10,29 +9,59 @@ auto EventListener::GetSingleton() -> EventListener*
void EventListener::Install() void EventListener::Install()
{ {
const auto eventSource = RE::ScriptEventSourceHolder::GetSingleton(); RE::PlayerCharacter::GetSingleton()->AddEventSink<RE::BGSActorCellEvent>(EventListener::GetSingleton());
eventSource->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton()); RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
eventSource->AddEventSink<RE::TESActorLocationChangeEvent>(EventListener::GetSingleton());
} }
auto EventListener::ProcessEvent( auto EventListener::ProcessEvent(
const RE::TESActorLocationChangeEvent* a_event, const RE::TESContainerChangedEvent* a_event,
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource) RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
-> RE::BSEventNotifyControl -> RE::BSEventNotifyControl
{ {
if (a_event->actor && a_event->actor->IsPlayerRef()) { if (!ArtifactTracker::g_bHomeContainer && (a_event->newContainer == 0x14 || a_event->oldContainer == 0x14)) {
ArtifactTracker::SetHomeLocation(a_event->newLoc); const auto form = ArtifactTracker::GetArtifactByID(a_event->baseObj);
if (form) {
ArtifactTracker::OnContainerChanged(a_event, form);
}
} }
return RE::BSEventNotifyControl::kContinue; return RE::BSEventNotifyControl::kContinue;
} }
auto EventListener::ProcessEvent( auto EventListener::ProcessEvent(
const RE::TESContainerChangedEvent* a_event, const RE::BGSActorCellEvent* a_event,
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource) RE::BSTEventSource<RE::BGSActorCellEvent>* a_eventSource)
-> RE::BSEventNotifyControl -> RE::BSEventNotifyControl
{ {
ArtifactTracker::OnContainerChanged(a_event); if (a_event->flags == RE::BGSActorCellEvent::CellFlag::kEnter) {
ArtifactTracker::OnCellEnter(a_event->cellID);
}
return RE::BSEventNotifyControl::kContinue; return RE::BSEventNotifyControl::kContinue;
} }
auto EventListener::ProcessEvent(
const RE::TESCellFullyLoadedEvent* a_event,
RE::BSTEventSource<RE::TESCellFullyLoadedEvent>* 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<RE::MenuOpenCloseEvent>* a_eventSource)
-> RE::BSEventNotifyControl
{
if (ArtifactTracker::IsHome() && a_event->menuName == "ContainerMenu") {
ArtifactTracker::SetContainerMode(a_event->opening);
}
return RE::BSEventNotifyControl::kContinue;
}

@ -1,8 +1,10 @@
#pragma once #pragma once
class EventListener : class EventListener :
public RE::BSTEventSink<RE::TESActorLocationChangeEvent>, public RE::BSTEventSink<RE::TESContainerChangedEvent>,
public RE::BSTEventSink<RE::TESContainerChangedEvent> public RE::BSTEventSink<RE::TESCellFullyLoadedEvent>,
public RE::BSTEventSink<RE::BGSActorCellEvent>,
public RE::BSTEventSink<RE::MenuOpenCloseEvent>
{ {
public: public:
~EventListener() = default; ~EventListener() = default;
@ -15,13 +17,23 @@ public:
static void Install(); static void Install();
auto ProcessEvent( auto ProcessEvent(
const RE::TESActorLocationChangeEvent* a_event, const RE::TESContainerChangedEvent* a_event,
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource) RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
-> RE::BSEventNotifyControl override; -> RE::BSEventNotifyControl override;
auto ProcessEvent( auto ProcessEvent(
const RE::TESContainerChangedEvent* a_event, const RE::TESCellFullyLoadedEvent* a_event,
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource) RE::BSTEventSource<RE::TESCellFullyLoadedEvent>* a_eventSource)
-> RE::BSEventNotifyControl override;
auto ProcessEvent(
const RE::BGSActorCellEvent* a_event,
RE::BSTEventSource<RE::BGSActorCellEvent>* a_eventSource)
-> RE::BSEventNotifyControl override;
auto ProcessEvent(
const RE::MenuOpenCloseEvent* a_event,
RE::BSTEventSource<RE::MenuOpenCloseEvent>* a_eventSource)
-> RE::BSEventNotifyControl override; -> RE::BSEventNotifyControl override;
private: private:

@ -15,7 +15,7 @@ namespace {
stl::report_and_fail("Failed to find standard logging directory"sv); stl::report_and_fail("Failed to find standard logging directory"sv);
} }
*path /= "ArtifactTrackerFunctions.log"sv; *path /= "ArtifactTracker.log"sv;
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true); auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true);
auto log = std::make_shared<spdlog::logger>("global log"s, std::move(sink)); auto log = std::make_shared<spdlog::logger>("global log"s, std::move(sink));
@ -32,6 +32,8 @@ namespace {
GetMessagingInterface()->RegisterListener([](MessagingInterface::Message* message) { GetMessagingInterface()->RegisterListener([](MessagingInterface::Message* message) {
if (message->type == MessagingInterface::kDataLoaded) { if (message->type == MessagingInterface::kDataLoaded) {
ArtifactTracker::Init(); ArtifactTracker::Init();
} else if (message->type == MessagingInterface::kPostLoadGame) {
ArtifactTracker::OnGameLoad();
} }
}); });
} }

@ -9,7 +9,7 @@ namespace Papyrus {
using StackID = RE::VMStackID; using StackID = RE::VMStackID;
using Severity = RE::BSScript::ErrorLogger::Severity; using Severity = RE::BSScript::ErrorLogger::Severity;
inline constexpr auto script = "ETR_Functions"sv; inline constexpr auto script = "ArtifactTrackerPlayer"sv;
bool Bind(VM* a_vm); bool Bind(VM* a_vm);
} }

@ -1,164 +1,91 @@
#pragma once #pragma once
#include "ArtifactTracker.h" #include "ArtifactTracker.h"
#include "Util.h"
namespace Papyrus::PapyrusFunctions namespace Papyrus::PapyrusFunctions
{ {
inline std::int32_t AddAllFormsToList(RE::StaticFunctionTag*, constexpr bool IsLoaded(RE::StaticFunctionTag*)
RE::BGSListForm* a_targetList,
short a_formType,
RE::BGSListForm* a_storedList,
RE::BGSListForm* a_foundList)
{ {
const auto formType = static_cast<RE::FormType>(a_formType); return ArtifactTracker::g_bLoaded;
}
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();
}
for (const auto& form : dataHandler->GetFormArray(formType)) { inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*)
if (!form || !form->GetPlayable()) { {
continue; return ArtifactTracker::g_cellStorage;
} }
if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) {
continue;
}
a_targetList->AddForm(form);
}
}
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*, // From po3's Papyrus Extender
RE::TESForm* a_refOrList, inline std::vector<RE::Actor*> GetPlayerFollowers(RE::StaticFunctionTag*)
RE::BGSListForm* a_targetList,
RE::BGSListForm* a_excludeList = NULL)
{ {
if (!a_refOrList) { std::vector<RE::Actor*> result;
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;
}
if (a_refOrList->Is(RE::FormType::FormList)) { if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) {
a_refOrList->As<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) { for (auto& actorHandle : processLists->highActorHandles) {
const auto refrItem = a_exform.As<RE::TESObjectREFR>(); if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) {
if (refrItem) { result.push_back(actor.get());
AddArtifactsToList(a_vm, a_stackID, nullptr, refrItem, a_targetList, a_excludeList);
} }
return true;
});
return a_targetList->forms.size();
}
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>();
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*, inline RE::TESObjectREFR* GetCurrentContainer(RE::StaticFunctionTag*)
RE::TESObjectREFR* a_ref,
RE::BGSListForm* a_refList,
RE::TESBoundObject* a_objectToCreate,
bool a_autoCreate = true)
{ {
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*, inline void RescanStoredArtifacts(RE::StaticFunctionTag*)
RE::TESObjectREFR* a_cellStorage,
RE::BGSListForm* a_excludeContainers)
{ {
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) { inline void RescanFoundArtifacts(RE::StaticFunctionTag*)
return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); {
}); ListRevert(ArtifactTracker::g_listFound);
ArtifactTracker::AddRefArtifactsToList(RE::PlayerCharacter::GetSingleton(), ArtifactTracker::g_listFound, ArtifactTracker::g_listStored);
for (const auto& item : inv) {
if (item.second.first > 0) {
a_targetList->AddForm(item.first);
}
}
} 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*, inline void RescanNewArtifacts(RE::StaticFunctionTag*)
RE::TESBoundObject* a_item)
{ {
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) inline void Bind(VM& a_vm)
{ {
BIND(AddAllFormsToList); BIND(IsLoaded);
logger::info("Registered AddAllFormsToList"sv); logger::info("Registered IsLoaded"sv);
BIND(AddArtifactsToList); BIND(RescanStoredArtifacts);
logger::info("Registered AddArtifactsToList"sv); logger::info("Registered RescanStoredArtifacts"sv);
BIND(AddArtifactsFromFollowersToList); BIND(RescanFoundArtifacts);
logger::info("Registered AddArtifactsFromFollowersToList"sv); logger::info("Registered RescanFoundArtifacts"sv);
BIND(RescanNewArtifacts);
logger::info("Registered RescanNewArtifacts"sv);
BIND(GetCellStorage); BIND(GetCellStorage);
logger::info("Registered GetCellStorage"sv); logger::info("Registered GetCellStorage"sv);
BIND(SyncCellStorage); BIND(SyncCellStorage);
logger::info("Registered SyncCellStorage"sv); logger::info("Registered SyncCellStorage"sv);
BIND(OnItemPickup); BIND(GetPlayerFollowers);
logger::info("Registered OnItemPickup"sv); logger::info("Registered GetPlayerFollowers"sv);
BIND(GetCurrentContainer);
logger::info("Registered GetCurrentContainer"sv);
} }
} }

@ -1,78 +1,66 @@
#pragma once #pragma once
void RemoveListItem(RE::BGSListForm* a_List, RE::TESForm* a_form) inline void ListRemoveItem(RE::BGSListForm* a_List, RE::TESForm* a_form)
{ {
using func_t = decltype(&RemoveListItem); using func_t = decltype(&ListRemoveItem);
REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) }; REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) };
return func(a_List, a_form); return func(a_List, a_form);
} }
void RevertList(RE::TESForm* a_form) inline void ListRevert(RE::BGSListForm* a_form)
{ {
using func_t = decltype(&RevertList); using func_t = decltype(&ListRevert);
REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) }; REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) };
return func(a_form); return func(a_form);
} }
inline bool RefHasItem(RE::TESObjectREFR* a_ref, RE::TESForm* a_item) inline bool RefHasItem(RE::TESForm* a_refOrList, RE::FormID a_formID)
{ {
if (!a_ref || !a_item) { if (!a_refOrList || !a_formID) {
SKSE::log::warn("Invalid arguments in RefHasItem"); SKSE::log::warn("Invalid arguments in RefHasItem");
return false; return false;
} }
auto invChanges = a_ref->GetInventoryChanges(); const auto refr = a_refOrList->As<RE::TESObjectREFR>();
if (invChanges && invChanges->entryList) {
for (auto& entry : *invChanges->entryList) { if (refr) {
if (entry && entry->object && entry->object->formID == a_item->formID) { const auto invChanges = refr->GetInventoryChanges();
return entry->countDelta > 0; if (invChanges && invChanges->entryList) {
} for (auto& entry : *invChanges->entryList) {
} if (entry && entry->object && entry->object->formID == a_formID) {
} return entry->countDelta > 0;
return false; }
} }
}
inline std::uint32_t GetItemCountInList(RE::BGSListForm* a_containerList, RE::TESBoundObject* a_form) return false;
{ }
if (!a_containerList || !a_form) {
SKSE::log::warn("Invalid arguments in GetItemCountInList"); const auto list = a_refOrList->As<RE::BGSListForm>();
return 0;
} if (list) {
for (const auto& ref : list->forms) {
std::uint32_t iResult = 0; if (ref && RefHasItem(ref, a_formID)) {
return true;
a_containerList->ForEachForm([&](RE::TESForm& a_container) { }
const auto refrItem = a_container.As<RE::TESObjectREFR>(); }
if (refrItem) { }
const auto inv = refrItem->GetInventory([&](RE::TESBoundObject& a_object) -> bool {
return a_form->formID == a_object.formID; return false;
}); }
const auto it = inv.find(a_form);
iResult += it != inv.end() ? it->second.first : 0; inline bool FollowersHaveItem(RE::TESForm* a_form)
} {
return true; if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) {
}); for (auto& actorHandle : processLists->highActorHandles) {
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) {
return iResult;
} if (RefHasItem(actor->As<RE::TESObjectREFR>(), a_form->formID)) {
return true;
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; return false;
});
const auto it = inv.find(a_form);
const auto iCount = it != inv.end() ? it->second.first : 0;
if (iCount > 0) {
return true;
}
}
}
}
return false;
} }

@ -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

@ -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

@ -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

@ -611,6 +611,9 @@ Function UpdateBooks()
UnBlockActivate() ; Allow the player to mess with them UnBlockActivate() ; Allow the player to mess with them
GoToState("") ; Now allow books to be updated again GoToState("") ; Now allow books to be updated again
; Artifact Tracker
SendModEvent("AT_HomeInventoryUpdate")
EndFunction EndFunction
State PlacingBooks State PlacingBooks

Loading…
Cancel
Save