parent
a7229e0bea
commit
f312441f13
20 changed files with 834 additions and 751 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.
Binary file not shown.
@ -1,321 +1,506 @@ |
|||||||
#include "ArtifactTracker.h" |
#include "ArtifactTracker.h" |
||||||
#include "BookCheck.h" |
#include "BookCheck.h" |
||||||
#include "EventListener.h" |
#include "EventListener.h" |
||||||
#include "Util.h" |
#include "Util.h" |
||||||
|
|
||||||
namespace ArtifactTracker |
namespace ArtifactTracker |
||||||
{ |
{ |
||||||
RE::TESGlobal* notifyNewArtifact; |
bool g_bLoaded; |
||||||
RE::TESBoundObject* cellContainer; |
bool g_bHomeContainer; |
||||||
RE::BGSListForm* listNew; |
RE::TESBoundObject* g_cellContainer; |
||||||
RE::BGSListForm* listStored; |
RE::BGSListForm* g_listNew; |
||||||
RE::BGSListForm* listFound; |
RE::BGSListForm* g_listStored; |
||||||
RE::BGSListForm* persistentStorage; |
RE::BGSListForm* g_listFound; |
||||||
RE::BGSKeyword* homeKeyword; |
RE::BGSListForm* g_persistentStorage; |
||||||
bool bAtHome; |
RE::BGSKeyword* g_homeKeyword; |
||||||
std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc; |
std::unordered_map<RE::FormID, RE::TESForm*> g_artifactMap; |
||||||
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks; |
std::unordered_map<RE::FormID, RE::TESObjectREFR*> g_persistentMap; |
||||||
|
RE::TESObjectREFR* g_cellStorage; |
||||||
void Init() |
|
||||||
{ |
void Init() |
||||||
EventListener::Install(); |
{ |
||||||
|
g_bLoaded = false; |
||||||
const auto dataHandler = RE::TESDataHandler::GetSingleton(); |
|
||||||
|
const auto dataHandler = RE::TESDataHandler::GetSingleton(); |
||||||
notifyNewArtifact = dataHandler->LookupForm<RE::TESGlobal>(0x809, "Artifact Tracker.esp"); |
|
||||||
cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>(); |
if (!dataHandler) { |
||||||
|
SKSE::log::warn("Failed to call RE::TESDataHandler::GetSingleton()"); |
||||||
listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp"); |
return; |
||||||
listStored = dataHandler->LookupForm<RE::BGSListForm>(0x805, "Artifact Tracker.esp"); |
} |
||||||
listFound = dataHandler->LookupForm<RE::BGSListForm>(0x806, "Artifact Tracker.esp"); |
|
||||||
persistentStorage = dataHandler->LookupForm<RE::BGSListForm>(0x807, "Artifact Tracker.esp"); |
g_cellContainer = dataHandler->LookupForm(0x800, "Artifact Tracker.esp")->As<RE::TESBoundObject>(); |
||||||
|
|
||||||
homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xFC1A3, "Skyrim.esm"); |
g_listNew = dataHandler->LookupForm<RE::BGSListForm>(0x803, "Artifact Tracker.esp"); |
||||||
|
g_listStored = dataHandler->LookupForm<RE::BGSListForm>(0x805, "Artifact Tracker.esp"); |
||||||
if (!cellContainer) { |
g_listFound = dataHandler->LookupForm<RE::BGSListForm>(0x806, "Artifact Tracker.esp"); |
||||||
SKSE::log::warn("cellContainer is empty"); |
g_persistentStorage = dataHandler->LookupForm<RE::BGSListForm>(0x807, "Artifact Tracker.esp"); |
||||||
} |
|
||||||
|
g_homeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xFC1A3, "Skyrim.esm"); |
||||||
if (!persistentStorage) { |
|
||||||
SKSE::log::warn("persistentStorage is empty"); |
const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
|
||||||
} |
RE::BGSListForm* excludeKeywords = dataHandler->LookupForm<RE::BGSListForm>(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords
|
||||||
|
|
||||||
// Preloading item lists
|
if (!g_cellContainer || !g_listNew || !g_listStored || !g_listFound || !g_persistentStorage || !g_homeKeyword || !recipeKeyword || !excludeKeywords) { |
||||||
const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
|
SKSE::log::warn("Failed to load data from Artifact Tracker.esp"); |
||||||
|
RE::DebugMessageBox("Failed to load data from Artifact Tracker.esp, the mod is disabled."); |
||||||
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) { |
return; |
||||||
if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { |
} |
||||||
validBooks[form->formID] = form; |
|
||||||
} |
// Preloading item lists
|
||||||
} |
|
||||||
|
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) { |
||||||
RE::BGSListForm* excludeKeywords = dataHandler->LookupForm<RE::BGSListForm>(0x801, "Artifact Tracker.esp"); // ETR_ExcludeMiscKeywords
|
if (form && !form->TeachesSpell() && (!form->HasKeyword(recipeKeyword) || BookCheck::IsBook(form))) { |
||||||
|
g_artifactMap[form->formID] = form; |
||||||
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectMISC>()) { |
} |
||||||
if (form->GetPlayable() && !form->IsGold() && !form->IsLockpick() && !form->HasKeywordInList(excludeKeywords, false)) { |
} |
||||||
validMisc[form->formID] = form; |
|
||||||
} |
for (const auto& form : dataHandler->GetFormArray(RE::FormType::Misc)) { |
||||||
} |
if (form->GetPlayable() && !form->HasKeywordInList(excludeKeywords, false)) { |
||||||
} |
g_artifactMap[form->formID] = form; |
||||||
|
} |
||||||
bool IsArtifact(RE::TESForm* a_form) |
} |
||||||
{ |
g_artifactMap.erase(0xA); // Lockpick
|
||||||
switch (a_form->GetFormType()) { |
g_artifactMap.erase(0xF); // Gold
|
||||||
case RE::FormType::Armor: |
|
||||||
return a_form->GetPlayable(); |
for (const auto& form : dataHandler->GetFormArray(RE::FormType::Weapon)) { |
||||||
case RE::FormType::Weapon: |
if (form->GetPlayable()) { |
||||||
return a_form->GetPlayable() && a_form->formID != 0x000001F4; |
g_artifactMap[form->formID] = form; |
||||||
case RE::FormType::Book: |
} |
||||||
return validBooks.contains(a_form->formID); |
} |
||||||
case RE::FormType::Misc: |
g_artifactMap.erase(0x1F4); // Unarmed
|
||||||
return validMisc.contains(a_form->formID); |
|
||||||
default: |
for (const auto& form : dataHandler->GetFormArray(RE::FormType::Armor)) { |
||||||
return false; |
if (form->GetPlayable()) { |
||||||
} |
g_artifactMap[form->formID] = form; |
||||||
} |
} |
||||||
|
} |
||||||
bool SetHomeLocation(RE::BGSLocation* a_location = NULL) |
|
||||||
{ |
OnGameLoad(); |
||||||
bAtHome = a_location && a_location->HasKeyword(homeKeyword); |
EventListener::Install(); |
||||||
return bAtHome; |
|
||||||
} |
g_bLoaded = true; |
||||||
|
} |
||||||
RE::TESObjectREFR* GetCellStorage( |
|
||||||
RE::TESObjectREFR* a_ref, |
bool IsArtifact(RE::TESForm* a_form) |
||||||
RE::BGSListForm* a_refList, |
{ |
||||||
RE::TESBoundObject* a_objectToCreate, |
if (!a_form) { |
||||||
bool a_autoCreate) |
return false; |
||||||
{ |
} |
||||||
RE::TESObjectREFR* result = NULL; |
|
||||||
|
const auto formType = a_form->GetFormType(); |
||||||
if (!a_ref || !a_refList || !a_objectToCreate) { |
|
||||||
SKSE::log::warn("Invalid arguments in GetCellStorage"); |
if (formType == RE::FormType::Armor || formType == RE::FormType::Weapon || formType == RE::FormType::Book || formType == RE::FormType::Misc) { |
||||||
return result; |
return g_artifactMap.contains(a_form->formID); |
||||||
} |
} |
||||||
|
|
||||||
RE::TESObjectCELL* cell = a_ref->GetParentCell(); |
return false; |
||||||
|
} |
||||||
a_refList->ForEachForm([&result, &cell, &a_objectToCreate](RE::TESForm& a_exform) { |
|
||||||
const auto ref = a_exform.As<RE::TESObjectREFR>(); |
RE::TESForm* GetArtifactByID(RE::FormID a_formID) |
||||||
|
{ |
||||||
if (ref && ref->GetParentCell() == cell && ref->GetBaseObject()->formID == a_objectToCreate->formID) { |
if (!a_formID) { |
||||||
result = ref; |
return nullptr; |
||||||
return false; |
} |
||||||
} |
|
||||||
|
const auto it = g_artifactMap.find(a_formID); |
||||||
return true; |
return it != g_artifactMap.end() ? it->second : nullptr; |
||||||
}); |
} |
||||||
|
|
||||||
if (!result && a_autoCreate) { |
void OnGameLoad() |
||||||
result = a_ref->PlaceObjectAtMe(a_objectToCreate, true).get(); |
{ |
||||||
result->Disable(); |
#ifdef _DEBUG |
||||||
a_refList->AddForm(result); |
SKSE::log::info("OnGameLoad"); |
||||||
} |
#endif |
||||||
|
|
||||||
if (!result) { |
g_persistentMap.clear(); |
||||||
SKSE::log::warn("Failed to find or create cell storage in GetCellStorage"); |
g_persistentStorage->ForEachForm([&](RE::TESForm& a_exform) { |
||||||
} |
if (&a_exform) { |
||||||
|
g_persistentMap[a_exform.formID] = a_exform.As<RE::TESObjectREFR>(); |
||||||
return result; |
} |
||||||
} |
return true; |
||||||
|
}); |
||||||
void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers) |
} |
||||||
{ |
|
||||||
if (!a_cellStorage || !a_excludeContainers) { |
void SetContainerMode(bool bOpening) |
||||||
SKSE::log::warn("Invalid arguments in SyncCellStorage"); |
{ |
||||||
return; |
if (bOpening) { |
||||||
} |
|
||||||
|
const auto refr = RE::TESObjectREFR::LookupByHandle(RE::ContainerMenu::GetTargetRefHandle()); |
||||||
std::unordered_map<RE::FormID, bool> cellItems; |
|
||||||
|
g_bHomeContainer = IsHome() |
||||||
const auto cell = a_cellStorage->GetParentCell(); |
&& refr |
||||||
const auto inv = a_cellStorage->GetInventory(); |
&& refr.get()->GetParentCell() == RE::PlayerCharacter::GetSingleton()->GetParentCell() |
||||||
|
&& !g_persistentStorage->HasForm(refr.get()); |
||||||
for (const RE::NiPointer<RE::TESObjectREFR> a_ref : cell->references) { |
|
||||||
const auto baseObj = a_ref->GetBaseObject(); |
#ifdef _DEBUG |
||||||
|
if (g_bHomeContainer) { |
||||||
if (baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As<RE::TESNPC>()->GetRace()->formID == 0x0010760A)) { |
RE::DebugNotification("Delayed processing enabled"); |
||||||
if (a_excludeContainers->HasForm(a_ref->formID) || baseObj->formID == 0xDC9E7) { // skip persistent and PlayerBookShelfContainer
|
} |
||||||
continue; |
#endif |
||||||
} |
|
||||||
|
} else if (g_bHomeContainer) { |
||||||
const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool { |
g_bHomeContainer = false; |
||||||
return !cellItems.contains(a_object.formID); |
SyncCellStorage(); |
||||||
}); |
} |
||||||
|
} |
||||||
for (const auto& [item, data] : contInv) { |
|
||||||
if (data.first > 0) { |
bool IsHome() |
||||||
cellItems[item->formID] = true; |
{ |
||||||
if (inv.find(item) == inv.end()) { |
return (bool)g_cellStorage; |
||||||
if (IsArtifact(item)) { |
} |
||||||
a_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr); |
|
||||||
} |
bool ToggleHomeMode(RE::TESObjectREFR* cellStorage) |
||||||
} |
{ |
||||||
} |
if (cellStorage) { |
||||||
} |
g_bHomeContainer = false; |
||||||
|
g_cellStorage = cellStorage; |
||||||
continue; |
RE::UI::GetSingleton()->AddEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton()); |
||||||
} |
#ifdef _DEBUG |
||||||
|
SKSE::log::info("Home mode ON"); |
||||||
if (a_ref->IsMarkedForDeletion()) { |
#endif |
||||||
SKSE::log::info("found marked for deletion"); |
return true; |
||||||
} |
} else if (g_cellStorage) { |
||||||
|
g_bHomeContainer = false; |
||||||
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) { |
g_cellStorage = nullptr; |
||||||
continue; |
RE::UI::GetSingleton()->RemoveEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton()); |
||||||
} |
#ifdef _DEBUG |
||||||
|
SKSE::log::info("Home mode OFF"); |
||||||
if (cellItems.contains(baseObj->formID)) { |
#endif |
||||||
continue; |
} |
||||||
} |
return false; |
||||||
|
} |
||||||
cellItems[baseObj->formID] = true; |
|
||||||
|
bool IsValidContainer(RE::TESObjectREFR* a_ref) |
||||||
if (!IsArtifact(baseObj)) { |
{ |
||||||
continue; |
if (!a_ref) { |
||||||
} |
return false; |
||||||
|
} |
||||||
if (inv.find(baseObj) == inv.end()) { |
|
||||||
a_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr); |
const auto baseObj = a_ref->GetBaseObject(); |
||||||
} |
|
||||||
} |
return baseObj->formType == RE::FormType::Container || (baseObj->formType == RE::FormType::NPC && !a_ref->IsDisabled() && baseObj->As<RE::TESNPC>()->GetRace()->formID == 0x0010760A); |
||||||
|
} |
||||||
for (const auto& [item, data] : inv) { |
|
||||||
const auto& [count, entry] = data; |
void OnCellEnter(RE::FormID a_formID) |
||||||
if (count > 0 && !cellItems.contains(item->formID)) { |
{ |
||||||
a_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); |
RE::TESObjectCELL* cell = RE::TESForm::LookupByID<RE::TESObjectCELL>(a_formID); |
||||||
} |
|
||||||
} |
if (!cell || !cell->IsInteriorCell()) { |
||||||
|
if (IsHome()) { |
||||||
cellItems.clear(); |
RE::ScriptEventSourceHolder::GetSingleton()->RemoveEventSink<RE::TESCellFullyLoadedEvent>(EventListener::GetSingleton()); |
||||||
} |
ToggleHomeMode(nullptr); |
||||||
|
} |
||||||
void OnItemPickup(RE::TESBoundObject* form) |
return; |
||||||
{ |
} |
||||||
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); |
|
||||||
|
RE::TESObjectREFR* cellStorage = nullptr; |
||||||
SyncCellStorage(cellStorage, persistentStorage); |
|
||||||
|
g_persistentStorage->ForEachForm([&](RE::TESForm& a_form) { |
||||||
if (!RefHasItem(cellStorage, form) && !GetItemCountInList(persistentStorage, form)) { |
const auto refr = a_form.As<RE::TESObjectREFR>(); |
||||||
RemoveListItem(listStored, form); |
if (refr && refr->GetParentCell()->formID == a_formID) { |
||||||
listFound->AddForm(form); |
cellStorage = refr; |
||||||
} |
return false; |
||||||
} |
} |
||||||
|
return true; |
||||||
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event) |
}); |
||||||
{ |
|
||||||
if (a_event->newContainer == 0x14) { |
ToggleHomeMode(cellStorage); |
||||||
return; |
|
||||||
RE::TESBoundObject* form = RE::TESForm::LookupByID<RE::TESBoundObject>(a_event->baseObj); |
if (!cellStorage) { |
||||||
|
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESCellFullyLoadedEvent>(EventListener::GetSingleton()); |
||||||
if (!form || !IsArtifact(form)) { |
} |
||||||
return; |
} |
||||||
} |
|
||||||
|
void OnCellEnter(RE::BGSLocation* location, RE::TESObjectCELL* cell) |
||||||
if (listFound->HasForm(form) || listStored->HasForm(form)) { |
{ |
||||||
return; |
if (!location || !cell->IsInteriorCell() || cell != RE::PlayerCharacter::GetSingleton()->GetParentCell() || !location->HasKeyword(g_homeKeyword)) { |
||||||
} |
ToggleHomeMode(nullptr); |
||||||
|
return; |
||||||
if (listNew->HasForm(form)) { |
} |
||||||
|
|
||||||
RemoveListItem(listNew, form); |
RE::TESObjectREFR* cellStorage = nullptr; |
||||||
|
|
||||||
listFound->AddForm(form); |
for (const auto& a_ref : cell->references) { |
||||||
|
if (a_ref.get()->GetBaseObject()->formID == g_cellContainer->formID) { |
||||||
if (notifyNewArtifact->value) { |
cellStorage = a_ref.get(); |
||||||
const auto itemName = form->GetName(); |
break; |
||||||
char* notificationText = new char[strlen("New artifact acquired: ") + strlen(itemName) + 1]; |
} |
||||||
strcpy(notificationText, "New artifact acquired: "); |
} |
||||||
strcat(notificationText, itemName); |
|
||||||
RE::DebugNotification(notificationText); |
if (cellStorage) { |
||||||
delete[] notificationText; |
if (!g_persistentStorage->HasForm(cellStorage)) { |
||||||
} |
g_persistentStorage->AddForm(cellStorage); |
||||||
|
g_persistentMap[cellStorage->formID] = cellStorage; |
||||||
} |
} |
||||||
|
ToggleHomeMode(cellStorage); |
||||||
} else if (a_event->oldContainer == 0x14) { |
return; |
||||||
|
} |
||||||
RE::TESBoundObject* form = RE::TESForm::LookupByID<RE::TESBoundObject>(a_event->baseObj); |
|
||||||
|
#ifdef _DEBUG |
||||||
SKSE::log::info("{}", form->GetName()); |
SKSE::log::info("Adding new storage in {}", cell->GetName()); |
||||||
if (!form || !IsArtifact(form)) { |
#endif |
||||||
return; |
|
||||||
} |
cellStorage = RE::PlayerCharacter::GetSingleton()->PlaceObjectAtMe(g_cellContainer, true).get(); |
||||||
|
|
||||||
if (!a_event->newContainer) { |
if (cellStorage) { |
||||||
if (bAtHome && a_event->reference) { // dropped at home
|
cellStorage->Disable(); |
||||||
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); |
g_persistentStorage->AddForm(cellStorage); |
||||||
if (!RefHasItem(cellStorage, form)) { |
g_persistentMap[cellStorage->formID] = cellStorage; |
||||||
cellStorage->AddObjectToContainer(form, nullptr, 1, nullptr); |
ToggleHomeMode(cellStorage); |
||||||
} |
} else { |
||||||
if (listFound->HasForm(form)) { |
SKSE::log::warn("Failed to create cell storage in OnCellEnter"); |
||||||
RemoveListItem(listFound, form); |
ToggleHomeMode(nullptr); |
||||||
} |
} |
||||||
listStored->AddForm(form); |
} |
||||||
RE::DebugNotification("added to stored"); |
|
||||||
return; |
void SyncCellStorage(RE::FormID skipRefID) |
||||||
} |
{ |
||||||
|
if (!IsHome()) { |
||||||
if (listStored->HasForm(form)) { |
#ifdef _DEBUG |
||||||
return; |
SKSE::log::info("SyncCellStorage called while not at home"); |
||||||
} |
#endif |
||||||
|
return; |
||||||
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form)) { |
} |
||||||
if (listFound->HasForm(form)) { |
|
||||||
RemoveListItem(listFound, form); |
#ifdef _DEBUG |
||||||
} |
SKSE::log::info("Running SyncCellStorage"); |
||||||
listNew->AddForm(form); |
#endif |
||||||
RE::DebugNotification("added to new"); |
|
||||||
} |
std::unordered_set<RE::FormID> cellItems; |
||||||
|
|
||||||
return; |
const auto cell = g_cellStorage->GetParentCell(); |
||||||
} |
const auto inv = g_cellStorage->GetInventory(); |
||||||
|
|
||||||
bool bPersistent = false; |
for (const auto& a_ref : cell->references) { |
||||||
for (const auto& ref : persistentStorage->forms) { |
const auto baseObj = a_ref->GetBaseObject(); |
||||||
if (ref && ref->formID == a_event->newContainer) { |
|
||||||
bPersistent = true; |
if (IsValidContainer(a_ref.get())) { |
||||||
break; |
if (g_cellContainer->formID == baseObj->formID || baseObj->formID == 0xDC9E7 || g_persistentMap.contains(a_ref->formID)) { // skip persistent and PlayerBookShelfContainer
|
||||||
} |
continue; |
||||||
} |
} |
||||||
|
|
||||||
if (!bPersistent && !bAtHome) { |
const auto contInv = a_ref->GetInventory([&](RE::TESBoundObject& a_object) -> bool { |
||||||
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form) && !FollowersHaveItem(form->As<RE::TESBoundObject>())) { |
return !cellItems.contains(a_object.formID); |
||||||
if (listFound->HasForm(form)) { |
}); |
||||||
RemoveListItem(listFound, form); |
|
||||||
} |
for (const auto& [item, data] : contInv) { |
||||||
listNew->AddForm(form); |
if (data.first > 0) { |
||||||
RE::DebugNotification("added to new"); |
cellItems.insert(item->formID); |
||||||
} |
if (IsArtifact(item)) { |
||||||
return; |
if (inv.find(item) == inv.end()) { |
||||||
} |
g_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr); |
||||||
|
} |
||||||
if (listStored->HasForm(form)) { |
if (!g_listStored->HasForm(item)) { |
||||||
return; |
ListRemoveItem(g_listNew, item); |
||||||
} |
ListRemoveItem(g_listFound, item); |
||||||
|
g_listStored->AddForm(item); |
||||||
if (!bPersistent && bAtHome) { |
} |
||||||
RE::TESObjectREFR* targetContainer = RE::TESForm::LookupByID<RE::TESObjectREFR>(a_event->newContainer); |
} |
||||||
|
} |
||||||
if (!targetContainer || targetContainer->GetParentCell() != RE::PlayerCharacter::GetSingleton()->GetParentCell()) { |
} |
||||||
return; |
|
||||||
} |
continue; |
||||||
|
} |
||||||
if (targetContainer->GetBaseObject()->Is(RE::FormType::NPC) && targetContainer->GetBaseObject()->As<RE::TESNPC>()->GetRace()->formID != 0x0010760A) { |
|
||||||
return; |
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion() || a_ref->formID == skipRefID) { |
||||||
} |
continue; |
||||||
|
} |
||||||
RE::TESObjectREFR* cellStorage = GetCellStorage(RE::PlayerCharacter::GetSingleton(), persistentStorage, cellContainer, true); |
|
||||||
cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), nullptr, 1, nullptr); |
if (cellItems.contains(baseObj->formID)) { |
||||||
RE::DebugNotification("updated cellStorage"); |
continue; |
||||||
} |
} |
||||||
|
|
||||||
if (listFound->HasForm(form)) { |
cellItems.insert(baseObj->formID); |
||||||
RemoveListItem(listFound, form); |
|
||||||
} |
if (!IsArtifact(baseObj)) { |
||||||
|
continue; |
||||||
listStored->AddForm(form); |
} |
||||||
RE::DebugNotification("added to stored"); |
|
||||||
} |
if (inv.find(baseObj) == inv.end()) { |
||||||
} |
g_cellStorage->AddObjectToContainer(baseObj, nullptr, 1, nullptr); |
||||||
} |
} |
||||||
|
|
||||||
|
if (!g_listStored->HasForm(baseObj)) { |
||||||
|
ListRemoveItem(g_listNew, baseObj); |
||||||
|
ListRemoveItem(g_listFound, baseObj); |
||||||
|
g_listStored->AddForm(baseObj); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (const auto& [item, data] : inv) { |
||||||
|
const auto& [count, entry] = data; |
||||||
|
if (count > 0 && !cellItems.contains(item->formID)) { |
||||||
|
g_cellStorage->RemoveItem(item, count, RE::ITEM_REMOVE_REASON::kRemove, nullptr, nullptr); |
||||||
|
if (!RefHasItem(g_persistentStorage, item->formID)) { |
||||||
|
ListRemoveItem(g_listStored, item); |
||||||
|
g_listFound->AddForm(item); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
cellItems.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event, RE::TESForm* form) |
||||||
|
{ |
||||||
|
if (a_event->newContainer == 0x14) { |
||||||
|
|
||||||
|
if (a_event->oldContainer) { |
||||||
|
|
||||||
|
if (g_persistentMap.contains(a_event->oldContainer)) { |
||||||
|
|
||||||
|
// Items in persistent containers are marked as stored by definition, no need to check the list
|
||||||
|
if (!RefHasItem(g_persistentStorage, a_event->baseObj)) { |
||||||
|
ListRemoveItem(g_listStored, form); |
||||||
|
g_listFound->AddForm(form); |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
|
||||||
|
} else if (g_cellStorage) { // non-persistent container at home
|
||||||
|
|
||||||
|
// g_bContainerMode is expected to be true, enabling processing after closing container.
|
||||||
|
// Reachable by Loot Menu or a mod, retrieving items via a spell of after scanning containers in cell.
|
||||||
|
// In extreme cases of mass retrieval, this can result in a few frames lag.
|
||||||
|
|
||||||
|
#ifdef _DEBUG |
||||||
|
SKSE::log::info("Synchronous processing of a non-persistent container (moved to player)"); |
||||||
|
#endif |
||||||
|
|
||||||
|
SyncCellStorage(); |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
} else if (g_cellStorage) { // probably, picked up at home (can also be crafted, but oh well)
|
||||||
|
|
||||||
|
// OnContainerChanged fires before a picked item gets marked for deletion
|
||||||
|
RE::FormID lastPickupFormID = NULL; |
||||||
|
const auto crosshairObj = RE::CrosshairPickData::GetSingleton()->target; |
||||||
|
if (crosshairObj && crosshairObj.get() && crosshairObj.get().get()->GetBaseObject()->formID == a_event->baseObj) { |
||||||
|
lastPickupFormID = crosshairObj.get().get()->formID; |
||||||
|
} |
||||||
|
|
||||||
|
SyncCellStorage(lastPickupFormID); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Instead of looking up in huge g_listNew, we check smaller g_listStored and g_listFound.
|
||||||
|
// Mass retrieval of found items is rare, so we check the stored list first.
|
||||||
|
if (g_listStored->HasForm(form) || g_listFound->HasForm(form)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// It's a new item, move it to found
|
||||||
|
ListRemoveItem(g_listNew, form); |
||||||
|
g_listFound->AddForm(form); |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (a_event->oldContainer != 0x14) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Items moved from player's inventory
|
||||||
|
|
||||||
|
if (!a_event->newContainer) { // no destination container
|
||||||
|
|
||||||
|
if (g_cellStorage && a_event->reference) { // dropped or placed on rack at home
|
||||||
|
if (!RefHasItem(g_cellStorage, form->formID)) { |
||||||
|
#ifdef _DEBUG |
||||||
|
SKSE::log::info("Added dropped {} to cell storage", form->GetName()); |
||||||
|
#endif |
||||||
|
RE::DebugNotification("adding to cell storage"); |
||||||
|
g_cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), nullptr, 1, nullptr); |
||||||
|
} |
||||||
|
ListRemoveItem(g_listFound, form); |
||||||
|
ListRemoveItem(g_listNew, form); |
||||||
|
g_listStored->AddForm(form); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (g_listStored->HasForm(form)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (!RefHasItem(RE::PlayerCharacter::GetSingleton(), form->formID) && !FollowersHaveItem(form)) { |
||||||
|
ListRemoveItem(g_listFound, form); |
||||||
|
ListRemoveItem(g_listNew, form); |
||||||
|
g_listNew->AddForm(form); |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (g_persistentMap.contains(a_event->newContainer)) { // moved to a persistent container
|
||||||
|
|
||||||
|
if (!g_listStored->HasForm(form)) { |
||||||
|
ListRemoveItem(g_listFound, form); |
||||||
|
g_listStored->AddForm(form); |
||||||
|
} |
||||||
|
|
||||||
|
} else if (g_cellStorage) { // stored at home in a non-persistent/non-registered container
|
||||||
|
|
||||||
|
// g_bContainerMode is expected to be true, enabling processing after closing container.
|
||||||
|
// Can be hit by autosorting mods. Most of them work with persistent containers, which should be added to the list of persistent containers.
|
||||||
|
|
||||||
|
#ifdef _DEBUG |
||||||
|
SKSE::log::info("Synchronous processing of a non-persistent container (moved from player)"); |
||||||
|
#endif |
||||||
|
|
||||||
|
const auto targetContainer = RE::TESForm::LookupByID<RE::TESObjectREFR>(a_event->newContainer); |
||||||
|
|
||||||
|
if (IsValidContainer(targetContainer)) { |
||||||
|
if (!RefHasItem(g_cellStorage, form->formID)) { |
||||||
|
g_cellStorage->AddObjectToContainer(form->As<RE::TESBoundObject>(), nullptr, 1, nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
if (!g_listStored->HasForm(form)) { |
||||||
|
ListRemoveItem(g_listFound, form); |
||||||
|
g_listStored->AddForm(form); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else if (g_listFound->HasForm(form) && !RefHasItem(RE::PlayerCharacter::GetSingleton(), form->formID) && !FollowersHaveItem(form)) { |
||||||
|
ListRemoveItem(g_listFound, form); |
||||||
|
g_listNew->AddForm(form); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AddRefArtifactsToList(RE::TESForm* a_refOrList, RE::BGSListForm* a_targetList, RE::BGSListForm* a_excludeList) |
||||||
|
{ |
||||||
|
if (!a_refOrList || !a_targetList) { |
||||||
|
SKSE::log::warn("Invalid arguments in AddRefArtifactsToList"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (a_refOrList->Is(RE::FormType::FormList)) { |
||||||
|
a_refOrList->As<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) { |
||||||
|
const auto refrItem = a_exform.As<RE::TESObjectREFR>(); |
||||||
|
if (refrItem) { |
||||||
|
AddRefArtifactsToList(refrItem, a_targetList, a_excludeList); |
||||||
|
} |
||||||
|
return true; |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>(); |
||||||
|
|
||||||
|
if (!containerRef) { |
||||||
|
SKSE::log::warn("containerRef in AddRefArtifactsToList is not a reference"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) { |
||||||
|
return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); |
||||||
|
}); |
||||||
|
|
||||||
|
for (const auto& item : inv) { |
||||||
|
if (item.second.first > 0) { |
||||||
|
a_targetList->AddForm(item.first); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
@ -1,164 +1,91 @@ |
|||||||
#pragma once |
#pragma once |
||||||
|
|
||||||
#include "ArtifactTracker.h" |
#include "ArtifactTracker.h" |
||||||
|
#include "Util.h" |
||||||
|
|
||||||
namespace Papyrus::PapyrusFunctions |
namespace Papyrus::PapyrusFunctions |
||||||
{ |
{ |
||||||
inline std::int32_t AddAllFormsToList(RE::StaticFunctionTag*, |
constexpr bool IsLoaded(RE::StaticFunctionTag*) |
||||||
RE::BGSListForm* a_targetList, |
|
||||||
short a_formType, |
|
||||||
RE::BGSListForm* a_storedList, |
|
||||||
RE::BGSListForm* a_foundList) |
|
||||||
{ |
{ |
||||||
const auto formType = static_cast<RE::FormType>(a_formType); |
return ArtifactTracker::g_bLoaded; |
||||||
|
} |
||||||
switch (formType) { |
|
||||||
|
|
||||||
case RE::FormType::Book: |
|
||||||
for (auto const& item : ArtifactTracker::validBooks) { |
|
||||||
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) { |
|
||||||
a_targetList->AddForm(item.second); |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case RE::FormType::Misc: |
|
||||||
for (auto const& item : ArtifactTracker::validMisc) { |
|
||||||
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) { |
|
||||||
a_targetList->AddForm(item.second); |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case RE::FormType::None: |
|
||||||
break; |
|
||||||
|
|
||||||
default: |
|
||||||
const auto dataHandler = RE::TESDataHandler::GetSingleton(); |
|
||||||
|
|
||||||
if (!dataHandler) { |
|
||||||
return a_targetList->forms.size(); |
|
||||||
} |
|
||||||
|
|
||||||
for (const auto& form : dataHandler->GetFormArray(formType)) { |
inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*) |
||||||
if (!form || !form->GetPlayable()) { |
{ |
||||||
continue; |
return ArtifactTracker::g_cellStorage; |
||||||
} |
} |
||||||
if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
a_targetList->AddForm(form); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return a_targetList->forms.size(); |
inline void SyncCellStorage(RE::StaticFunctionTag*, RE::FormID skipRefID = NULL) |
||||||
|
{ |
||||||
|
ArtifactTracker::SyncCellStorage(skipRefID); |
||||||
} |
} |
||||||
|
|
||||||
inline std::int32_t AddArtifactsToList(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, |
// From po3's Papyrus Extender
|
||||||
RE::TESForm* a_refOrList, |
inline std::vector<RE::Actor*> GetPlayerFollowers(RE::StaticFunctionTag*) |
||||||
RE::BGSListForm* a_targetList, |
|
||||||
RE::BGSListForm* a_excludeList = NULL) |
|
||||||
{ |
{ |
||||||
if (!a_refOrList) { |
std::vector<RE::Actor*> result; |
||||||
a_vm->TraceStack("a_refOrList in AddArtifactsToList is None", a_stackID); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
if (!a_targetList) { |
|
||||||
a_vm->TraceStack("a_targetList in AddArtifactsToList is None", a_stackID); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
if (a_refOrList->Is(RE::FormType::FormList)) { |
if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { |
||||||
a_refOrList->As<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) { |
for (auto& actorHandle : processLists->highActorHandles) { |
||||||
const auto refrItem = a_exform.As<RE::TESObjectREFR>(); |
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { |
||||||
if (refrItem) { |
result.push_back(actor.get()); |
||||||
AddArtifactsToList(a_vm, a_stackID, nullptr, refrItem, a_targetList, a_excludeList); |
|
||||||
} |
} |
||||||
return true; |
|
||||||
}); |
|
||||||
return a_targetList->forms.size(); |
|
||||||
} |
|
||||||
|
|
||||||
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>(); |
|
||||||
|
|
||||||
if (!containerRef) { |
|
||||||
a_vm->TraceStack("containerRef in AddArtifactsToList is not a reference", a_stackID); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) { |
|
||||||
return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); |
|
||||||
}); |
|
||||||
|
|
||||||
for (const auto& item : inv) { |
|
||||||
if (item.second.first > 0) { |
|
||||||
a_targetList->AddForm(item.first); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
return a_targetList->forms.size(); |
return result; |
||||||
} |
} |
||||||
|
|
||||||
inline RE::TESObjectREFR* GetCellStorage(RE::StaticFunctionTag*, |
inline RE::TESObjectREFR* GetCurrentContainer(RE::StaticFunctionTag*) |
||||||
RE::TESObjectREFR* a_ref, |
|
||||||
RE::BGSListForm* a_refList, |
|
||||||
RE::TESBoundObject* a_objectToCreate, |
|
||||||
bool a_autoCreate = true) |
|
||||||
{ |
{ |
||||||
return ArtifactTracker::GetCellStorage(a_ref, a_refList, a_objectToCreate, a_autoCreate); |
const auto handle = RE::ContainerMenu::GetTargetRefHandle(); |
||||||
|
const auto refr = RE::TESObjectREFR::LookupByHandle(handle); |
||||||
|
return refr ? refr.get() : nullptr; |
||||||
} |
} |
||||||
|
|
||||||
inline void SyncCellStorage(RE::StaticFunctionTag*, |
inline void RescanStoredArtifacts(RE::StaticFunctionTag*) |
||||||
RE::TESObjectREFR* a_cellStorage, |
|
||||||
RE::BGSListForm* a_excludeContainers) |
|
||||||
{ |
{ |
||||||
ArtifactTracker::SyncCellStorage(a_cellStorage, a_excludeContainers); |
ListRevert(ArtifactTracker::g_listStored); |
||||||
|
ArtifactTracker::AddRefArtifactsToList(ArtifactTracker::g_persistentStorage, ArtifactTracker::g_listStored); |
||||||
} |
} |
||||||
|
|
||||||
inline std::int32_t AddArtifactsFromFollowersToList(RE::StaticFunctionTag*, |
|
||||||
RE::BGSListForm* a_targetList, |
|
||||||
RE::BGSListForm* a_excludeList = NULL) |
|
||||||
{ |
|
||||||
if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { |
|
||||||
for (auto& actorHandle : processLists->highActorHandles) { |
|
||||||
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { |
|
||||||
|
|
||||||
const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_exform) { |
inline void RescanFoundArtifacts(RE::StaticFunctionTag*) |
||||||
return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform)); |
{ |
||||||
}); |
ListRevert(ArtifactTracker::g_listFound); |
||||||
|
ArtifactTracker::AddRefArtifactsToList(RE::PlayerCharacter::GetSingleton(), ArtifactTracker::g_listFound, ArtifactTracker::g_listStored); |
||||||
for (const auto& item : inv) { |
|
||||||
if (item.second.first > 0) { |
|
||||||
a_targetList->AddForm(item.first); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
for (const auto& ref : GetPlayerFollowers(nullptr)) { |
||||||
} |
ArtifactTracker::AddRefArtifactsToList(ref, ArtifactTracker::g_listFound, ArtifactTracker::g_listStored); |
||||||
} |
} |
||||||
|
|
||||||
return a_targetList->forms.size(); |
|
||||||
} |
} |
||||||
|
|
||||||
inline void OnItemPickup(RE::StaticFunctionTag*, |
inline void RescanNewArtifacts(RE::StaticFunctionTag*) |
||||||
RE::TESBoundObject* a_item) |
|
||||||
{ |
{ |
||||||
ArtifactTracker::OnItemPickup(a_item); |
ListRevert(ArtifactTracker::g_listNew); |
||||||
|
for (auto const& item : ArtifactTracker::g_artifactMap) { |
||||||
|
if (!ArtifactTracker::g_listStored->HasForm(item.second) && !ArtifactTracker::g_listFound->HasForm(item.second)) { |
||||||
|
ArtifactTracker::g_listNew->AddForm(item.second); |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
inline void Bind(VM& a_vm) |
inline void Bind(VM& a_vm) |
||||||
{ |
{ |
||||||
BIND(AddAllFormsToList); |
BIND(IsLoaded); |
||||||
logger::info("Registered AddAllFormsToList"sv); |
logger::info("Registered IsLoaded"sv); |
||||||
BIND(AddArtifactsToList); |
BIND(RescanStoredArtifacts); |
||||||
logger::info("Registered AddArtifactsToList"sv); |
logger::info("Registered RescanStoredArtifacts"sv); |
||||||
BIND(AddArtifactsFromFollowersToList); |
BIND(RescanFoundArtifacts); |
||||||
logger::info("Registered AddArtifactsFromFollowersToList"sv); |
logger::info("Registered RescanFoundArtifacts"sv); |
||||||
|
BIND(RescanNewArtifacts); |
||||||
|
logger::info("Registered RescanNewArtifacts"sv); |
||||||
BIND(GetCellStorage); |
BIND(GetCellStorage); |
||||||
logger::info("Registered GetCellStorage"sv); |
logger::info("Registered GetCellStorage"sv); |
||||||
BIND(SyncCellStorage); |
BIND(SyncCellStorage); |
||||||
logger::info("Registered SyncCellStorage"sv); |
logger::info("Registered SyncCellStorage"sv); |
||||||
BIND(OnItemPickup); |
BIND(GetPlayerFollowers); |
||||||
logger::info("Registered OnItemPickup"sv); |
logger::info("Registered GetPlayerFollowers"sv); |
||||||
|
BIND(GetCurrentContainer); |
||||||
|
logger::info("Registered GetCurrentContainer"sv); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,78 +1,66 @@ |
|||||||
#pragma once |
#pragma once |
||||||
|
|
||||||
void RemoveListItem(RE::BGSListForm* a_List, RE::TESForm* a_form) |
inline void ListRemoveItem(RE::BGSListForm* a_List, RE::TESForm* a_form) |
||||||
{ |
{ |
||||||
using func_t = decltype(&RemoveListItem); |
using func_t = decltype(&ListRemoveItem); |
||||||
REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) }; |
REL::Relocation<func_t> func{ REL::RelocationID(20471, 20914) }; |
||||||
return func(a_List, a_form); |
return func(a_List, a_form); |
||||||
} |
} |
||||||
|
|
||||||
void RevertList(RE::TESForm* a_form) |
inline void ListRevert(RE::BGSListForm* a_form) |
||||||
{ |
{ |
||||||
using func_t = decltype(&RevertList); |
using func_t = decltype(&ListRevert); |
||||||
REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) }; |
REL::Relocation<func_t> func{ REL::RelocationID(20469, 20912) }; |
||||||
return func(a_form); |
return func(a_form); |
||||||
} |
} |
||||||
|
|
||||||
inline bool RefHasItem(RE::TESObjectREFR* a_ref, RE::TESForm* a_item) |
inline bool RefHasItem(RE::TESForm* a_refOrList, RE::FormID a_formID) |
||||||
{ |
{ |
||||||
if (!a_ref || !a_item) { |
if (!a_refOrList || !a_formID) { |
||||||
SKSE::log::warn("Invalid arguments in RefHasItem"); |
SKSE::log::warn("Invalid arguments in RefHasItem"); |
||||||
return false; |
return false; |
||||||
} |
} |
||||||
|
|
||||||
auto invChanges = a_ref->GetInventoryChanges(); |
const auto refr = a_refOrList->As<RE::TESObjectREFR>(); |
||||||
if (invChanges && invChanges->entryList) { |
|
||||||
for (auto& entry : *invChanges->entryList) { |
if (refr) { |
||||||
if (entry && entry->object && entry->object->formID == a_item->formID) { |
const auto invChanges = refr->GetInventoryChanges(); |
||||||
return entry->countDelta > 0; |
if (invChanges && invChanges->entryList) { |
||||||
} |
for (auto& entry : *invChanges->entryList) { |
||||||
} |
if (entry && entry->object && entry->object->formID == a_formID) { |
||||||
} |
return entry->countDelta > 0; |
||||||
return false; |
} |
||||||
} |
} |
||||||
|
} |
||||||
inline std::uint32_t GetItemCountInList(RE::BGSListForm* a_containerList, RE::TESBoundObject* a_form) |
return false; |
||||||
{ |
} |
||||||
if (!a_containerList || !a_form) { |
|
||||||
SKSE::log::warn("Invalid arguments in GetItemCountInList"); |
const auto list = a_refOrList->As<RE::BGSListForm>(); |
||||||
return 0; |
|
||||||
} |
if (list) { |
||||||
|
for (const auto& ref : list->forms) { |
||||||
std::uint32_t iResult = 0; |
if (ref && RefHasItem(ref, a_formID)) { |
||||||
|
return true; |
||||||
a_containerList->ForEachForm([&](RE::TESForm& a_container) { |
} |
||||||
const auto refrItem = a_container.As<RE::TESObjectREFR>(); |
} |
||||||
if (refrItem) { |
} |
||||||
const auto inv = refrItem->GetInventory([&](RE::TESBoundObject& a_object) -> bool { |
|
||||||
return a_form->formID == a_object.formID; |
return false; |
||||||
}); |
} |
||||||
const auto it = inv.find(a_form); |
|
||||||
iResult += it != inv.end() ? it->second.first : 0; |
inline bool FollowersHaveItem(RE::TESForm* a_form) |
||||||
} |
{ |
||||||
return true; |
if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { |
||||||
}); |
for (auto& actorHandle : processLists->highActorHandles) { |
||||||
|
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { |
||||||
return iResult; |
|
||||||
} |
if (RefHasItem(actor->As<RE::TESObjectREFR>(), a_form->formID)) { |
||||||
|
return true; |
||||||
inline bool FollowersHaveItem(RE::TESBoundObject* a_form) |
} |
||||||
{ |
|
||||||
if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists) { |
} |
||||||
for (auto& actorHandle : processLists->highActorHandles) { |
} |
||||||
if (auto actor = actorHandle.get(); actor && actor->IsPlayerTeammate()) { |
} |
||||||
const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_object) -> bool { |
|
||||||
return a_form->formID == a_object.formID; |
return false; |
||||||
}); |
|
||||||
const auto it = inv.find(a_form); |
|
||||||
const auto iCount = it != inv.end() ? it->second.first : 0; |
|
||||||
|
|
||||||
if (iCount > 0) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
} |
@ -0,0 +1,120 @@ |
|||||||
|
Scriptname ArtifactTrackerPlayer extends ReferenceAlias |
||||||
|
|
||||||
|
FormList Property ETR_ItemsNew Auto |
||||||
|
FormList Property ETR_ItemsFound Auto |
||||||
|
FormList Property ETR_ItemsStored Auto |
||||||
|
|
||||||
|
Keyword Property LocTypePlayerHouse Auto |
||||||
|
|
||||||
|
bool bAtHome = false |
||||||
|
int iFollowerIndex |
||||||
|
|
||||||
|
|
||||||
|
event OnInit() |
||||||
|
OnPlayerLoadGame() |
||||||
|
endevent |
||||||
|
|
||||||
|
|
||||||
|
Event OnPlayerLoadGame() |
||||||
|
|
||||||
|
if ! IsLoaded() |
||||||
|
ETR_ItemsNew.Revert() |
||||||
|
ETR_ItemsFound.Revert() |
||||||
|
ETR_ItemsStored.Revert() |
||||||
|
UnregisterForUpdate() |
||||||
|
Debug.Notification("Failed to initialize ArtifactTracker.dll") |
||||||
|
return |
||||||
|
endif |
||||||
|
|
||||||
|
if skse.GetPluginVersion("Ahzaab's moreHUD Plugin") >= 30800 |
||||||
|
ahzmorehud.RegisterIconFormList("dbmNew", ETR_ItemsNew) |
||||||
|
ahzmorehud.RegisterIconFormList("dbmFound", ETR_ItemsFound) |
||||||
|
ahzmorehud.RegisterIconFormList("dbmDisp", ETR_ItemsStored) |
||||||
|
endif |
||||||
|
|
||||||
|
if skse.GetPluginVersion("Ahzaab's moreHUD Inventory Plugin") >= 10017 |
||||||
|
ahzmorehudie.RegisterIconFormList("dbmNew", ETR_ItemsNew) |
||||||
|
ahzmorehudie.RegisterIconFormList("dbmFound", ETR_ItemsFound) |
||||||
|
ahzmorehudie.RegisterIconFormList("dbmDisp", ETR_ItemsStored) |
||||||
|
endif |
||||||
|
|
||||||
|
if SKSE.GetPluginVersion("QuickLootRE") >= 292 |
||||||
|
QuickLootRE.RegisterNewItemsList(ETR_ItemsNew) |
||||||
|
QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored) |
||||||
|
QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound) |
||||||
|
endif |
||||||
|
|
||||||
|
; Rebuild all lists to avoid discrepancies, stale data, and broken records |
||||||
|
RescanStoredArtifacts() |
||||||
|
RescanFoundArtifacts() |
||||||
|
RescanNewArtifacts() |
||||||
|
|
||||||
|
Location currentLocation = (GetReference() as ObjectReference).GetCurrentLocation() |
||||||
|
bAtHome = currentLocation && currentLocation.HasKeyword(LocTypePlayerHouse) |
||||||
|
|
||||||
|
if bAtHome |
||||||
|
RegisterForModEvent("AT_HomeInventoryUpdate", "OnHomeInventoryUpdate") |
||||||
|
else |
||||||
|
UnregisterForModEvent("AT_HomeInventoryUpdate") |
||||||
|
endif |
||||||
|
|
||||||
|
EndEvent |
||||||
|
|
||||||
|
|
||||||
|
Event OnLocationChange(Location akOldLoc, Location akNewLoc) |
||||||
|
|
||||||
|
bAtHome = akNewLoc && akNewLoc.HasKeyword(LocTypePlayerHouse) |
||||||
|
|
||||||
|
if bAtHome |
||||||
|
RegisterForModEvent("AT_HomeInventoryUpdate", "OnHomeInventoryUpdate") |
||||||
|
elseif akOldLoc && akOldLoc.HasKeyword(LocTypePlayerHouse) |
||||||
|
UnregisterForModEvent("AT_HomeInventoryUpdate") |
||||||
|
endif |
||||||
|
|
||||||
|
int iCurrentFollowers = 0; |
||||||
|
Actor[] aFollowers = GetPlayerFollowers() |
||||||
|
int i = aFollowers.length |
||||||
|
while i > 0 |
||||||
|
i -= 1 |
||||||
|
iCurrentFollowers += aFollowers[i].GetFormID() |
||||||
|
endwhile |
||||||
|
|
||||||
|
if iCurrentFollowers != iFollowerIndex |
||||||
|
iFollowerIndex = iCurrentFollowers |
||||||
|
RegisterForSingleUpdate(5.0) ; wait until followers load into the location |
||||||
|
endif |
||||||
|
|
||||||
|
endEvent |
||||||
|
|
||||||
|
|
||||||
|
Event OnUpdate() |
||||||
|
|
||||||
|
Debug.Notification("Team changed, updating ETR_ItemsFound") |
||||||
|
RescanFoundArtifacts() |
||||||
|
|
||||||
|
EndEvent |
||||||
|
|
||||||
|
|
||||||
|
event OnHomeInventoryUpdate() |
||||||
|
if bAtHome |
||||||
|
Debug.Notification("Triggered AT_HomeInventoryUpdate") |
||||||
|
SyncCellStorage() |
||||||
|
endif |
||||||
|
endevent |
||||||
|
|
||||||
|
|
||||||
|
; NATIVE FUNCTIONS |
||||||
|
|
||||||
|
bool function IsLoaded() native global |
||||||
|
|
||||||
|
function RescanStoredArtifacts() native global |
||||||
|
|
||||||
|
function RescanFoundArtifacts() native global |
||||||
|
|
||||||
|
function RescanNewArtifacts() native global |
||||||
|
|
||||||
|
ObjectReference function GetCellStorage() native global |
||||||
|
|
||||||
|
function SyncCellStorage(int FormID = 0) native global |
||||||
|
|
||||||
|
Actor[] function GetPlayerFollowers() native global |
@ -1,13 +0,0 @@ |
|||||||
Scriptname ETR_Functions Hidden |
|
||||||
|
|
||||||
int function AddAllFormsToList(FormList targetList, int formType, FormList storedList, FormList foundList) native global |
|
||||||
|
|
||||||
int function AddArtifactsToList(Form refOrList, FormList targetList, FormList excludeList = None) native global |
|
||||||
|
|
||||||
ObjectReference function GetCellStorage(ObjectReference ref, FormList refList, Form refToCreate, bool autoCreate = true) native global |
|
||||||
|
|
||||||
function SyncCellStorage(ObjectReference cellStorage, FormList excludeContainers) native global |
|
||||||
|
|
||||||
int function AddArtifactsFromFollowersToList(FormList targetList, FormList excludeList = None) native global |
|
||||||
|
|
||||||
function OnItemPickup(Form item) native global |
|
@ -1,179 +0,0 @@ |
|||||||
Scriptname ETR_TrackStoredItems extends ReferenceAlias |
|
||||||
|
|
||||||
Actor Property PlayerRef Auto |
|
||||||
|
|
||||||
FormList Property ETR_ItemsNew Auto |
|
||||||
FormList Property ETR_ItemsFound Auto |
|
||||||
FormList Property ETR_ItemsStored Auto |
|
||||||
FormList Property ETR_PersistentStorageList Auto |
|
||||||
|
|
||||||
Container Property ETR_CellStorageContainer Auto |
|
||||||
|
|
||||||
Keyword Property LocTypePlayerHouse Auto |
|
||||||
|
|
||||||
bool bBusy = false |
|
||||||
bool bAtHome = false |
|
||||||
bool bRescanHome = false |
|
||||||
bool bRescanPersistent = false |
|
||||||
ObjectReference lastDestContainer = None |
|
||||||
bool lastDestIsPersistent = false |
|
||||||
ObjectReference lastSourceContainer = None |
|
||||||
bool lastSourceIsPersistent = false |
|
||||||
int iUpdateCount |
|
||||||
|
|
||||||
|
|
||||||
event OnInit() |
|
||||||
OnPlayerLoadGame() |
|
||||||
endevent |
|
||||||
|
|
||||||
|
|
||||||
Event OnPlayerLoadGame() |
|
||||||
AddInventoryEventFilter(ETR_ItemsStored) |
|
||||||
|
|
||||||
if skse.GetPluginVersion("Ahzaab's moreHUD Plugin") >= 30800 |
|
||||||
ahzmorehud.RegisterIconFormList("dbmNew", ETR_ItemsNew) |
|
||||||
ahzmorehud.RegisterIconFormList("dbmFound", ETR_ItemsFound) |
|
||||||
ahzmorehud.RegisterIconFormList("dbmDisp", ETR_ItemsStored) |
|
||||||
endif |
|
||||||
|
|
||||||
if skse.GetPluginVersion("Ahzaab's moreHUD Inventory Plugin") >= 10017 |
|
||||||
ahzmorehudie.RegisterIconFormList("dbmNew", ETR_ItemsNew) |
|
||||||
ahzmorehudie.RegisterIconFormList("dbmFound", ETR_ItemsFound) |
|
||||||
ahzmorehudie.RegisterIconFormList("dbmDisp", ETR_ItemsStored) |
|
||||||
endif |
|
||||||
|
|
||||||
if SKSE.GetPluginVersion("QuickLootRE") >= 292 |
|
||||||
QuickLootRE.RegisterNewItemsList(ETR_ItemsNew) |
|
||||||
QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored) |
|
||||||
QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound) |
|
||||||
endif |
|
||||||
|
|
||||||
; Rebuild all lists to avoid discrepancies, stale data, and broken records |
|
||||||
|
|
||||||
ETR_ItemsStored.Revert() |
|
||||||
ETR_Functions.AddArtifactsToList(ETR_PersistentStorageList, ETR_ItemsStored) |
|
||||||
|
|
||||||
ETR_ItemsFound.Revert() |
|
||||||
ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored) |
|
||||||
ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored) |
|
||||||
|
|
||||||
ETR_ItemsNew.Revert() |
|
||||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 41, ETR_ItemsStored, ETR_ItemsFound) |
|
||||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 32, ETR_ItemsStored, ETR_ItemsFound) |
|
||||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 27, ETR_ItemsStored, ETR_ItemsFound) |
|
||||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 26, ETR_ItemsStored, ETR_ItemsFound) |
|
||||||
|
|
||||||
Location currentLocation = PlayerRef.GetCurrentLocation() |
|
||||||
bAtHome = currentLocation && currentLocation.HasKeyword(LocTypePlayerHouse) |
|
||||||
if bAtHome |
|
||||||
GotoState("AtHome") |
|
||||||
else |
|
||||||
GotoState("") |
|
||||||
endif |
|
||||||
EndEvent |
|
||||||
|
|
||||||
|
|
||||||
Event OnLocationChange(Location akOldLoc, Location akNewLoc) |
|
||||||
bAtHome = akNewLoc && akNewLoc.HasKeyword(LocTypePlayerHouse) |
|
||||||
if bAtHome |
|
||||||
GotoState("AtHome") |
|
||||||
else |
|
||||||
GotoState("") |
|
||||||
endif |
|
||||||
endEvent |
|
||||||
|
|
||||||
|
|
||||||
Event OnMenuClose(String MenuName) |
|
||||||
UnregisterForUpdate() |
|
||||||
OnUpdate() |
|
||||||
EndEvent |
|
||||||
|
|
||||||
|
|
||||||
Event OnUpdate() |
|
||||||
|
|
||||||
if UI.IsMenuOpen("ContainerMenu") |
|
||||||
RegisterForMenu("ContainerMenu") |
|
||||||
return |
|
||||||
endif |
|
||||||
|
|
||||||
while bBusy |
|
||||||
Debug.Notification("Stored OnUpdate is busy") |
|
||||||
Utility.wait(0.5) |
|
||||||
endwhile |
|
||||||
|
|
||||||
bBusy = true |
|
||||||
|
|
||||||
iUpdateCount += 1 |
|
||||||
Debug.Notification("Running Stored OnUpdate " + iUpdateCount) |
|
||||||
|
|
||||||
if bRescanHome |
|
||||||
if lastSourceContainer && lastSourceContainer as Actor && (lastSourceContainer as Actor).IsPlayerTeammate() |
|
||||||
lastSourceContainer = None |
|
||||||
return |
|
||||||
endif |
|
||||||
bRescanHome = false |
|
||||||
|
|
||||||
ObjectReference bookShelf |
|
||||||
if lastDestContainer |
|
||||||
if lastDestContainer as PlayerBookShelfContainerScript |
|
||||||
bookShelf = lastDestContainer |
|
||||||
endif |
|
||||||
elseif lastSourceContainer |
|
||||||
if lastSourceContainer as PlayerBookShelfContainerScript |
|
||||||
bookShelf = lastSourceContainer |
|
||||||
endif |
|
||||||
endif |
|
||||||
|
|
||||||
if bookShelf |
|
||||||
int iLimit = 10 |
|
||||||
while iLimit > 0 && (bookShelf as PlayerBookShelfContainerScript).GetState() == "PlacingBooks" |
|
||||||
Debug.Notification("Waiting for shelf update") |
|
||||||
iLimit -= 1 |
|
||||||
Utility.wait(0.5) |
|
||||||
endwhile |
|
||||||
endif |
|
||||||
|
|
||||||
ObjectReference cellStorage = ETR_Functions.GetCellStorage(PlayerRef, ETR_PersistentStorageList, ETR_CellStorageContainer) |
|
||||||
ETR_Functions.SyncCellStorage(cellStorage, ETR_PersistentStorageList) |
|
||||||
endif |
|
||||||
|
|
||||||
if bRescanPersistent |
|
||||||
bRescanPersistent = false |
|
||||||
|
|
||||||
ETR_ItemsStored.Revert() |
|
||||||
Form[] aContainers = ETR_PersistentStorageList.ToArray() |
|
||||||
|
|
||||||
int n = aContainers.length |
|
||||||
while n > 0 |
|
||||||
n -= 1 |
|
||||||
ETR_Functions.AddArtifactsToList(aContainers[n], ETR_ItemsStored) |
|
||||||
endwhile |
|
||||||
|
|
||||||
ETR_ItemsFound.Revert() |
|
||||||
ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored) |
|
||||||
ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored) |
|
||||||
endif |
|
||||||
|
|
||||||
bBusy = false |
|
||||||
|
|
||||||
EndEvent |
|
||||||
|
|
||||||
|
|
||||||
; We acquired a stored item, and we want to find out if we just have taken the last stored item of its kind |
|
||||||
event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) |
|
||||||
|
|
||||||
if akSourceContainer |
|
||||||
if lastSourceContainer != akSourceContainer |
|
||||||
lastSourceContainer = akSourceContainer |
|
||||||
lastSourceIsPersistent = ETR_PersistentStorageList.HasForm(akSourceContainer) |
|
||||||
endif |
|
||||||
if bAtHome || lastSourceIsPersistent |
|
||||||
bRescanHome = bAtHome |
|
||||||
bRescanPersistent = true |
|
||||||
RegisterForSingleUpdate(0.5) |
|
||||||
endif |
|
||||||
elseif bAtHome |
|
||||||
ETR_Functions.OnItemPickup(akBaseItem) |
|
||||||
endif |
|
||||||
|
|
||||||
endevent |
|
Loading…
Reference in new issue