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 ## Define project
######################################################################################################################## ########################################################################################################################
project( project(
ArtifactTrackerFunctions ArtifactTracker
VERSION 1.0.0 VERSION 1.0.0
DESCRIPTION "Eddoursul's Artifact Tracker" DESCRIPTION "Eddoursul's Artifact Tracker"
LANGUAGES CXX) LANGUAGES CXX)
@ -51,7 +51,7 @@ target_include_directories(${PROJECT_NAME}
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME}
PUBLIC PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>) $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_precompile_headers(${PROJECT_NAME} target_precompile_headers(${PROJECT_NAME}
PRIVATE PRIVATE

View File

@ -5,137 +5,277 @@
namespace ArtifactTracker namespace ArtifactTracker
{ {
RE::TESGlobal* notifyNewArtifact; bool g_bLoaded;
RE::TESBoundObject* cellContainer; bool g_bHomeContainer;
RE::BGSListForm* listNew; RE::TESBoundObject* g_cellContainer;
RE::BGSListForm* listStored; RE::BGSListForm* g_listNew;
RE::BGSListForm* listFound; RE::BGSListForm* g_listStored;
RE::BGSListForm* persistentStorage; RE::BGSListForm* g_listFound;
RE::BGSKeyword* homeKeyword; RE::BGSListForm* g_persistentStorage;
bool bAtHome; RE::BGSKeyword* g_homeKeyword;
std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc; std::unordered_map<RE::FormID, RE::TESForm*> g_artifactMap;
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks; std::unordered_map<RE::FormID, RE::TESObjectREFR*> g_persistentMap;
RE::TESObjectREFR* g_cellStorage;
void Init() void Init()
{ {
EventListener::Install(); g_bLoaded = false;
const auto dataHandler = RE::TESDataHandler::GetSingleton(); const auto dataHandler = RE::TESDataHandler::GetSingleton();
notifyNewArtifact = dataHandler->LookupForm<RE::TESGlobal>(0x809, "Artifact Tracker.esp"); if (!dataHandler) {
cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>(); SKSE::log::warn("Failed to call RE::TESDataHandler::GetSingleton()");
return;
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) { g_cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>();
SKSE::log::warn("persistentStorage is empty");
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 // Preloading item lists
const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) { for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) {
if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { 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::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::TESObjectMISC>()) { for (const auto& form : dataHandler->GetFormArray(RE::FormType::Weapon)) {
if (form->GetPlayable() && !form->IsGold() && !form->IsLockpick() && !form->HasKeywordInList(excludeKeywords, false)) { if (form->GetPlayable()) {
validMisc[form->formID] = form; 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) bool IsArtifact(RE::TESForm* a_form)
{ {
switch (a_form->GetFormType()) { if (!a_form) {
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 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; return true;
}); });
if (!result && a_autoCreate) {
result = a_ref->PlaceObjectAtMe(a_objectToCreate, true).get();
result->Disable();
a_refList->AddForm(result);
} }
if (!result) { void SetContainerMode(bool bOpening)
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) { if (bOpening) {
SKSE::log::warn("Invalid arguments in SyncCellStorage");
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; return;
} }
std::unordered_map<RE::FormID, bool> cellItems; RE::TESObjectREFR* cellStorage = nullptr;
const auto cell = a_cellStorage->GetParentCell(); g_persistentStorage->ForEachForm([&](RE::TESForm& a_form) {
const auto inv = a_cellStorage->GetInventory(); 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(); 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 (IsValidContainer(a_ref.get())) {
if (a_excludeContainers->HasForm(a_ref->formID) || baseObj->formID == 0xDC9E7) { // skip persistent and PlayerBookShelfContainer if (g_cellContainer->formID == baseObj->formID || baseObj->formID == 0xDC9E7 || g_persistentMap.contains(a_ref->formID)) { // skip persistent and PlayerBookShelfContainer
continue; continue;
} }
@ -145,10 +285,15 @@ namespace ArtifactTracker
for (const auto& [item, data] : contInv) { for (const auto& [item, data] : contInv) {
if (data.first > 0) { if (data.first > 0) {
cellItems[item->formID] = true; cellItems.insert(item->formID);
if (inv.find(item) == inv.end()) {
if (IsArtifact(item)) { if (IsArtifact(item)) {
a_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr); 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; continue;
} }
if (a_ref->IsMarkedForDeletion()) { if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion() || a_ref->formID == skipRefID) {
SKSE::log::info("found marked for deletion");
}
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) {
continue; continue;
} }
@ -169,153 +310,197 @@ namespace ArtifactTracker
continue; continue;
} }
cellItems[baseObj->formID] = true; cellItems.insert(baseObj->formID);
if (!IsArtifact(baseObj)) { if (!IsArtifact(baseObj)) {
continue; continue;
} }
if (inv.find(baseObj) == inv.end()) { 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) { for (const auto& [item, data] : inv) {
const auto& [count, entry] = data; const auto& [count, entry] = data;
if (count > 0 && !cellItems.contains(item->formID)) { 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(); 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);
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) { if (a_event->newContainer == 0x14) {
return;
RE::TESBoundObject* form = RE::TESForm::LookupByID<RE::TESBoundObject>(a_event->baseObj);
if (!form || !IsArtifact(form)) { 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; return;
} }
if (listFound->HasForm(form) || listStored->HasForm(form)) { } 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; return;
} }
if (listNew->HasForm(form)) { // 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.
RemoveListItem(listNew, form); if (g_listStored->HasForm(form) || g_listFound->HasForm(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; return;
} }
if (!a_event->newContainer) { // It's a new item, move it to found
if (bAtHome && a_event->reference) { // dropped at home ListRemoveItem(g_listNew, form);
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); g_listFound->AddForm(form);
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; return;
} }
if (listStored->HasForm(form)) { if (a_event->oldContainer != 0x14) {
return; return;
} }
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form)) { // Items moved from player's inventory
if (listFound->HasForm(form)) {
RemoveListItem(listFound, form); 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);
} }
listNew->AddForm(form); ListRemoveItem(g_listFound, form);
RE::DebugNotification("added to new"); 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; return;
} }
bool bPersistent = false; if (g_persistentMap.contains(a_event->newContainer)) { // moved to a persistent container
for (const auto& ref : persistentStorage->forms) {
if (ref && ref->formID == a_event->newContainer) { if (!g_listStored->HasForm(form)) {
bPersistent = true; ListRemoveItem(g_listFound, form);
break; 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);
} }
} }
if (!bPersistent && !bAtHome) { } else if (g_listFound->HasForm(form) && !RefHasItem(RE::PlayerCharacter::GetSingleton(), form->formID) && !FollowersHaveItem(form)) {
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form->As<RE::TESBoundObject>())) { ListRemoveItem(g_listFound, form);
if (listFound->HasForm(form)) { g_listNew->AddForm(form);
RemoveListItem(listFound, form);
} }
listNew->AddForm(form);
RE::DebugNotification("added to new");
} }
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; return;
} }
if (listStored->HasForm(form)) { 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; return;
} }
if (!bPersistent && bAtHome) { const auto containerRef = a_refOrList->As<RE::TESObjectREFR>();
RE::TESObjectREFR* targetContainer = RE::TESForm::LookupByID<RE::TESObjectREFR>(a_event->newContainer);
if (!targetContainer || targetContainer->GetParentCell() != RE::PlayerCharacter::GetSingleton()->GetParentCell()) { if (!containerRef) {
SKSE::log::warn("containerRef in AddRefArtifactsToList is not a reference");
return; return;
} }
if (targetContainer->GetBaseObject()->Is(RE::FormType::NPC) && targetContainer->GetBaseObject()->As<RE::TESNPC>()->GetRace()->formID != 0x0010760A) { const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) {
return; return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform));
} });
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); for (const auto& item : inv) {
cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), nullptr, 1, nullptr); if (item.second.first > 0) {
RE::DebugNotification("updated cellStorage"); a_targetList->AddForm(item.first);
} }
if (listFound->HasForm(form)) {
RemoveListItem(listFound, form);
}
listStored->AddForm(form);
RE::DebugNotification("added to stored");
} }
} }
} }

