1

Moved almost everything to the DLL

This commit is contained in:
Eddoursul 2022-06-30 12:31:01 +02:00
parent a7229e0bea
commit f312441f13
20 changed files with 845 additions and 762 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -5,137 +5,277 @@
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;
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()
{
EventListener::Install();
g_bLoaded = false;
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 (!dataHandler) {
SKSE::log::warn("Failed to call RE::TESDataHandler::GetSingleton()");
return;
}
if (!persistentStorage) {
SKSE::log::warn("persistentStorage is empty");
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
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;
g_artifactMap[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;
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)
{
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;
if (!a_form) {
return false;
}
RE::TESObjectCELL* cell = a_ref->GetParentCell();
const auto formType = a_form->GetFormType();
a_refList->ForEachForm([&result, &cell, &a_objectToCreate](RE::TESForm& a_exform) {
const auto ref = a_exform.As<RE::TESObjectREFR>();
if (formType == RE::FormType::Armor || formType == RE::FormType::Weapon || formType == RE::FormType::Book || formType == RE::FormType::Misc) {
return g_artifactMap.contains(a_form->formID);
}
if (ref && ref->GetParentCell() == cell && ref->GetBaseObject()->formID == a_objectToCreate->formID) {
result = ref;
return false;
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;
});
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)
void SetContainerMode(bool bOpening)
{
if (!a_cellStorage || !a_excludeContainers) {
SKSE::log::warn("Invalid arguments in SyncCellStorage");
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;
}
std::unordered_map<RE::FormID, bool> cellItems;
RE::TESObjectREFR* cellStorage = nullptr;
const auto cell = a_cellStorage->GetParentCell();
const auto inv = a_cellStorage->GetInventory();
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;
});
for (const RE::NiPointer<RE::TESObjectREFR> a_ref : cell->references) {
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 (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
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;
}
@ -145,10 +285,15 @@ namespace ArtifactTracker
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);
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);
}
}
}
@ -157,11 +302,7 @@ namespace ArtifactTracker
continue;
}
if (a_ref->IsMarkedForDeletion()) {
SKSE::log::info("found marked for deletion");
}
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) {
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion() || a_ref->formID == skipRefID) {
continue;
}
@ -169,153 +310,197 @@ namespace ArtifactTracker
continue;
}
cellItems[baseObj->formID] = true;
cellItems.insert(baseObj->formID);
if (!IsArtifact(baseObj)) {
continue;
}
if (inv.find(baseObj) == inv.end()) {
a_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr);
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)) {
a_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr);
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 OnItemPickup(RE::TESBoundObject* form)
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event, RE::TESForm* form)
{
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true);
if (a_event->newContainer == 0x14) {
SyncCellStorage(cellStorage, persistentStorage);
if (a_event->oldContainer) {
if (!RefHasItem(cellStorage, form) && !GetItemCountInList(persistentStorage, form)) {
RemoveListItem(listStored, form);
listFound->AddForm(form);
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 OnContainerChanged(const RE::TESContainerChangedEvent* a_event)
void AddRefArtifactsToList(RE::TESForm* a_refOrList, RE::BGSListForm* a_targetList, RE::BGSListForm* a_excludeList)
{
if (a_event->newContainer == 0x14) {
if (!a_refOrList || !a_targetList) {
SKSE::log::warn("Invalid arguments in AddRefArtifactsToList");
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;
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);
}
} 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");
}
}
}

View File

@ -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 OnItemPickup(RE::TESBoundObject* form);
void SetContainerMode(bool bOpening);
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event);
void OnCellEnter(RE::FormID a_formID);
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);
}

View File

@ -1,7 +1,6 @@
#include "EventListener.h"
#include "ArtifactTracker.h"
auto EventListener::GetSingleton() -> EventListener*
{
static EventListener singleton{};
@ -10,21 +9,8 @@ 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());
}
auto EventListener::ProcessEvent(
const RE::TESActorLocationChangeEvent* a_event,
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource)
-> RE::BSEventNotifyControl
{
if (a_event->actor && a_event->actor->IsPlayerRef()) {
ArtifactTracker::SetHomeLocation(a_event->newLoc);
}
return RE::BSEventNotifyControl::kContinue;
RE::PlayerCharacter::GetSingleton()->AddEventSink<RE::BGSActorCellEvent>(EventListener::GetSingleton());
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
}
auto EventListener::ProcessEvent(
@ -32,7 +18,50 @@ auto EventListener::ProcessEvent(
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
-> RE::BSEventNotifyControl
{
ArtifactTracker::OnContainerChanged(a_event);
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::BGSActorCellEvent* a_event,
RE::BSTEventSource<RE::BGSActorCellEvent>* a_eventSource)
-> RE::BSEventNotifyControl
{
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;
}

View File

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

View File

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

View File

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

View File

@ -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();
}
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);
}
}
return a_targetList->forms.size();
return ArtifactTracker::g_bLoaded;
}
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)
inline RE::TESObjectREFR* GetCellStorage(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;
}
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);
}
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 ArtifactTracker::g_cellStorage;
}
inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*,
RE::TESObjectREFR* a_ref,
RE::BGSListForm* a_refList,
RE::TESBoundObject* a_objectToCreate,
bool a_autoCreate = true)
inline void SyncCellStorage(RE::StaticFunctionTag*, RE::FormID skipRefID = NULL)
{
return ArtifactTracker::GetCellStorage(a_ref, a_refList, a_objectToCreate, a_autoCreate);
ArtifactTracker::SyncCellStorage(skipRefID);
}
inline void SyncCellStorage(RE::StaticFunctionTag*,
RE::TESObjectREFR* a_cellStorage,
RE::BGSListForm* a_excludeContainers)
// From po3's Papyrus Extender
inline std::vector<RE::Actor*> GetPlayerFollowers(RE::StaticFunctionTag*)
{
ArtifactTracker::SyncCellStorage(a_cellStorage, a_excludeContainers);
}
std::vector<RE::Actor*> result;
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);
}
}
result.push_back(actor.get());
}
}
}
return a_targetList->forms.size();
return result;
}
inline void OnItemPickup(RE::StaticFunctionTag*,
RE::TESBoundObject* a_item)
inline RE::TESObjectREFR* GetCurrentContainer(RE::StaticFunctionTag*)
{
ArtifactTracker::OnItemPickup(a_item);
const auto handle = RE::ContainerMenu::GetTargetRefHandle();
const auto refr = RE::TESObjectREFR::LookupByHandle(handle);
return refr ? refr.get() : nullptr;
}
inline void RescanStoredArtifacts(RE::StaticFunctionTag*)
{
ListRevert(ArtifactTracker::g_listStored);
ArtifactTracker::AddRefArtifactsToList(ArtifactTracker::g_persistentStorage, ArtifactTracker::g_listStored);
}
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);
}
}
inline void RescanNewArtifacts(RE::StaticFunctionTag*)
{
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);
}
}

View File

@ -1,75 +1,63 @@
#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) };
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) };
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");
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;
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 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)
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()) {
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) {
if (RefHasItem(actor->As<RE::TESObjectREFR>(), a_form->formID)) {
return true;
}
}
}
}

View File

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

View File

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

View File

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

View File

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