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