View File

@ -2,28 +2,37 @@
namespace ArtifactTracker namespace ArtifactTracker
{ {
extern RE::TESGlobal* notifyNewArtifact; extern bool g_bLoaded;
extern RE::TESBoundObject* cellContainer; extern bool g_bHomeContainer;
extern RE::BGSListForm* listNew; extern RE::TESBoundObject* g_cellContainer;
extern RE::BGSListForm* listStored; extern RE::BGSListForm* g_listNew;
extern RE::BGSListForm* listFound; extern RE::BGSListForm* g_listStored;
extern RE::BGSListForm* persistentStorage; extern RE::BGSListForm* g_listFound;
extern RE::BGSKeyword* homeKeyword; extern RE::BGSListForm* g_persistentStorage;
extern bool bAtHome; extern RE::BGSKeyword* g_homeKeyword;
extern std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc; extern std::unordered_map<RE::FormID, RE::TESForm*> g_artifactMap;
extern std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks; extern std::unordered_map<RE::FormID, RE::TESObjectREFR*> g_persistentMap;
extern RE::TESObjectREFR* g_cellStorage;
void Init(); void Init();
bool IsArtifact(RE::TESForm* a_item); bool IsArtifact(RE::TESForm* a_item);
bool SetHomeLocation(RE::BGSLocation* a_location); RE::TESForm* GetArtifactByID(RE::FormID a_formID);
RE::TESObjectREFR* GetCellStorage(RE::TESObjectREFR* a_ref, RE::BGSListForm* a_refList, RE::TESBoundObject* a_objectToCreate, bool a_autoCreate = true); bool IsHome();
void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers); void OnGameLoad();
void 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 "EventListener.h"
#include "ArtifactTracker.h" #include "ArtifactTracker.h"
auto EventListener::GetSingleton() -> EventListener* auto EventListener::GetSingleton() -> EventListener*
{ {
static EventListener singleton{}; static EventListener singleton{};
@ -10,21 +9,8 @@ auto EventListener::GetSingleton() -> EventListener*
void EventListener::Install() void EventListener::Install()
{ {
const auto eventSource = RE::ScriptEventSourceHolder::GetSingleton(); RE::PlayerCharacter::GetSingleton()->AddEventSink<RE::BGSActorCellEvent>(EventListener::GetSingleton());
eventSource->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton()); RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
eventSource->AddEventSink<RE::TESActorLocationChangeEvent>(EventListener::GetSingleton());
}
auto EventListener::ProcessEvent(
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;
} }
auto EventListener::ProcessEvent( auto EventListener::ProcessEvent(
@ -32,7 +18,50 @@ auto EventListener::ProcessEvent(
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource) RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
-> RE::BSEventNotifyControl -> 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; return RE::BSEventNotifyControl::kContinue;
} }

View File

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

View File

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

View File

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

View File

@ -1,164 +1,91 @@
#pragma once #pragma once
#include "ArtifactTracker.h" #include "ArtifactTracker.h"
#include "Util.h"
namespace Papyrus::PapyrusFunctions namespace Papyrus::PapyrusFunctions
{ {
inline std::int32_t AddAllFormsToList(RE::StaticFunctionTag*, constexpr bool IsLoaded(RE::StaticFunctionTag*)
RE::BGSListForm* a_targetList,
short a_formType,
RE::BGSListForm* a_storedList,
RE::BGSListForm* a_foundList)
{ {
const auto formType = static_cast<RE::FormType>(a_formType); return ArtifactTracker::g_bLoaded;
switch (formType) {
case RE::FormType::Book:
for (auto const& item : ArtifactTracker::validBooks) {
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) {
a_targetList->AddForm(item.second);
}
}
break;
case RE::FormType::Misc:
for (auto const& item : ArtifactTracker::validMisc) {
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) {
a_targetList->AddForm(item.second);
}
}
break;
case RE::FormType::None:
break;
default:
const auto dataHandler = RE::TESDataHandler::GetSingleton();
if (!dataHandler) {
return a_targetList->forms.size();
} }
for (const auto& form : dataHandler->GetFormArray(formType)) { inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*)
if (!form || !form->GetPlayable()) {
continue;
}
if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) {
continue;
}
a_targetList->AddForm(form);
}
}
return a_targetList->forms.size();
}
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)
{ {
if (!a_refOrList) { return ArtifactTracker::g_cellStorage;
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)) { inline void SyncCellStorage(RE::StaticFunctionTag*, RE::FormID skipRefID = NULL)
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();
}
inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*,
RE::TESObjectREFR* a_ref,
RE::BGSListForm* a_refList,
RE::TESBoundObject* a_objectToCreate,
bool a_autoCreate = true)
{ {
return ArtifactTracker::GetCellStorage(a_ref, a_refList, a_objectToCreate, a_autoCreate); ArtifactTracker::SyncCellStorage(skipRefID);
} }
inline void SyncCellStorage(RE::StaticFunctionTag*, // From po3's Papyrus Extender
RE::TESObjectREFR* a_cellStorage, inline std::vector<RE::Actor*> GetPlayerFollowers(RE::StaticFunctionTag*)
RE::BGSListForm* a_excludeContainers)
{ {
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) { if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) {
for (auto& actorHandle : processLists->highActorHandles) { for (auto& actorHandle : processLists->highActorHandles) {
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) {
result.push_back(actor.get());
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);
}
}
} }
} }
} }
return a_targetList->forms.size(); return result;
} }
inline void OnItemPickup(RE::StaticFunctionTag*, inline RE::TESObjectREFR* GetCurrentContainer(RE::StaticFunctionTag*)
RE::TESBoundObject* a_item)
{ {
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) inline void Bind(VM& a_vm)
{ {
BIND(AddAllFormsToList); BIND(IsLoaded);
logger::info("Registered AddAllFormsToList"sv); logger::info("Registered IsLoaded"sv);
BIND(AddArtifactsToList); BIND(RescanStoredArtifacts);
logger::info("Registered AddArtifactsToList"sv); logger::info("Registered RescanStoredArtifacts"sv);
BIND(AddArtifactsFromFollowersToList); BIND(RescanFoundArtifacts);
logger::info("Registered AddArtifactsFromFollowersToList"sv); logger::info("Registered RescanFoundArtifacts"sv);
BIND(RescanNewArtifacts);
logger::info("Registered RescanNewArtifacts"sv);
BIND(GetCellStorage); BIND(GetCellStorage);
logger::info("Registered GetCellStorage"sv); logger::info("Registered GetCellStorage"sv);
BIND(SyncCellStorage); BIND(SyncCellStorage);
logger::info("Registered SyncCellStorage"sv); logger::info("Registered SyncCellStorage"sv);
BIND(OnItemPickup); BIND(GetPlayerFollowers);
logger::info("Registered OnItemPickup"sv); logger::info("Registered GetPlayerFollowers"sv);
BIND(GetCurrentContainer);
logger::info("Registered GetCurrentContainer"sv);
} }
} }

