#pragma once namespace Papyrus::ArtifactTracker { inline bool is_excluded(RE::TESForm* a_form, RE::TESForm* a_excludeForm = NULL) { if (!a_excludeForm) { return false; } const RE::BGSListForm* list = a_excludeForm->As(); if (list) { if (list->HasForm(a_form)) { return true; } else { bool isExcluded = false; list->ForEachForm([&](RE::TESForm& a_exform) { const auto exlist = a_exform.As(); if (exlist) { if (exlist->forms.size() > 0) { if (exlist->forms[0]->Is(RE::FormType::Keyword) ? a_form->HasKeywordInList(exlist, false) : exlist->HasForm(a_form)) { isExcluded = true; return false; } } } else { const auto exkeyword = a_exform.As(); if (exkeyword && a_form->As()->HasKeyword(exkeyword)) { isExcluded = true; return false; } } return true; }); return isExcluded; } } const RE::BGSKeyword* keyword = a_excludeForm->As(); if (keyword) { if (a_form->As()->HasKeyword(keyword)) { return true; } } return false; } 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::TESForm* a_excludeForm = NULL) { const auto formType = static_cast(a_formType); switch (formType) { case RE::FormType::Book: for (auto const& item : BookCheck::GetBookList()) { if (!a_excludeForm || !is_excluded(item.second, a_excludeForm)) { a_targetList->AddForm(item.second); } } break; case RE::FormType::Misc: for (auto const& item : MiscCheck::GetMiscList()) { if (!a_excludeForm || !is_excluded(item.second, a_excludeForm)) { 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_excludeForm && is_excluded(form, a_excludeForm)) { 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::TESForm* a_excludeForm = 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()->ForEachForm([&](RE::TESForm& a_exform) { const auto refrItem = a_exform.As(); if (refrItem) { AddArtifactsToList(a_vm, a_stackID, {}, refrItem, a_targetList, a_excludeForm); } return true; }); return a_targetList->forms.size(); } const auto containerRef = a_refOrList->As(); if (!containerRef) { a_vm->TraceStack("containerRef in AddArtifactsToList is not a reference", a_stackID); return 0; } auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) { return is_artifact(&a_exform) && !is_excluded(&a_exform, a_excludeForm); }); 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(); 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::TESForm* a_excludeForm = NULL) { if (!a_cellStorage) { return; } std::unordered_map cellItems; const auto storageBase = a_cellStorage->GetBaseObject(); const auto cell = a_cellStorage->GetParentCell(); const auto inv = a_cellStorage->GetInventory(); for (const RE::NiPointer 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()->GetRace()->formID == 0x0010760A)) { if (a_ref->GetBaseObject()->formID == storageBase->formID) { 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) && !is_excluded(item, a_excludeForm)) { 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()) { if (!a_excludeForm || !is_excluded(baseObj, a_excludeForm)) { 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 GetPlayerFollowers(RE::StaticFunctionTag*) { std::vector 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::TESForm* a_excludeForm = NULL) { for (const auto& actor : GetPlayerFollowers(nullptr)) { auto inv = actor->GetInventory([&](RE::TESBoundObject& a_exform) { return is_artifact(&a_exform) && !is_excluded(&a_exform, a_excludeForm); }); 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); } }