# include "ArtifactTracker.h"
# include "BookCheck.h"
# include "EventListener.h"
# include "Util.h"
using namespace SKSE ;
using namespace SKSE : : log ;
namespace ArtifactTracker
{
bool g_bLoaded = false ;
bool g_bSaveLoaded = true ;
bool g_bHomeContainer = false ;
bool g_bBookShelf = false ;
bool g_bTakeAll = false ;
bool g_bNotifyNewArtifact = false ;
bool g_bWarnMissingMoreHUD = true ;
std : : uint32_t g_bTakeAllCount = 0 ;
std : : int32_t g_iFollowerIndex = 0 ;
RE : : TESBoundObject * g_cellContainer ;
RE : : BGSListForm * g_listNew ;
RE : : BGSListForm * g_listStored ;
RE : : BGSListForm * g_listFound ;
RE : : BGSListForm * g_persistentStorage ;
RE : : BGSKeyword * g_homeKeyword ;
std : : unordered_map < RE : : FormID , RE : : TESForm * > g_artifactMap ;
std : : unordered_set < RE : : FormType > g_artifactFormTypes ;
std : : unordered_set < RE : : FormType > g_artifactAllFormTypes ;
std : : unordered_map < RE : : FormID , RE : : TESObjectREFR * > g_persistentMap ;
RE : : TESObjectREFR * g_cellStorage ;
const SKSE : : LoadInterface * g_loadInterface ;
bool Init ( bool bKID )
{
if ( g_bLoaded ) {
return true ;
}
const auto dataHandler = RE : : TESDataHandler : : GetSingleton ( ) ;
if ( ! dataHandler ) {
// Called before kDataLoaded?
log : : error ( " DataHandler is not initialized. " ) ;
return false ;
}
SKSE : : GetModCallbackEventSource ( ) - > RemoveEventSink ( EventListener : : GetSingleton ( ) ) ;
g_cellContainer = dataHandler - > LookupForm ( 0x804 , " Artifact Tracker.esp " ) - > As < RE : : TESBoundObject > ( ) ; // ETR_CellStorageContainer
g_listNew = dataHandler - > LookupForm < RE : : BGSListForm > ( 0x800 , " Artifact Tracker.esp " ) ; // ETR_ItemsNew
g_listStored = dataHandler - > LookupForm < RE : : BGSListForm > ( 0x801 , " Artifact Tracker.esp " ) ; // ETR_ItemsStored
g_listFound = dataHandler - > LookupForm < RE : : BGSListForm > ( 0x802 , " Artifact Tracker.esp " ) ; // ETR_ItemsFound
g_persistentStorage = dataHandler - > LookupForm < RE : : BGSListForm > ( 0x803 , " Artifact Tracker.esp " ) ; // ETR_PersistentStorageList
g_homeKeyword = dataHandler - > LookupForm < RE : : BGSKeyword > ( 0xFC1A3 , " Skyrim.esm " ) ; // LocTypePlayerHouse
const auto extraArtifactKeyword = dataHandler - > LookupForm < RE : : BGSKeyword > ( 0xAFC110 , " Update.esm " ) ; // ETR_ExtraArtifact
const auto notArtifactKeyword = dataHandler - > LookupForm < RE : : BGSKeyword > ( 0xAFC111 , " Update.esm " ) ; // ETR_NotArtifact
const auto npcRaceKeyword = dataHandler - > LookupForm < RE : : BGSKeyword > ( 0x13794 , " Skyrim.esm " ) ; // ActorTypeNPC
if ( ! g_cellContainer | | ! g_listNew | | ! g_listStored | | ! g_listFound | | ! g_persistentStorage | | ! g_homeKeyword | | ! extraArtifactKeyword | | ! notArtifactKeyword | | ! npcRaceKeyword ) {
log : : warn ( " Unable to load data from Artifact Tracker.esp " ) ;
RE : : DebugMessageBox ( " Unable to load data from Artifact Tracker.esp, the mod is disabled. " ) ;
return false ;
}
std : : map < std : : string , bool > settings {
{ " DumpItemList " , false } ,
{ " NewArtifactNotifications " , false } ,
{ " WarnMissingMoreHUD " , true } ,
{ " WarnMissingKID " , true } ,
} ;
LoadINI ( & settings , " Data/SKSE/Plugins/ArtifactTracker.ini " ) ;
g_bNotifyNewArtifact = settings . at ( " NewArtifactNotifications " ) ;
g_bWarnMissingMoreHUD = settings . at ( " WarnMissingMoreHUD " ) ;
// Preloading item lists
g_artifactAllFormTypes . insert ( RE : : FormType : : Weapon ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : Armor ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : Book ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : Misc ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : AlchemyItem ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : Ingredient ) ;
g_artifactAllFormTypes . insert ( RE : : FormType : : SoulGem ) ;
g_artifactFormTypes . insert ( RE : : FormType : : Weapon ) ;
for ( const auto & form : dataHandler - > GetFormArray < RE : : TESObjectWEAP > ( ) ) {
if ( form - > GetPlayable ( ) & & ! form - > IsBound ( ) & & ! form - > weaponData . flags . all ( RE : : TESObjectWEAP : : Data : : Flag : : kCantDrop ) ) {
if ( ( ! form - > HasKeyword ( notArtifactKeyword ) | | form - > HasKeyword ( extraArtifactKeyword ) ) & & strlen ( form - > GetName ( ) ) > 0 ) {
g_artifactMap [ form - > formID ] = form ;
}
}
}
g_artifactMap . erase ( 0x1F4 ) ; // Unarmed
g_artifactFormTypes . insert ( RE : : FormType : : Armor ) ;
for ( const auto & form : dataHandler - > GetFormArray < RE : : TESObjectARMO > ( ) ) {
if ( form - > GetPlayable ( ) & & form - > race & & ( form - > race - > formID = = 0x19 | | form - > race - > HasKeyword ( npcRaceKeyword ) ) ) {
if ( ( ! form - > HasKeyword ( notArtifactKeyword ) | | form - > HasKeyword ( extraArtifactKeyword ) ) & & strlen ( form - > GetName ( ) ) > 0 ) {
g_artifactMap [ form - > formID ] = form ;
}
}
}
g_artifactMap . erase ( 0xD64 ) ; // SkinNaked
g_artifactMap . erase ( 0x69CE3 ) ; // SkinNakedBeast
g_artifactMap . erase ( 0xCDD86 ) ; // SkinNakedWerewolfBeast
g_artifactFormTypes . insert ( RE : : FormType : : Book ) ;
for ( const auto & form : dataHandler - > GetFormArray < RE : : TESObjectBOOK > ( ) ) {
if ( ( form - > HasKeyword ( extraArtifactKeyword ) | | ( ! form - > TeachesSpell ( ) & & BookCheck : : IsBook ( form ) ) ) & & ! form - > HasKeyword ( notArtifactKeyword ) ) {
g_artifactMap [ form - > formID ] = form ;
}
}
g_artifactFormTypes . insert ( RE : : FormType : : Misc ) ;
for ( const auto & form : dataHandler - > GetFormArray < RE : : TESObjectMISC > ( ) ) {
if ( form - > GetPlayable ( ) & & ( form - > GetNumKeywords ( ) = = 0 | | ( ! bKID & & form - > HasKeyword ( extraArtifactKeyword ) ) | | ( bKID & & ( ! form - > HasKeyword ( notArtifactKeyword ) | | form - > HasKeyword ( extraArtifactKeyword ) ) ) ) & & strlen ( form - > GetName ( ) ) > 0 ) {
g_artifactMap [ form - > formID ] = form ;
}
}
g_artifactMap . erase ( 0xA ) ; // Lockpick
g_artifactMap . erase ( 0xF ) ; // Gold
for ( const auto & form : dataHandler - > GetFormArray < RE : : AlchemyItem > ( ) ) {
if ( form - > HasKeyword ( extraArtifactKeyword ) & & ! form - > HasKeyword ( notArtifactKeyword ) ) {
g_artifactMap [ form - > formID ] = form ;
g_artifactFormTypes . insert ( RE : : FormType : : AlchemyItem ) ;
}
}
for ( const auto & form : dataHandler - > GetFormArray < RE : : IngredientItem > ( ) ) {
if ( form - > HasKeyword ( extraArtifactKeyword ) & & ! form - > HasKeyword ( notArtifactKeyword ) ) {
g_artifactMap [ form - > formID ] = form ;
g_artifactFormTypes . insert ( RE : : FormType : : Ingredient ) ;
}
}
for ( const auto & form : dataHandler - > GetFormArray < RE : : TESSoulGem > ( ) ) {
if ( form - > HasKeyword ( extraArtifactKeyword ) & & ! form - > HasKeyword ( notArtifactKeyword ) ) {
g_artifactMap [ form - > formID ] = form ;
g_artifactFormTypes . insert ( RE : : FormType : : SoulGem ) ;
}
}
EventListener : : Install ( ) ;
OnGameLoad ( ) ; // covers new game and coc'ing from the main menu
g_bLoaded = true ;
if ( bKID ) {
log : : info ( " Keyword Item Distributor is detected. " ) ;
} else {
log : : info ( " Keyword Item Distributor has NOT been detected, using the baseline configuration. " ) ;
}
RE : : ConsoleLog : : GetSingleton ( ) - > Print ( std : : format ( " Artifact Tracker registered {} items. " , g_artifactMap . size ( ) ) . c_str ( ) ) ;
log : : info ( " Total artifacts: {} " , g_artifactMap . size ( ) ) ;
if ( settings . at ( " DumpItemList " ) ) {
for ( const auto & item : g_artifactMap ) {
log : : info ( " [{:08X}] {} " , item . second - > formID , item . second - > GetName ( ) ) ;
}
}
if ( dataHandler - > LookupLoadedModByName ( " DBM_RelicNotifications.esp " ) ) {
RE : : DebugMessageBox ( " Artifact Tracker is incompatible with The Curator's Companion. " ) ;
}
if ( ! bKID & & settings . at ( " WarnMissingKID " ) ) {
RE : : DebugMessageBox ( " Artifact Tracker requires Keyword Item Distributor. If its absence is intentional, set WarnMissingKID=false in ArtifactTracker.ini. " ) ;
}
return true ;
}
bool IsArtifact ( const RE : : TESForm * a_form )
{
return a_form & & g_artifactFormTypes . contains ( a_form - > GetFormType ( ) ) & & g_artifactMap . contains ( a_form - > formID ) ;
}
RE : : TESForm * GetArtifactByID ( const 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
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 ;
} ) ;
std : : uint32_t savedCount = g_listStored - > forms . size ( ) + g_listFound - > forms . size ( ) + g_listNew - > forms . size ( ) ;
if ( savedCount ! = g_artifactMap . size ( ) ) {
ListRevert ( g_listNew ) ;
}
RescanStoredArtifacts ( ) ;
RescanFoundArtifacts ( ) ;
RescanNewArtifacts ( ) ;
const auto vm = RE : : BSScript : : Internal : : VirtualMachine : : GetSingleton ( ) ;
RE : : BSTSmartPointer < RE : : BSScript : : IStackCallbackFunctor > stackCallback ;
bool bMoreHUDInstalled = false ;
if ( const auto pluginInfo = g_loadInterface - > GetPluginInfo ( " Ahzaab's moreHUD Plugin " ) ; pluginInfo ) {
if ( ! g_bLoaded ) log : : info ( " Detected {} v{} " , pluginInfo - > name , pluginInfo - > version ) ;
if ( pluginInfo - > version = = 0 ) {
log : : error ( " MoreHUD has not been detected. " ) ;
} else if ( pluginInfo - > version < 30800 ) {
log : : error ( " MoreHUD is outdated. " ) ;
} else if ( vm - > TypeIsValid ( " AhzMoreHud " ) ) {
if ( ! g_bLoaded ) log : : info ( " Registering icons in MoreHUD... " ) ;
vm - > DispatchStaticCall ( " AhzMoreHud " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmNew " , std : : move ( g_listNew ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " AhzMoreHud " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmFound " , std : : move ( g_listFound ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " AhzMoreHud " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmDisp " , std : : move ( g_listStored ) ) , stackCallback ) ;
bMoreHUDInstalled = true ;
} else {
log : : error ( " MoreHUD has not been installed correctly. " ) ;
}
} else if ( ! g_bLoaded ) {
log : : error ( " MoreHUD has not been detected. " ) ;
}
bool bMoreHUDInvInstalled = false ;
if ( const auto pluginInfo = g_loadInterface - > GetPluginInfo ( " Ahzaab's moreHUD Inventory Plugin " ) ; pluginInfo ) {
if ( ! g_bLoaded ) log : : info ( " Detected {} v{} " , pluginInfo - > name , pluginInfo - > version ) ;
if ( pluginInfo - > version = = 0 ) {
log : : error ( " MoreHUD Inventory Edition has not been detected. " ) ;
} else if ( pluginInfo - > version < 10017 ) {
log : : error ( " MoreHUD Inventory Edition is outdated. " ) ;
} else if ( vm - > TypeIsValid ( " AhzMoreHudIE " ) ) {
if ( ! g_bLoaded ) log : : info ( " Registering icons in MoreHUD Inventory Edition... " ) ;
vm - > DispatchStaticCall ( " AhzMoreHudIE " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmNew " , std : : move ( g_listNew ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " AhzMoreHudIE " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmFound " , std : : move ( g_listFound ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " AhzMoreHudIE " , " RegisterIconFormList " , RE : : MakeFunctionArguments < RE : : BSString , RE : : BGSListForm * > ( " dbmDisp " , std : : move ( g_listStored ) ) , stackCallback ) ;
bool bMoreHUDInvInstalled = true ;
} else {
log : : error ( " MoreHUD Inventory Edition has not been installed correctly. " ) ;
}
} else if ( ! g_bLoaded ) {
log : : error ( " MoreHUD Inventory Edition has not been detected. " ) ;
}
if ( g_bWarnMissingMoreHUD & & ! bMoreHUDInstalled & & ! bMoreHUDInvInstalled ) {
RE : : DebugMessageBox ( " Artifact Tracker requires up-to-date MoreHUD and/or MoreHUD Inventory Edition. If their absence is intentional, set WarnMissingMoreHUD=false in ArtifactTracker.ini. " ) ;
}
// TODO: Uncomment when/if QuickLoot EE brings back registering formlists
/*
if ( const auto pluginInfo = g_loadInterface - > GetPluginInfo ( " QuickLootEE " ) ; pluginInfo ) {
if ( ! g_bLoaded ) log : : info ( " Detected {} v{} " , pluginInfo - > name , pluginInfo - > version ) ;
if ( pluginInfo - > version = = 0 ) {
log : : error ( " QuickLoot EE has not been detected. " ) ;
} else if ( vm - > TypeIsValid ( " QuickLootEE " ) ) {
if ( ! g_bLoaded ) log : : info ( " Registering icons with QuickLootEE... " ) ;
vm - > DispatchStaticCall ( " QuickLootEE " , " RegisterNewItemsList " , RE : : MakeFunctionArguments < RE : : BGSListForm * > ( std : : move ( g_listNew ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " QuickLootEE " , " RegisterFoundItemsList " , RE : : MakeFunctionArguments < RE : : BGSListForm * > ( std : : move ( g_listFound ) ) , stackCallback ) ;
vm - > DispatchStaticCall ( " QuickLootEE " , " RegisterDisplayedItemsList " , RE : : MakeFunctionArguments < RE : : BGSListForm * > ( std : : move ( g_listStored ) ) , stackCallback ) ;
} else {
log : : error ( " QuickLoot EE has not been installed correctly. " ) ;
}
} else if ( ! g_bLoaded ) {
log : : error ( " QuickLoot EE has not been detected. " ) ;
}
*/
}
void SetContainerMode ( const bool bOpening )
{
if ( bOpening ) {
const auto refr = RE : : TESObjectREFR : : LookupByHandle ( RE : : ContainerMenu : : GetTargetRefHandle ( ) ) ;
g_bHomeContainer = IsHome ( )
& & refr
& & IsInSameCell ( refr . get ( ) )
& & ! g_persistentMap . contains ( refr . get ( ) - > formID ) ;
g_bBookShelf = g_bHomeContainer & & refr - > GetBaseObject ( ) - > formID = = 0xDC9E7 ;
# ifdef _DEBUG
if ( g_bHomeContainer ) {
RE : : DebugNotification ( " Delayed processing enabled " ) ;
}
# endif
} else if ( g_bHomeContainer ) {
g_bHomeContainer = false ;
if ( g_bBookShelf ) {
g_bBookShelf = false ;
std : : thread ( [ ] ( ) {
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 1200 ) ) ;
ArtifactTracker : : SyncCellStorage ( ) ;
} ) . detach ( ) ;
} else {
SyncCellStorage ( ) ;
}
}
}
bool IsHome ( )
{
return ( bool ) g_cellStorage ;
}
bool ToggleHomeMode ( RE : : TESObjectREFR * cellStorage )
{
if ( cellStorage ) {
g_bHomeContainer = false ;
g_cellStorage = cellStorage ;
RE : : ScriptEventSourceHolder : : GetSingleton ( ) - > AddEventSink < RE : : TESActivateEvent > ( EventListener : : GetSingleton ( ) ) ;
# ifdef _DEBUG
log : : info ( " Home mode ON " ) ;
# endif
return true ;
} else if ( g_cellStorage ) {
g_bHomeContainer = false ;
g_cellStorage = nullptr ;
RE : : ScriptEventSourceHolder : : GetSingleton ( ) - > RemoveEventSink < RE : : TESActivateEvent > ( EventListener : : GetSingleton ( ) ) ;
# ifdef _DEBUG
log : : info ( " Home mode OFF " ) ;
# endif
}
return false ;
}
bool IsValidContainer ( RE : : TESObjectREFR * a_ref )
{
if ( ! a_ref | | a_ref - > IsMarkedForDeletion ( ) ) {
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 = = 0x10760A ) ;
}
void OnCellEnter ( const RE : : FormID a_formID )
{
if ( ! g_bSaveLoaded ) {
// Cell load events fire before formlists are loaded from savegame
return ;
}
RE : : TESObjectCELL * cell = RE : : TESForm : : LookupByID < RE : : TESObjectCELL > ( a_formID ) ;
RE : : BGSLocation * location = cell ? cell - > GetLocation ( ) : nullptr ;
if ( ! cell | | ! location | | ! cell - > IsInteriorCell ( ) | | ! location - > HasKeyword ( g_homeKeyword ) ) {
if ( IsHome ( ) ) {
RE : : ScriptEventSourceHolder : : GetSingleton ( ) - > RemoveEventSink < RE : : TESCellFullyLoadedEvent > ( EventListener : : GetSingleton ( ) ) ;
ToggleHomeMode ( nullptr ) ;
}
return ;
}
RE : : TESObjectREFR * cellStorage = nullptr ;
bool bHasDupes = false ;
g_persistentStorage - > ForEachForm ( [ & ] ( RE : : TESForm & a_form ) {
const auto refr = a_form . As < RE : : TESObjectREFR > ( ) ;
if ( refr & & refr - > GetParentCell ( ) - > formID = = a_formID & & refr - > GetBaseObject ( ) = = g_cellContainer ) {
if ( cellStorage ) {
log : : warn ( " Multiple cell storages detected in {} " , cell - > GetName ( ) ) ;
bHasDupes = true ;
} else {
cellStorage = refr ;
}
}
return true ;
} ) ;
# ifdef _DEBUG
if ( cellStorage ) {
log : : info ( " Found cell storage in {} (first pass) " , cell - > GetName ( ) ) ;
}
# endif
ToggleHomeMode ( cellStorage ) ;
if ( ! cellStorage | | bHasDupes ) {
RE : : ScriptEventSourceHolder : : GetSingleton ( ) - > AddEventSink < RE : : TESCellFullyLoadedEvent > ( EventListener : : GetSingleton ( ) ) ;
}
}
void OnCellEnter ( const RE : : BGSLocation * location , const RE : : TESObjectCELL * cell )
{
RE : : ScriptEventSourceHolder : : GetSingleton ( ) - > RemoveEventSink < RE : : TESCellFullyLoadedEvent > ( EventListener : : GetSingleton ( ) ) ;
if ( ! g_bSaveLoaded ) {
// Cell load events fire before formlists are loaded from savegame, duh!
return ;
}
if ( ! location | | ! cell - > IsInteriorCell ( ) | | cell ! = RE : : PlayerCharacter : : GetSingleton ( ) - > GetParentCell ( ) | | ! location - > HasKeyword ( g_homeKeyword ) ) {
ToggleHomeMode ( nullptr ) ;
return ;
}
RE : : TESObjectREFR * cellStorage = nullptr ;
std : : vector < RE : : TESObjectREFR * > dupes ;
cell - > ForEachReference ( [ & cellStorage , & dupes ] ( RE : : TESObjectREFR & a_ref ) {
if ( a_ref . GetBaseObject ( ) = = g_cellContainer & & ! a_ref . IsMarkedForDeletion ( ) ) {
if ( cellStorage ) {
dupes . push_back ( & a_ref ) ;
} else {
cellStorage = & a_ref ;
}
}
return true ;
} ) ;
for ( int i = 0 ; i < dupes . size ( ) ; i + + ) {
log : : warn ( " Removing duplicate storage {:08X} " , dupes [ i ] - > formID ) ;
g_persistentMap . erase ( dupes [ i ] - > formID ) ;
ListRemoveItem ( g_persistentStorage , dupes [ i ] ) ;
dupes [ i ] - > Disable ( ) ;
dupes [ i ] - > SetDelete ( true ) ;
}
dupes . clear ( ) ;
if ( cellStorage ) {
# ifdef _DEBUG
log : : info ( " Found cell storage in {} (second pass) " , cell - > GetName ( ) ) ;
# endif
if ( ! g_persistentMap . contains ( cellStorage - > formID ) ) {
g_persistentStorage - > AddForm ( cellStorage ) ;
g_persistentMap [ cellStorage - > formID ] = cellStorage ;
}
ToggleHomeMode ( cellStorage ) ;
SyncCellStorage ( ) ;
return ;
}
# ifdef _DEBUG
log : : info ( " Adding new storage in {} " , cell - > GetName ( ) ) ;
# endif
SKSE : : GetTaskInterface ( ) - > AddTask ( [ ] ( ) {
const auto cellStorage = RE : : PlayerCharacter : : GetSingleton ( ) - > PlaceObjectAtMe ( g_cellContainer , true ) . get ( ) ;
if ( cellStorage ) {
# ifdef _DEBUG
log : : info ( " Created storage {:08X} " , cellStorage - > formID ) ;
# endif
cellStorage - > Disable ( ) ;
g_persistentStorage - > AddForm ( cellStorage ) ;
g_persistentMap [ cellStorage - > formID ] = cellStorage ;
ToggleHomeMode ( cellStorage ) ;
SyncCellStorage ( ) ;
} else {
log : : error ( " Failed to create cell storage in OnCellEnter " ) ;
ToggleHomeMode ( nullptr ) ;
}
} ) ;
}
void SyncCellStorage ( const RE : : TESObjectREFR * a_ignoreRef )
{
if ( ! IsHome ( ) ) {
# ifdef _DEBUG
log : : info ( " SyncCellStorage called while not at home " ) ;
# endif
return ;
}
# ifdef _DEBUG
log : : info ( " Running SyncCellStorage " ) ;
# endif
const RE : : FormID ignoreFormID = a_ignoreRef ? a_ignoreRef - > formID : NULL ;
SKSE : : GetTaskInterface ( ) - > AddTask ( [ ignoreFormID ] ( ) {
std : : unordered_set < RE : : FormID > cellItems ;
const auto cell = g_cellStorage - > GetParentCell ( ) ;
const auto inv = g_cellStorage - > GetInventory ( ) ;
cell - > ForEachReference ( [ & ] ( RE : : TESObjectREFR & a_ref ) {
if ( ignoreFormID & & ignoreFormID = = a_ref . formID ) {
return true ;
}
const auto baseObj = a_ref . GetBaseObject ( ) ;
if ( IsValidContainer ( & a_ref ) ) {
if ( g_cellContainer = = baseObj | | baseObj - > formID = = 0xDC9E7 | | g_persistentMap . contains ( a_ref . formID ) ) { // skip persistent and PlayerBookShelfContainer
return true ;
}
const auto contInv = a_ref . GetInventory ( [ & ] ( RE : : TESBoundObject & a_object ) - > bool {
return ! cellItems . contains ( a_object . formID ) & & g_artifactAllFormTypes . contains ( a_object . GetFormType ( ) ) ;
} ) ;
for ( const auto & [ item , data ] : contInv ) {
if ( data . first > 0 ) {
cellItems . insert ( item - > formID ) ;
if ( inv . find ( item ) = = inv . end ( ) ) {
g_cellStorage - > AddObjectToContainer ( item , nullptr , 1 , nullptr ) ;
}
if ( IsArtifact ( item ) & & ! g_listStored - > HasForm ( item ) ) {
ListRemoveItem ( g_listNew , item ) ;
ListRemoveItem ( g_listFound , item ) ;
g_listStored - > AddForm ( item ) ;
}
}
}
return true ;
}
if ( ! g_artifactAllFormTypes . contains ( baseObj - > GetFormType ( ) ) | | a_ref . IsDisabled ( ) | | a_ref . IsMarkedForDeletion ( ) | | cellItems . contains ( baseObj - > formID ) ) {
return true ;
}
cellItems . insert ( baseObj - > formID ) ;
if ( inv . find ( baseObj ) = = inv . end ( ) ) {
g_cellStorage - > AddObjectToContainer ( baseObj , nullptr , 1 , nullptr ) ;
}
if ( IsArtifact ( baseObj ) & & ! g_listStored - > HasForm ( baseObj ) ) {
ListRemoveItem ( g_listNew , baseObj ) ;
ListRemoveItem ( g_listFound , baseObj ) ;
g_listStored - > AddForm ( baseObj ) ;
}
return true ;
} ) ;
for ( const auto & [ item , data ] : inv ) {
const auto & [ count , entry ] = data ;
if ( count > 0 & & ! cellItems . contains ( item - > formID ) ) {
g_cellStorage - > RemoveItem ( item , count , RE : : ITEM_REMOVE_REASON : : kRemove , nullptr , nullptr ) ;
if ( IsArtifact ( item ) & & ! RefListHasItem ( g_persistentStorage , item - > formID ) ) {
ListRemoveItem ( g_listStored , item ) ;
if ( GetItemCount ( RE : : PlayerCharacter : : GetSingleton ( ) , item ) | | FollowersHaveItem ( item ) ) {
ListRemoveItem ( g_listNew , item ) ;
g_listFound - > AddForm ( item ) ;
} else {
ListRemoveItem ( g_listFound , item ) ;
g_listNew - > AddForm ( item ) ;
}
}
}
}
cellItems . clear ( ) ;
} ) ;
}
void OnContainerChanged ( const RE : : TESContainerChangedEvent * a_event , RE : : TESForm * form )
{
if ( a_event - > newContainer ) { // added to a container or actor
if ( a_event - > newContainer = = 0x14 ) { // acquired by player
if ( a_event - > oldContainer ) {
if ( const auto it = g_persistentMap . find ( a_event - > oldContainer ) ; it ! = g_persistentMap . end ( ) ) { // moved from a persistent container
const auto ref = it - > second ;
if ( ref & & ! GetItemCount ( ref , form ) ) { // no items left in the container
for ( const auto & persref : g_persistentMap ) {
if ( persref . second ! = ref ) {
if ( GetItemCount ( persref . second , form ) ) {
// if other containers have it, do nothing
return ;
}
}
}
ListRemoveItem ( g_listStored , form ) ;
g_listFound - > AddForm ( form ) ;
}
return ;
} else if ( g_cellStorage ) { // taken from a container at home
if ( ! g_bHomeContainer ) {
const auto container = RE : : TESForm : : LookupByID < RE : : TESObjectREFR > ( a_event - > oldContainer ) ;
if ( container & & ! GetItemCount ( container , form ) ) {
SyncCellStorage ( container ) ;
}
}
return ;
}
}
if ( ! g_listStored - > HasForm ( form ) & & ! g_listFound - > HasForm ( form ) ) { // it's a new item, move it to found
ListRemoveItem ( g_listNew , form ) ;
g_listFound - > AddForm ( form ) ;
if ( g_bNotifyNewArtifact ) {
if ( g_bTakeAll ) {
g_bTakeAllCount + + ;
} else {
//RE::DebugNotification(fmt::format("New artifact acquired: {}", form->GetName()).c_str());
RE : : BSTSmartPointer < RE : : BSScript : : IStackCallbackFunctor > stackCallback ;
RE : : BSScript : : Internal : : VirtualMachine : : GetSingleton ( ) - > DispatchStaticCall ( " ETR_NewArtifactNotification " , " Show " , RE : : MakeFunctionArguments < RE : : TESForm * > ( std : : move ( form ) ) , stackCallback ) ;
}
}
}
} else if ( g_cellStorage & & g_cellStorage - > formID = = a_event - > newContainer ) {
return ; // ignore cell storage
} else if ( g_persistentMap . contains ( a_event - > newContainer ) ) { // stored in a registered persistent container
if ( ! g_listStored - > HasForm ( form ) ) {
ListRemoveItem ( g_listFound , form ) ;
g_listStored - > AddForm ( form ) ;
}
} else {
const auto ref = RE : : TESForm : : LookupByID ( a_event - > newContainer ) ;
if ( ref - > Is ( RE : : FormType : : ActorCharacter ) ) {
if ( ref - > As < RE : : Actor > ( ) - > IsPlayerTeammate ( ) ) { // acquired by companion
if ( ! g_listFound - > HasForm ( form ) & & ! g_listStored - > HasForm ( form ) ) {
ListRemoveItem ( g_listNew , form ) ;
g_listFound - > AddForm ( form ) ;
}
}
} else {
const auto container = ref - > As < RE : : TESObjectREFR > ( ) ;
if ( container ) {
if ( g_cellStorage & & IsInSameCell ( container ) ) { // stored at home
if ( ! g_bHomeContainer & & ! g_listStored - > HasForm ( form ) ) {
ListRemoveItem ( g_listFound , form ) ;
ListRemoveItem ( g_listNew , form ) ;
g_listStored - > AddForm ( form ) ;
}
} else if ( a_event - > oldContainer = = 0x14 & & ! g_listStored - > HasForm ( form ) ) {
SKSE : : GetTaskInterface ( ) - > AddTask ( [ form ] ( ) {
if ( ! GetItemCount ( RE : : PlayerCharacter : : GetSingleton ( ) , form ) & & ! FollowersHaveItem ( form ) ) {
// disposed by player
ListRemoveItem ( g_listFound , form ) ;
g_listNew - > AddForm ( form ) ;
}
} ) ;
}
}
}
}
} else if ( a_event - > oldContainer ) { // removed from container
if ( g_cellStorage & & a_event - > reference ) { // dropped or placed on rack at home by any actor
if ( a_event - > oldContainer ! = 0x14 ) {
const auto ref = RE : : TESForm : : LookupByID < RE : : TESObjectREFR > ( a_event - > oldContainer ) ;
if ( ! ref | | ! IsInSameCell ( ref ) ) {
return ;
}
}
if ( ! GetItemCount ( g_cellStorage , form ) ) {
# ifdef _DEBUG
log : : info ( " Added dropped {} to cell storage " , form - > GetName ( ) ) ;
RE : : DebugNotification ( " Adding to cell storage " ) ;
# endif
g_cellStorage - > AddObjectToContainer ( form - > As < RE : : TESBoundObject > ( ) , nullptr , 1 , nullptr ) ;
}
if ( ! g_listStored - > HasForm ( form ) ) {
ListRemoveItem ( g_listFound , form ) ;
ListRemoveItem ( g_listNew , form ) ;
g_listStored - > AddForm ( form ) ;
}
} else if ( a_event - > oldContainer = = 0x14 ) { // dropped, consumed, dismantled, removed by script
if ( ! g_listStored - > HasForm ( form ) ) {
// Seems like OnContainerChanged runs concurrently with updating ContainerChanges.
// In small modlists ContainerChanges may not be propagated yet in this event, so we need to schedule GetItemCount.
SKSE : : GetTaskInterface ( ) - > AddTask ( [ form ] ( ) {
if ( ! GetItemCount ( RE : : PlayerCharacter : : GetSingleton ( ) , form ) & & ! FollowersHaveItem ( form ) ) {
ListRemoveItem ( g_listFound , form ) ;
g_listNew - > AddForm ( form ) ;
} else if ( ! g_listFound - > HasForm ( form ) ) {
ListRemoveItem ( g_listNew , form ) ;
g_listFound - > AddForm ( form ) ;
}
} ) ;
}
} else if ( g_cellStorage & & g_cellStorage - > formID = = a_event - > oldContainer ) {
return ; // ignore cell storage
} else if ( const auto it = g_persistentMap . find ( a_event - > oldContainer ) ; it ! = g_persistentMap . end ( ) ) { // deleted from a persistent container
const auto ref = it - > second ;
if ( ref & & ! GetItemCount ( ref , form ) ) { // no items left in the container
for ( const auto & persref : g_persistentMap ) {
if ( persref . second ! = ref ) {
if ( GetItemCount ( persref . second , form ) ) {
// if other containers have it, do nothing
return ;
}
}
}
ListRemoveItem ( g_listStored , form ) ;
if ( GetItemCount ( RE : : PlayerCharacter : : GetSingleton ( ) , form ) | | FollowersHaveItem ( form ) ) {
g_listFound - > AddForm ( form ) ;
} else {
g_listNew - > AddForm ( form ) ;
}
}
} else {
const auto ref = RE : : TESForm : : LookupByID ( a_event - > oldContainer ) ;
if ( ref - > Is ( RE : : FormType : : ActorCharacter ) ) {
const auto actor = ref - > As < RE : : Actor > ( ) ;
if ( actor & & actor - > IsPlayerTeammate ( ) & & ! GetItemCount ( actor , form ) ) { // removed from companion (probably, disarmed)
if ( g_listFound - > HasForm ( form ) ) {
if ( ! GetItemCount ( RE : : PlayerCharacter : : GetSingleton ( ) , form ) ) {
// player does not have it, check companions
if ( const auto processLists = RE : : ProcessLists : : GetSingleton ( ) ; processLists ) {
for ( auto & actorHandle : processLists - > highActorHandles ) {
if ( auto actor = actorHandle . get ( ) ; actor & & actor - > IsPlayerTeammate ( ) & & actor - > formID ! = ref - > formID ) {
if ( GetItemCount ( actor . get ( ) , form ) ) {
// other companion has it, do nothing
return ;
}
}
}
}
ListRemoveItem ( g_listFound , form ) ;
g_listNew - > AddForm ( form ) ;
}
}
}
} else {
const auto container = ref - > As < RE : : TESObjectREFR > ( ) ;
if ( container ) {
if ( g_cellStorage & & IsInSameCell ( container ) ) { // removed from container at home
if ( ! GetItemCount ( container , form ) ) {
SyncCellStorage ( container ) ;
}
}
}
}
}
}
}
void AddRefArtifactsToList ( RE : : TESForm * a_refOrList , RE : : BGSListForm * a_targetList , RE : : BGSListForm * a_excludeList )
{
if ( ! a_refOrList | | ! a_targetList ) {
log : : warn ( " Invalid arguments in AddRefArtifactsToList " ) ;
return ;
}
if ( a_refOrList - > Is ( RE : : FormType : : FormList ) ) {
a_refOrList - > As < RE : : BGSListForm > ( ) - > ForEachForm ( [ & ] ( RE : : TESForm & a_exform ) {
const auto refrItem = a_exform . As < RE : : TESObjectREFR > ( ) ;
if ( refrItem ) {
AddRefArtifactsToList ( refrItem , a_targetList , a_excludeList ) ;
}
return true ;
} ) ;
return ;
}
const auto containerRef = a_refOrList - > As < RE : : TESObjectREFR > ( ) ;
if ( ! containerRef ) {
log : : warn ( " containerRef in AddRefArtifactsToList is not a reference " ) ;
return ;
}
const auto inv = containerRef - > GetInventory ( [ & ] ( RE : : TESBoundObject & a_exform ) {
return ArtifactTracker : : IsArtifact ( & a_exform ) & & ( ! a_excludeList | | ! a_excludeList - > HasForm ( & a_exform ) ) ;
} ) ;
for ( const auto & item : inv ) {
if ( item . second . first > 0 ) {
a_targetList - > AddForm ( item . first ) ;
ListRemoveItem ( g_listNew , item . first ) ;
}
}
}
void RescanFoundArtifacts ( )
{
ListRevert ( g_listFound ) ;
AddRefArtifactsToList ( RE : : PlayerCharacter : : GetSingleton ( ) , g_listFound , g_listStored ) ;
for ( const auto & ref : GetPlayerFollowers ( ) ) {
AddRefArtifactsToList ( ref , g_listFound , g_listStored ) ;
}
}
void RescanStoredArtifacts ( )
{
ListRevert ( g_listStored ) ;
AddRefArtifactsToList ( g_persistentStorage , g_listStored ) ;
}
void RescanNewArtifacts ( )
{
for ( auto const & item : g_artifactMap ) {
if ( ! g_listNew - > HasForm ( item . second ) & & ! g_listStored - > HasForm ( item . second ) & & ! g_listFound - > HasForm ( item . second ) ) {
g_listNew - > AddForm ( item . second ) ;
}
}
}
void OnLocationChange ( )
{
std : : int32_t iCurrentFollowers = 0 ;
for ( const auto & actor : GetPlayerFollowers ( ) ) {
iCurrentFollowers + = actor - > formID ;
}
if ( iCurrentFollowers ! = g_iFollowerIndex ) {
g_iFollowerIndex = iCurrentFollowers ;
std : : thread ( [ ] ( ) {
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 3000 ) ) ; // wait for followers to load into the new cell
SKSE : : GetTaskInterface ( ) - > AddTask ( [ ] ( ) {
RescanFoundArtifacts ( ) ;
} ) ;
} ) . detach ( ) ;
}
}
}