View File

@ -1,30 +1,33 @@
#pragma once #pragma once
void RemoveListItem(RE::BGSListForm* a_List, RE::TESForm* a_form) inline void ListRemoveItem(RE::BGSListForm* a_List, RE::TESForm* a_form)
{ {
using func_t = decltype(&RemoveListItem); using func_t = decltype(&ListRemoveItem);
REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) }; REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) };
return func(a_List, a_form); return func(a_List, a_form);
} }
void RevertList(RE::TESForm* a_form) inline void ListRevert(RE::BGSListForm* a_form)
{ {
using func_t = decltype(&RevertList); using func_t = decltype(&ListRevert);
REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) }; REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) };
return func(a_form); return func(a_form);
} }
inline bool RefHasItem(RE::TESObjectREFR* a_ref, RE::TESForm* a_item) inline bool RefHasItem(RE::TESForm* a_refOrList, RE::FormID a_formID)
{ {
if (!a_ref || !a_item) { if (!a_refOrList || !a_formID) {
SKSE::log::warn("Invalid arguments in RefHasItem"); SKSE::log::warn("Invalid arguments in RefHasItem");
return false; return false;
} }
auto invChanges = a_ref->GetInventoryChanges(); const auto refr = a_refOrList->As<RE::TESObjectREFR>();
if (refr) {
const auto invChanges = refr->GetInventoryChanges();
if (invChanges && invChanges->entryList) { if (invChanges && invChanges->entryList) {
for (auto& entry : *invChanges->entryList) { for (auto& entry : *invChanges->entryList) {
if (entry && entry->object && entry->object->formID == a_item->formID) { if (entry && entry->object && entry->object->formID == a_formID) {
return entry->countDelta > 0; return entry->countDelta > 0;
} }
} }
@ -32,44 +35,29 @@ inline bool RefHasItem(RE::TESObjectREFR* a_ref, RE::TESForm* a_item)
return false; return false;
} }
inline std::uint32_t GetItemCountInList(RE::BGSListForm* a_containerList, RE::TESBoundObject* a_form) const auto list = a_refOrList->As<RE::BGSListForm>();
{
if (!a_containerList || !a_form) {
SKSE::log::warn("Invalid arguments in GetItemCountInList");
return 0;
}
std::uint32_t iResult = 0; if (list) {
for (const auto& ref : list->forms) {
a_containerList->ForEachForm([&](RE::TESForm& a_container) { if (ref && RefHasItem(ref, a_formID)) {
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 true;
}); }
}
return iResult; }
}
return false;
inline bool FollowersHaveItem(RE::TESBoundObject* a_form) }
{
if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { inline bool FollowersHaveItem(RE::TESForm* a_form)
for (auto& actorHandle : processLists->highActorHandles) { {
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) {
const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_object) -> bool { for (auto& actorHandle : processLists->highActorHandles) {
return a_form->formID == a_object.formID; if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) {
});
const auto it = inv.find(a_form); if (RefHasItem(actor->As<RE::TESObjectREFR>(), a_form->formID)) {
const auto iCount = it != inv.end() ? it->second.first : 0; return true;
}
if (iCount > 0) {
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 UnBlockActivate() ; Allow the player to mess with them
GoToState("") ; Now allow books to be updated again GoToState("") ; Now allow books to be updated again
; Artifact Tracker
SendModEvent("AT_HomeInventoryUpdate")
EndFunction EndFunction
State PlacingBooks State PlacingBooks