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