ianpatt / f4se

Fallout 4 Script Extender
https://f4se.silverlock.org
120 stars 24 forks source link

BGSInventoryList Data Structure, and how ObjectReference.GetInventoryItems() works #28

Closed ArgoreOfficial closed 4 months ago

ArgoreOfficial commented 4 months ago

Using .GetInventoryItems() through either papyrus or directly in C++ seems to give inconsistent results of what is actually in containers.

If there are two or more of a weapon or armor with different mods, it only seems to return one of them. Items sometimes don't return the correct number of items, most consistently when there are many items in a container.

I copied and modified the GetInventoryItems to return object references, rather than just forms, but other than that it's mostly identical

// does refr->GetFullName() work on containers?
printf( "  ---------- %s ---------- \n", refr->GetFullName() );
BGSInventoryList* inventory = refr->inventoryList;
if ( !inventory )
    return;

inventory->inventoryLock.LockForRead();

printf( "  Items.count    : %i \n", inventory->items.count );
printf( "  items.capacity : %i \n", inventory->items.capacity );

for ( int i = 0; i < inventory->items.count; i++ )
{
    BGSInventoryItem& item = inventory->items[ i ];

    // item.form->GetFullName() is sometimes cut off? 
    // eg. Laser Pistol returns as just Laser. CreationKit issue?
    printf( "item: %s(%i) [%02x]\n", item.form->GetFullName(), item.stack->count, item.form->formID );

    // get object mods, if any
    VMArray<BGSMod::Attachment::Mod*> omods = getOMods( item ); // identical to PapyrusObjectReference.cpp -> papyrusObjectReference::GetAllMods
    for ( int i = 0; i < omods.Length(); i++ )
    {
        BGSMod::Attachment::Mod* omod;
        omods.Get( &omod, i );

        printf( "  mod: %s [%02x]\n", omod->fullName.name.c_str(), omod->formID);
    }
}

inventory->inventoryLock.UnlockRead();

Papyrus fragment using the original F4SE function

Form[] items = refContainer.GetInventoryItems()
Debug.Notification( items.length )

Video Demonstration

Papyrus fragment is printed through the debug notification, C++ is printed through output console

ArgoreOfficial commented 4 months ago

Seems to have been just weirdness in how the data is stored, along with some bugged items

The weapons (and armor too then I will assume) are in a linked list in the Stack* object, while stack.count is used for items that are identical

so for example two identical laser pistols, and a laser rifle, and two identical automatic laser rifles would look something like this:

Laser Pistol (2)
         └── Laser Rifle (1)
                   └── Automatic Laser Rifle (2)

the updated loop body if anybody's interested:

BGSInventoryItem& item = inventory->items[ i ];
BGSInventoryItem::Stack* itemstack = item.stack;

do
{
    printf( "item: %s(%i) [%02x]\n", item.form->GetFullName(), item.stack->count, item.form->formID );

    // get extra data, if any
    for ( UInt32 i = 1; i < 0xD6; i++ )
    {
        if ( !itemstack->extraData )
            continue;
        if ( !itemstack->extraData->HasType( i ) )
            continue;

        printf( "  extra: %s\n", getExtraDataType( i ) ); // stringify kExtraData enum
    }

    // get object mods, if any
    VMArray<BGSMod::Attachment::Mod*> omods = getOMods( itemstack ); // identical to PapyrusObjectReference.cpp -> papyrusObjectReference::GetAllMods
    for ( int i = 0; i < omods.Length(); i++ )
    {
        BGSMod::Attachment::Mod* omod;
        omods.Get( &omod, i );

        printf( "  mod: %s [%02x]\n", omod->fullName.name.c_str(), omod->formID);
    }

    itemstack = itemstack->next;
} while ( itemstack );

I'll keep this here if anyone else has issues with it in the future

ianpatt commented 4 months ago

Inventory lists are complicated, especially when they have extradata.