Moved most synchronous operations to DLL; decoded Formlist.Revert and FormList.RemoveAddedForm
This commit is contained in:
parent
bb427cc419
commit
a7229e0bea
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -23,8 +23,9 @@ configure_file(
|
||||
set(sources
|
||||
src/Main.cpp
|
||||
src/Papyrus.cpp
|
||||
src/ArtifactTracker.cpp
|
||||
src/EventListener.cpp
|
||||
src/BookCheck.cpp
|
||||
src/MiscCheck.cpp
|
||||
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.rc)
|
||||
|
||||
|
321
Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp
Normal file
321
Source/ArtifactTrackerDLL/src/ArtifactTracker.cpp
Normal file
@ -0,0 +1,321 @@
|
||||
#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");
|
||||
}
|
||||
}
|
||||
}
|
29
Source/ArtifactTrackerDLL/src/ArtifactTracker.h
Normal file
29
Source/ArtifactTrackerDLL/src/ArtifactTracker.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
namespace ArtifactTracker
|
||||
{
|
||||
extern RE::TESGlobal* notifyNewArtifact;
|
||||
extern RE::TESBoundObject* cellContainer;
|
||||
extern RE::BGSListForm* listNew;
|
||||
extern RE::BGSListForm* listStored;
|
||||
extern RE::BGSListForm* listFound;
|
||||
extern RE::BGSListForm* persistentStorage;
|
||||
extern RE::BGSKeyword* homeKeyword;
|
||||
extern bool bAtHome;
|
||||
extern std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc;
|
||||
extern std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks;
|
||||
|
||||
void Init();
|
||||
|
||||
bool IsArtifact(RE::TESForm* a_item);
|
||||
|
||||
bool SetHomeLocation(RE::BGSLocation* a_location);
|
||||
|
||||
RE::TESObjectREFR* GetCellStorage(RE::TESObjectREFR* a_ref, RE::BGSListForm* a_refList, RE::TESBoundObject* a_objectToCreate, bool a_autoCreate = true);
|
||||
|
||||
void SyncCellStorage(RE::TESObjectREFR* a_cellStorage, RE::BGSListForm* a_excludeContainers);
|
||||
|
||||
void OnItemPickup(RE::TESBoundObject* form);
|
||||
|
||||
void OnContainerChanged(const RE::TESContainerChangedEvent* a_event);
|
||||
}
|
@ -3,33 +3,6 @@
|
||||
// Mostly borrowed from Fix Note icon for SkyUI by 0xC0000005
|
||||
namespace BookCheck
|
||||
{
|
||||
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> validBooks;
|
||||
|
||||
void PreloadBookList()
|
||||
{
|
||||
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
|
||||
if (!dataHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
|
||||
|
||||
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) {
|
||||
if (!form || form->TeachesSpell()) {
|
||||
continue;
|
||||
}
|
||||
if (form->HasKeyword(recipeKeyword) || IsBook(form)) {
|
||||
validBooks[form->formID] = form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> GetBookList()
|
||||
{
|
||||
return validBooks;
|
||||
}
|
||||
|
||||
const char* ExtractFileName(const char* const path, size_t& fileNameLength)
|
||||
{
|
||||
if (!path) {
|
||||
|
@ -3,10 +3,6 @@
|
||||
// Mostly borrowed from Fix Note icon for SkyUI by 0xC0000005
|
||||
namespace BookCheck
|
||||
{
|
||||
void PreloadBookList();
|
||||
|
||||
std::unordered_map<RE::FormID, RE::TESObjectBOOK*> GetBookList();
|
||||
|
||||
const char* ExtractFileName(const char* const path, size_t& fileNameLength);
|
||||
|
||||
// Performs a case-sensitive search for 'needle' in the specified string,
|
||||
|
38
Source/ArtifactTrackerDLL/src/EventListener.cpp
Normal file
38
Source/ArtifactTrackerDLL/src/EventListener.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "EventListener.h"
|
||||
#include "ArtifactTracker.h"
|
||||
|
||||
|
||||
auto EventListener::GetSingleton() -> EventListener*
|
||||
{
|
||||
static EventListener singleton{};
|
||||
return std::addressof(singleton);
|
||||
}
|
||||
|
||||
void EventListener::Install()
|
||||
{
|
||||
const auto eventSource = RE::ScriptEventSourceHolder::GetSingleton();
|
||||
eventSource->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
|
||||
eventSource->AddEventSink<RE::TESActorLocationChangeEvent>(EventListener::GetSingleton());
|
||||
}
|
||||
|
||||
auto EventListener::ProcessEvent(
|
||||
const RE::TESActorLocationChangeEvent* a_event,
|
||||
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource)
|
||||
-> RE::BSEventNotifyControl
|
||||
{
|
||||
if (a_event->actor && a_event->actor->IsPlayerRef()) {
|
||||
ArtifactTracker::SetHomeLocation(a_event->newLoc);
|
||||
}
|
||||
|
||||
return RE::BSEventNotifyControl::kContinue;
|
||||
}
|
||||
|
||||
auto EventListener::ProcessEvent(
|
||||
const RE::TESContainerChangedEvent* a_event,
|
||||
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
|
||||
-> RE::BSEventNotifyControl
|
||||
{
|
||||
ArtifactTracker::OnContainerChanged(a_event);
|
||||
|
||||
return RE::BSEventNotifyControl::kContinue;
|
||||
}
|
29
Source/ArtifactTrackerDLL/src/EventListener.h
Normal file
29
Source/ArtifactTrackerDLL/src/EventListener.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
class EventListener :
|
||||
public RE::BSTEventSink<RE::TESActorLocationChangeEvent>,
|
||||
public RE::BSTEventSink<RE::TESContainerChangedEvent>
|
||||
{
|
||||
public:
|
||||
~EventListener() = default;
|
||||
EventListener(const EventListener&) = delete;
|
||||
EventListener& operator=(const EventListener&) = delete;
|
||||
EventListener& operator=(EventListener&&) = delete;
|
||||
|
||||
static auto GetSingleton() -> EventListener*;
|
||||
|
||||
static void Install();
|
||||
|
||||
auto ProcessEvent(
|
||||
const RE::TESActorLocationChangeEvent* a_event,
|
||||
RE::BSTEventSource<RE::TESActorLocationChangeEvent>* a_eventSource)
|
||||
-> RE::BSEventNotifyControl override;
|
||||
|
||||
auto ProcessEvent(
|
||||
const RE::TESContainerChangedEvent* a_event,
|
||||
RE::BSTEventSource<RE::TESContainerChangedEvent>* a_eventSource)
|
||||
-> RE::BSEventNotifyControl override;
|
||||
|
||||
private:
|
||||
EventListener() = default;
|
||||
};
|
@ -1,288 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Papyrus::ArtifactTracker
|
||||
{
|
||||
inline bool is_artifact(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 BookCheck::GetBookList().contains(a_form->formID);
|
||||
case RE::FormType::Misc:
|
||||
return MiscCheck::GetMiscList().contains(a_form->formID);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::int32_t AddAllFormsToList(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);
|
||||
|
||||
switch (formType) {
|
||||
|
||||
case RE::FormType::Book:
|
||||
for (auto const& item : BookCheck::GetBookList()) {
|
||||
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 : MiscCheck::GetMiscList()) {
|
||||
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) {
|
||||
a_targetList->AddForm(item.second);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RE::FormType::None:
|
||||
break;
|
||||
|
||||
default:
|
||||
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
|
||||
if (!dataHandler) {
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
for (const auto& form : dataHandler->GetFormArray(formType)) {
|
||||
if (!form || !form->GetPlayable()) {
|
||||
continue;
|
||||
}
|
||||
if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) {
|
||||
continue;
|
||||
}
|
||||
a_targetList->AddForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
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) {
|
||||
a_vm->TraceStack("a_refOrList in AddArtifactsToList is None", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
if (!a_targetList) {
|
||||
a_vm->TraceStack("a_targetList in AddArtifactsToList is None", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_refOrList->Is(RE::FormType::FormList)) {
|
||||
a_refOrList->As<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) {
|
||||
const auto refrItem = a_exform.As<RE::TESObjectREFR>();
|
||||
if (refrItem) {
|
||||
AddArtifactsToList(a_vm, a_stackID, nullptr, refrItem, a_targetList, a_excludeList);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>();
|
||||
|
||||
if (!containerRef) {
|
||||
a_vm->TraceStack("containerRef in AddArtifactsToList is not a reference", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) {
|
||||
return is_artifact(&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)
|
||||
{
|
||||
RE::TESObjectREFR* result = NULL;
|
||||
|
||||
if (!a_ref || !a_refList || !a_objectToCreate) {
|
||||
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);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void SyncCellStorage(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*,
|
||||
RE::TESObjectREFR* a_cellStorage,
|
||||
RE::BGSListForm* a_excludeContainers)
|
||||
{
|
||||
if (!a_cellStorage) {
|
||||
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 (is_artifact(item)) {
|
||||
a_cellStorage->AddObjectToContainer(item, nullptr, 1, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (a_ref->IsDisabled() || a_ref->IsMarkedForDeletion()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cellItems.contains(baseObj->formID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cellItems[baseObj->formID] = true;
|
||||
|
||||
if (!is_artifact(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();
|
||||
}
|
||||
|
||||
// From po3's Papyrus Extender
|
||||
inline std::vector<RE::Actor*> GetPlayerFollowers(RE::StaticFunctionTag*)
|
||||
{
|
||||
std::vector<RE::Actor*> result;
|
||||
|
||||
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 result;
|
||||
}
|
||||
|
||||
inline std::int32_t AddArtifactsFromFollowersToList(RE::StaticFunctionTag*,
|
||||
RE::BGSListForm* a_targetList,
|
||||
RE::BGSListForm* a_excludeList = NULL)
|
||||
{
|
||||
for (const auto& actor : GetPlayerFollowers(nullptr)) {
|
||||
const auto inv = actor->GetInventory([&](RE::TESBoundObject& a_exform) {
|
||||
return is_artifact(&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 bool FollowersHaveItem(RE::StaticFunctionTag*,
|
||||
RE::TESBoundObject* a_form)
|
||||
{
|
||||
for (const auto& actor : GetPlayerFollowers(nullptr)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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(GetCellStorage);
|
||||
logger::info("Registered GetCellStorage"sv);
|
||||
BIND(SyncCellStorage);
|
||||
logger::info("Registered SyncCellStorage"sv);
|
||||
BIND(FollowersHaveItem);
|
||||
logger::info("Registered FollowersHaveItem"sv);
|
||||
BIND(GetPlayerFollowers);
|
||||
logger::info("Registered GetPlayerFollowers"sv);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Papyrus::ObjectReference
|
||||
{
|
||||
inline std::uint32_t GetItemCountInList(RE::StaticFunctionTag*,
|
||||
RE::BGSListForm* a_containerList,
|
||||
RE::TESBoundObject* a_form)
|
||||
{
|
||||
if (!a_containerList || !a_form) {
|
||||
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 void Bind(VM& a_vm)
|
||||
{
|
||||
BIND(GetItemCountInList);
|
||||
logger::info("Registered GetItemCountInList"sv);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
#include "Papyrus.h"
|
||||
#include "BookCheck.h"
|
||||
#include "MiscCheck.h"
|
||||
#include "ArtifactTracker.h"
|
||||
|
||||
using namespace ArtifactTracker;
|
||||
using namespace RE::BSScript;
|
||||
using namespace SKSE;
|
||||
using namespace SKSE::log;
|
||||
@ -27,11 +27,11 @@ namespace {
|
||||
spdlog::set_pattern("[%l] %v"s);
|
||||
}
|
||||
|
||||
void InitializeMessaging() {
|
||||
void InitializeMessaging()
|
||||
{
|
||||
GetMessagingInterface()->RegisterListener([](MessagingInterface::Message* message) {
|
||||
if (message->type == MessagingInterface::kDataLoaded) {
|
||||
BookCheck::PreloadBookList();
|
||||
MiscCheck::PreloadMiscList();
|
||||
ArtifactTracker::Init();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -46,6 +46,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
|
||||
|
||||
Init(skse);
|
||||
InitializeMessaging();
|
||||
|
||||
SKSE::GetPapyrusInterface()->Register(Papyrus::Bind);
|
||||
|
||||
log::info("{} has finished loading.", plugin->GetName());
|
||||
|
@ -1,28 +0,0 @@
|
||||
#include "MiscCheck.h"
|
||||
|
||||
namespace MiscCheck
|
||||
{
|
||||
std::unordered_map<RE::FormID, RE::TESObjectMISC*> validMisc;
|
||||
|
||||
void PreloadMiscList()
|
||||
{
|
||||
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
|
||||
if (!dataHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<RE::FormID, RE::TESObjectMISC*> GetMiscList()
|
||||
{
|
||||
return validMisc;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace MiscCheck
|
||||
{
|
||||
void PreloadMiscList();
|
||||
|
||||
std::unordered_map<RE::FormID, RE::TESObjectMISC*> GetMiscList();
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
#include "Papyrus.h"
|
||||
#include "BookCheck.h"
|
||||
#include "MiscCheck.h"
|
||||
|
||||
#include "Functions/ObjectReference.h"
|
||||
#include "Functions/ArtifactTracker.h"
|
||||
#include "PapyrusFunctions.h"
|
||||
|
||||
namespace Papyrus
|
||||
{
|
||||
@ -16,8 +12,7 @@ namespace Papyrus
|
||||
|
||||
logger::info("{:*^30}", "FUNCTIONS"sv);
|
||||
|
||||
ObjectReference::Bind(*a_vm);
|
||||
ArtifactTracker::Bind(*a_vm);
|
||||
PapyrusFunctions::Bind(*a_vm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
164
Source/ArtifactTrackerDLL/src/PapyrusFunctions.h
Normal file
164
Source/ArtifactTrackerDLL/src/PapyrusFunctions.h
Normal file
@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include "ArtifactTracker.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)
|
||||
{
|
||||
const auto formType = static_cast<RE::FormType>(a_formType);
|
||||
|
||||
switch (formType) {
|
||||
|
||||
case RE::FormType::Book:
|
||||
for (auto const& item : ArtifactTracker::validBooks) {
|
||||
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) {
|
||||
a_targetList->AddForm(item.second);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RE::FormType::Misc:
|
||||
for (auto const& item : ArtifactTracker::validMisc) {
|
||||
if (!a_storedList->HasForm(item.second) && !a_foundList->HasForm(item.second)) {
|
||||
a_targetList->AddForm(item.second);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RE::FormType::None:
|
||||
break;
|
||||
|
||||
default:
|
||||
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
|
||||
if (!dataHandler) {
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
for (const auto& form : dataHandler->GetFormArray(formType)) {
|
||||
if (!form || !form->GetPlayable()) {
|
||||
continue;
|
||||
}
|
||||
if (a_storedList->HasForm(form) || a_foundList->HasForm(form)) {
|
||||
continue;
|
||||
}
|
||||
a_targetList->AddForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
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) {
|
||||
a_vm->TraceStack("a_refOrList in AddArtifactsToList is None", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
if (!a_targetList) {
|
||||
a_vm->TraceStack("a_targetList in AddArtifactsToList is None", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_refOrList->Is(RE::FormType::FormList)) {
|
||||
a_refOrList->As<RE::BGSListForm>()->ForEachForm([&](RE::TESForm& a_exform) {
|
||||
const auto refrItem = a_exform.As<RE::TESObjectREFR>();
|
||||
if (refrItem) {
|
||||
AddArtifactsToList(a_vm, a_stackID, nullptr, refrItem, a_targetList, a_excludeList);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
const auto containerRef = a_refOrList->As<RE::TESObjectREFR>();
|
||||
|
||||
if (!containerRef) {
|
||||
a_vm->TraceStack("containerRef in AddArtifactsToList is not a reference", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) {
|
||||
return ArtifactTracker::IsArtifact(&a_exform) && (!a_excludeList || !a_excludeList->HasForm(&a_exform));
|
||||
});
|
||||
|
||||
for (const auto& item : inv) {
|
||||
if (item.second.first > 0) {
|
||||
a_targetList->AddForm(item.first);
|
||||
}
|
||||
}
|
||||
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
inline void SyncCellStorage(RE::StaticFunctionTag*,
|
||||
RE::TESObjectREFR* a_cellStorage,
|
||||
RE::BGSListForm* a_excludeContainers)
|
||||
{
|
||||
ArtifactTracker::SyncCellStorage(a_cellStorage, a_excludeContainers);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
inline void OnItemPickup(RE::StaticFunctionTag*,
|
||||
RE::TESBoundObject* a_item)
|
||||
{
|
||||
ArtifactTracker::OnItemPickup(a_item);
|
||||
}
|
||||
|
||||
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(GetCellStorage);
|
||||
logger::info("Registered GetCellStorage"sv);
|
||||
BIND(SyncCellStorage);
|
||||
logger::info("Registered SyncCellStorage"sv);
|
||||
BIND(OnItemPickup);
|
||||
logger::info("Registered OnItemPickup"sv);
|
||||
}
|
||||
}
|
78
Source/ArtifactTrackerDLL/src/Util.h
Normal file
78
Source/ArtifactTrackerDLL/src/Util.h
Normal file
@ -0,0 +1,78 @@
|
||||
#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;
|
||||
}
|
@ -4,14 +4,10 @@ int function AddAllFormsToList(FormList targetList, int formType, FormList store
|
||||
|
||||
int function AddArtifactsToList(Form refOrList, FormList targetList, FormList excludeList = None) native global
|
||||
|
||||
int function GetItemCountInList(FormList refList, Form baseForm) native global
|
||||
|
||||
ObjectReference function GetCellStorage(ObjectReference ref, FormList refList, Form refToCreate, bool autoCreate = true) native global
|
||||
|
||||
function SyncCellStorage(ObjectReference cellStorage, FormList excludeContainers) native global
|
||||
|
||||
Actor[] function GetPlayerFollowers() native global
|
||||
|
||||
bool function FollowersHaveItem(Form baseForm) native global
|
||||
|
||||
int function AddArtifactsFromFollowersToList(FormList targetList, FormList excludeList = None) native global
|
||||
|
||||
function OnItemPickup(Form item) native global
|
||||
|
@ -1,168 +0,0 @@
|
||||
Scriptname ETR_TrackFoundItems 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
|
||||
int iFollowerIndex = 0
|
||||
bool bAtHome = false
|
||||
bool bRescanHome = false
|
||||
bool bRescanPersistent = false
|
||||
bool bRescanFollowers = false
|
||||
ObjectReference lastDestContainer = None
|
||||
bool lastDestIsPersistent = false
|
||||
int iUpdateCount
|
||||
|
||||
|
||||
event OnInit()
|
||||
OnPlayerLoadGame()
|
||||
endevent
|
||||
|
||||
|
||||
Event OnPlayerLoadGame()
|
||||
AddInventoryEventFilter(ETR_ItemsFound)
|
||||
Location currentLocation = PlayerRef.GetCurrentLocation()
|
||||
bAtHome = currentLocation && currentLocation.HasKeyword(LocTypePlayerHouse)
|
||||
lastDestContainer = None
|
||||
bBusy = false
|
||||
EndEvent
|
||||
|
||||
|
||||
Event OnLocationChange(Location akOldLoc, Location akNewLoc)
|
||||
|
||||
bAtHome = akNewLoc && akNewLoc.HasKeyword(LocTypePlayerHouse)
|
||||
lastDestContainer = None
|
||||
|
||||
bRescanFollowers = true
|
||||
RegisterForSingleUpdate(3.0)
|
||||
|
||||
endEvent
|
||||
|
||||
|
||||
Event OnMenuClose(String MenuName)
|
||||
UnregisterForUpdate()
|
||||
OnUpdate()
|
||||
EndEvent
|
||||
|
||||
|
||||
Event OnUpdate()
|
||||
|
||||
if bRescanFollowers
|
||||
bRescanFollowers = false
|
||||
|
||||
int iCurrentFollowers = 0;
|
||||
Actor[] aFollowers = ETR_Functions.GetPlayerFollowers()
|
||||
int i = aFollowers.length
|
||||
while i > 0
|
||||
i -= 1
|
||||
iCurrentFollowers += aFollowers[i].GetFormID()
|
||||
endwhile
|
||||
|
||||
if iCurrentFollowers == iFollowerIndex
|
||||
return
|
||||
endif
|
||||
|
||||
iFollowerIndex = iCurrentFollowers
|
||||
Debug.Notification("Team changed, rescanning")
|
||||
endif
|
||||
|
||||
if lastDestContainer && lastDestContainer as Actor && (lastDestContainer as Actor).IsPlayerTeammate()
|
||||
lastDestContainer = None
|
||||
bRescanHome = false
|
||||
return
|
||||
endif
|
||||
|
||||
if UI.IsMenuOpen("ContainerMenu")
|
||||
RegisterForMenu("ContainerMenu")
|
||||
return
|
||||
endif
|
||||
|
||||
while bBusy
|
||||
Debug.Notification("Found OnUpdate is busy")
|
||||
Utility.wait(0.5)
|
||||
endwhile
|
||||
|
||||
bBusy = true
|
||||
|
||||
iUpdateCount += 1
|
||||
Debug.Notification("Running Found OnUpdate " + iUpdateCount)
|
||||
|
||||
if bRescanHome
|
||||
bRescanHome = false
|
||||
|
||||
if lastDestContainer && lastDestContainer as PlayerBookShelfContainerScript
|
||||
int iLimit = 10
|
||||
while iLimit >= 0 && (lastDestContainer 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)
|
||||
if ! bRescanPersistent
|
||||
ETR_Functions.AddArtifactsToList(cellStorage, ETR_ItemsStored)
|
||||
endif
|
||||
endif
|
||||
|
||||
if bRescanPersistent
|
||||
bRescanPersistent = false
|
||||
Form[] aContainers = ETR_PersistentStorageList.ToArray()
|
||||
int n = aContainers.length
|
||||
while n > 0
|
||||
n -= 1
|
||||
ETR_Functions.AddArtifactsToList(aContainers[n], ETR_ItemsStored)
|
||||
endwhile
|
||||
endif
|
||||
|
||||
ETR_ItemsFound.Revert()
|
||||
ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored)
|
||||
ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored)
|
||||
|
||||
bBusy = false
|
||||
|
||||
EndEvent
|
||||
|
||||
|
||||
event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
|
||||
|
||||
if akDestContainer
|
||||
if lastDestContainer != akDestContainer
|
||||
lastDestContainer = akDestContainer
|
||||
lastDestIsPersistent = ETR_PersistentStorageList.HasForm(akDestContainer)
|
||||
endif
|
||||
|
||||
; Moving items without latent functions should help with avoiding stack dumps
|
||||
if lastDestIsPersistent
|
||||
bRescanHome = false
|
||||
bRescanPersistent = true
|
||||
RegisterForSingleUpdate(0.5)
|
||||
elseif bAtHome
|
||||
bRescanHome = true
|
||||
bRescanPersistent = false
|
||||
RegisterForSingleUpdate(0.5)
|
||||
elseif PlayerRef.GetItemCount(akBaseItem) == 0 && ! ETR_Functions.FollowersHaveItem(akBaseItem)
|
||||
ETR_ItemsFound.RemoveAddedForm(akBaseItem)
|
||||
ETR_ItemsNew.AddForm(akBaseItem)
|
||||
endif
|
||||
|
||||
elseif bAtHome && akItemReference
|
||||
ETR_ItemsFound.RemoveAddedForm(akBaseItem)
|
||||
ETR_ItemsStored.AddForm(akBaseItem)
|
||||
ETR_Functions.GetCellStorage(PlayerRef, ETR_PersistentStorageList, ETR_CellStorageContainer).AddItem(akBaseItem, 1, true)
|
||||
|
||||
elseif PlayerRef.GetItemCount(akBaseItem) == 0 && ! ETR_Functions.FollowersHaveItem(akBaseItem)
|
||||
ETR_ItemsFound.RemoveAddedForm(akBaseItem)
|
||||
ETR_ItemsNew.AddForm(akBaseItem)
|
||||
endif
|
||||
|
||||
endevent
|
@ -1,68 +0,0 @@
|
||||
Scriptname ETR_TrackNewItems 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
|
||||
|
||||
GlobalVariable Property ETR_NotifyNewArtifact Auto
|
||||
|
||||
|
||||
event OnInit()
|
||||
OnPlayerLoadGame()
|
||||
endevent
|
||||
|
||||
|
||||
event OnPlayerLoadGame()
|
||||
|
||||
AddInventoryEventFilter(ETR_ItemsNew)
|
||||
|
||||
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)
|
||||
|
||||
endevent
|
||||
|
||||
|
||||
event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
|
||||
|
||||
ETR_ItemsNew.RemoveAddedForm(akBaseItem)
|
||||
ETR_ItemsFound.AddForm(akBaseItem)
|
||||
ETR_ItemsStored.RemoveAddedForm(akBaseItem)
|
||||
|
||||
if ETR_NotifyNewArtifact.Value
|
||||
Debug.Notification("New artifact acquired: " + akBaseItem.GetName())
|
||||
endif
|
||||
|
||||
endevent
|
@ -29,6 +29,40 @@ 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
|
||||
@ -108,6 +142,7 @@ Event OnUpdate()
|
||||
|
||||
ETR_ItemsStored.Revert()
|
||||
Form[] aContainers = ETR_PersistentStorageList.ToArray()
|
||||
|
||||
int n = aContainers.length
|
||||
while n > 0
|
||||
n -= 1
|
||||
@ -117,7 +152,6 @@ Event OnUpdate()
|
||||
ETR_ItemsFound.Revert()
|
||||
ETR_Functions.AddArtifactsToList(PlayerRef, ETR_ItemsFound, ETR_ItemsStored)
|
||||
ETR_Functions.AddArtifactsFromFollowersToList(ETR_ItemsFound, ETR_ItemsStored)
|
||||
|
||||
endif
|
||||
|
||||
bBusy = false
|
||||
@ -139,41 +173,7 @@ event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemRefere
|
||||
RegisterForSingleUpdate(0.5)
|
||||
endif
|
||||
elseif bAtHome
|
||||
ObjectReference cellStorage = ETR_Functions.GetCellStorage(PlayerRef, ETR_PersistentStorageList, ETR_CellStorageContainer)
|
||||
if cellStorage.GetItemCount(akBaseItem)
|
||||
ETR_Functions.SyncCellStorage(cellStorage, ETR_PersistentStorageList)
|
||||
if ETR_Functions.GetItemCountInList(ETR_PersistentStorageList, akBaseItem) == 0
|
||||
ETR_ItemsStored.RemoveAddedForm(akBaseItem)
|
||||
ETR_ItemsFound.AddForm(akBaseItem)
|
||||
endif
|
||||
endif
|
||||
ETR_Functions.OnItemPickup(akBaseItem)
|
||||
endif
|
||||
|
||||
endevent
|
||||
|
||||
|
||||
state AtHome
|
||||
|
||||
; The item is already registered as stored, and we just stored more
|
||||
event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
|
||||
|
||||
if akDestContainer
|
||||
if lastDestContainer != akDestContainer
|
||||
lastDestContainer = akDestContainer
|
||||
lastDestIsPersistent = ETR_PersistentStorageList.HasForm(akDestContainer)
|
||||
endif
|
||||
if ! lastDestIsPersistent
|
||||
bRescanHome = true
|
||||
bRescanPersistent = false
|
||||
RegisterForSingleUpdate(0.5)
|
||||
endif
|
||||
elseif akItemReference
|
||||
ObjectReference cellStorage = ETR_Functions.GetCellStorage(PlayerRef, ETR_PersistentStorageList, ETR_CellStorageContainer)
|
||||
if cellStorage.GetItemCount(akBaseItem) == 0
|
||||
cellStorage.AddItem(akBaseItem, 1, true)
|
||||
endif
|
||||
endif
|
||||
|
||||
endevent
|
||||
|
||||
endstate
|
||||
|
Loading…
Reference in New Issue
Block a user