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
########################################################################################################################
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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_precompile_headers(${PROJECT_NAME}
PRIVATE

@ -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<RE::FormID, RE::TESObjectMISC*> validMisc;
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks;
void Init()
{
EventListener::Install();
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>();
listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp");
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");
homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(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<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) {
if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) {
validBooks[form->formID] = form;
}
}
RE::BGSListForm* excludeKeywords = dataHandler->LookupForm<RE::BGSListForm>(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectMISC>()) {
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<RE::TESObjectREFR>();
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<RE::FormID, bool> cellItems;
const auto cell = a_cellStorage->GetParentCell();
const auto inv = a_cellStorage->GetInventory();
for (const RE::NiPointer<RE::TESObjectREFR> 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<RE::TESNPC>()->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<RE::TESBoundObject>(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<RE::TESBoundObject>(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<RE::TESBoundObject>())) {
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<RE::TESObjectREFR>(a_event->newContainer);
if (!targetContainer || targetContainer->GetParentCell() != RE::PlayerCharacter::GetSingleton()->GetParentCell()) {
return;
}
if (targetContainer->GetBaseObject()->Is(RE::FormType::NPC) && targetContainer->GetBaseObject()->As<RE::TESNPC>()->GetRace()->formID != 0x0010760A) {
return;
}
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true);
cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), 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<RE::FormID, RE::TESForm*> g_artifactMap;
std::unordered_map<RE::FormID, RE::TESObjectREFR*> 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<RE::TESBoundObject>();
g_listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp");
g_listStored = dataHandler->LookupForm<RE::BGSListForm>(0x805, "Artifact Tracker.esp");
g_listFound = dataHandler->LookupForm<RE::BGSListForm>(0x806, "Artifact Tracker.esp");
g_persistentStorage = dataHandler->LookupForm<RE::BGSListForm>(0x807, "Artifact Tracker.esp");
g_homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xFC1A3, "Skyrim.esm");
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
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<RE::TESObjectBOOK>()) {
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<RE::TESObjectREFR>();
}
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<RE::MenuOpenCloseEvent>(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<RE::MenuOpenCloseEvent>(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<RE::TESNPC>()->GetRace()->formID == 0x0010760A);
}
void OnCellEnter(RE::FormID a_formID)
{
RE::TESObjectCELL* cell = RE::TESForm::LookupByID<RE::TESObjectCELL>(a_formID);
if (!cell || !cell->IsInteriorCell()) {
if (IsHome()) {
RE::ScriptEventSourceHolder::GetSingleton()->RemoveEventSink<RE::TESCellFullyLoadedEvent>(EventListener::GetSingleton());
ToggleHomeMode(nullptr);
}
return;
}
RE::TESObjectREFR* cellStorage = nullptr;
g_persistentStorage->ForEachForm([&](RE::TESForm& a_form) {
const auto refr = a_form.As<RE::TESObjectREFR>();
if (refr && refr->GetParentCell()->formID == a_formID) {
cellStorage = refr;
return false;
}
return true;
});
ToggleHomeMode(cellStorage);
if (!cellStorage) {
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESCellFullyLoadedEvent>(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<RE::FormID> 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<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
{
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<RE::FormID, RE::TESObjectMISC*> validMisc;
extern std::unordered_map<RE::FormID, RE::TESObjectBOOK*> 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<RE::FormID, RE::TESForm*> g_artifactMap;
extern std::unordered_map<RE::FormID, RE::TESObjectREFR*> 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);
}

@ -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<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
eventSource->AddEventSink<RE::TESActorLocationChangeEvent>(EventListener::GetSingleton());
RE::PlayerCharacter::GetSingleton()->AddEventSink<RE::BGSActorCellEvent>(EventListener::GetSingleton());
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
}
auto EventListener::ProcessEvent(
const RE::TESActorLocationChangeEvent* a_event,
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource)
const RE::TESContainerChangedEvent* a_event,
RE::BSTEventSource<RE::TESContainerChangedEvent>* 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<RE::TESContainerChangedEvent>* a_eventSource)
const RE::BGSActorCellEvent* a_event,
RE::BSTEventSource<RE::BGSActorCellEvent>* 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;
}
}
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
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:
~EventListener() = default;
@ -15,13 +17,23 @@ public:
static void Install();
auto ProcessEvent(
const RE::TESActorLocationChangeEvent* a_event,
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource)
const RE::TESContainerChangedEvent* a_event,
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
-> RE::BSEventNotifyControl override;
auto ProcessEvent(
const RE::TESContainerChangedEvent* a_event,
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
const RE::TESCellFullyLoadedEvent* a_event,
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;
private:

@ -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<spdlog::sinks::basic_file_sink_mt>(path->string(), true);
auto log = std::make_shared<spdlog::logger>("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();
}
});
}

@ -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);
}

@ -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<RE::FormType>(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<RE::Actor*> 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<RE::Actor*> result;
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) {
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<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*,
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);
}
}

@ -1,78 +1,66 @@
#pragma once
void RemoveListItem(RE::BGSListForm* a_List, RE::TESForm* a_form)
{
using func_t = decltype(&RemoveListItem);
REL::Relocation<func_t> 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_t> 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<RE::TESObjectREFR>();
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_t> 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_t> 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<RE::TESObjectREFR>();
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<RE::BGSListForm>();
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<RE::TESObjectREFR>(), a_form->formID)) {
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
GoToState("") ; Now allow books to be updated again
; Artifact Tracker
SendModEvent("AT_HomeInventoryUpdate")
EndFunction
State PlacingBooks

Loading…
Cancel
Save