#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(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()->ForEachForm([&](RE::TESForm& a_exform) { const auto refrItem = a_exform.As(); 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(); 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(); 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 cellItems; 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_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 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::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); } }