llde / xOBSE

Oblivion Script extender source
250 stars 36 forks source link

Issue with Inventory Reference Iteration #190

Closed Gaebrial closed 1 year ago

Gaebrial commented 1 year ago

Game: Oblivion OBSE version: 22.x

When iterating through inventory references using a ForEach loop, certain inventory references that are removed using RemoveMeIR are then re-processed in the same loop.

I think I have narrowed it down to those inventory references that are equipped or at least are equippable (weapons, armor, clothing).

I have tested using the corpse of the first Arena opponent, and her armor, shield, helmet, and sword are all duplicated in the ForEach loop, so when using RemoveMeIR to move these items into a different container, you end up with two copies of each item.

[Edit] Here is an extract from the debug log, showing the duplication (Constantia is the name of the Arena combatant): Constantia has 1x Arena Light Raiment (000236F0). Item is type 20 with slot 20 valued at 580. Constantia surrendered 1x Arena Light Raiment (000236F0). Constantia has 1x Process Token (0600BCC3). Item is type 22 with slot 6 valued at 0. Constantia has 4x Token RogueMage LastCheck (81013B1B). Item is type 22 with slot 6 valued at 0. Constantia has 4x Token RogueMage PayDay (8100B971). Item is type 22 with slot 6 valued at 0. Constantia has 84x Gold Coin (0000000F). Item is type 27 with slot 0 valued at 84. Constantia has 1x aaaHumantouchNPCItem (B4000ED3). Item is type 22 with slot 255 valued at 0. Constantia has 1x Iron Longsword (000977D1). Item is type 33 with slot 16 valued at 20. Constantia sold 1x Iron Longsword (000977D1) for 6.72g. Constantia has 1x Fur Shield (000977BE). Item is type 20 with slot 13 valued at 16. Constantia sold 1x Fur Shield (000977BE) for 5.376g. Constantia has 1x Fur Helmet (000733E2). Item is type 20 with slot 1 valued at 8. Constantia sold 1x Fur Helmet (000733E2) for 2.688g. Constantia has 1x Boss Token (0600BCC2). Item is type 22 with slot 6 valued at 0. Constantia has 1x Fur Helmet (000733E2). Item is type 20 with slot 1 valued at 8. Constantia sold 1x Fur Helmet (000733E2) for 2.688g. Constantia has 1x Fur Shield (000977BE). Item is type 20 with slot 13 valued at 16. Constantia sold 1x Fur Shield (000977BE) for 5.376g. Constantia has 1x Arena Light Raiment (000236F0). Item is type 20 with slot 20 valued at 580. Constantia surrendered 1x Arena Light Raiment (000236F0). Constantia has 1x Iron Longsword (000977D1). Item is type 33 with slot 16 valued at 20. Constantia sold 1x Iron Longsword (000977D1) for 6.72g. Constantia has 1x Potion of Healing (00098496). Item is type 40 with slot 0 valued at 58. Constantia surrendered 1x Potion of Healing (00098496). Constantia's equipment yielded 32 gold.

I have checked back through the releases, and this issue has been present since version 22.0.

llde commented 1 year ago

What is the Loop Code?

Gaebrial commented 1 year ago

Here's the inventory processing part of the script. I've omitted some setup lines and additional code after the loop, which aren't relevant for the loop itself.

For corpses, the script is called with rActor being the actor and sType being either -1 or -2.

The idea is that it saves the player having to inspect and loot every corpse.

The token NCCorpse is used to indicate that the accompanying quest script should highlight the corpse to tell the player to manually inspect it (because of quest items or scripted items that would otherwise be removed). And the reference to the tail slot item is because they seem to be treated differently.

scn NCProcessInventory

ref rActor
short sType
ref rItem
ref rBase
int iIndex

short sNum
short sSlot
short sGold
float fGold
float fMult
short sMerc

ref rTail

begin function { rActor sType }

    SetFunctionValue 0

    if 0 == IsFormValid rActor
        return
    elseif rActor.GetIsReference Player
        return
    endif

    Let sMerc := Player.GetActorValue Mercantile
    Let fMult := (150.0 + (sMerc * 2.0)) / 500.0

    Let sGold := 0
    Let rTail := 0
    Let rItem := 0

    ForEach rItem <- rActor

        Let sNum := rItem.GetRefCount
        Let sSlot := GetEquipmentSlot rItem
        Let rBase := rItem.GetBaseObject
        Let iIndex := GetSourceModIndex rBase
        Let fGold := 1.0 * GetFullGoldValue rItem * sNum

        PrintD $rActor + " has " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + "). Item is type " + $(rItem.GetObjectType) + " with slot " + $sSlot + " valued at " + $fGold + "."

        if rItem.IsPlayable2 == 0
            continue
        elseif rItem.IsQuestItem
            rActor.Call NCSetItemCount NCCorpse 1
        elseif sSlot == 15
            Let rTail := rBase
            PrintD $rActor + " found tail item: " + $rItem + " (" + GetFormIDString rBase + ")."
        elseif sSlot == 14
            if GetFullGoldValue rItem > 0 && sType == -1
                rActor.Call NCSetItemCount NCCorpse 1
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + "."
            endif
        elseif rItem.GetObjectType == 39
            if sType == -1
                rActor.Call NCSetItemCount NCCorpse 1
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + "."
            endif
        elseif rItem.GetBaseObject == Gold001
            continue
        elseif GetFullGoldValue rItem == 0 && GetWeight rItem == 0
            PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
        elseif sSlot == 6 || sSlot == 7 || sSlot == 8
            if sType != 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                rItem.RemoveMeIR NCChestRef
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        elseif (sSlot == 13 || sSlot == 16 || sSlot == 17) && sType >= 0
            PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
        elseif GetEnchantment rItem
            if sType != 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                rItem.RemoveMeIR NCChestRef
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        elseif (sSlot == 13 || sSlot == 16 || sSlot == 17) ; Processing a corpse
            if rItem.IsScripted && sType == -1
                rActor.Call NCSetItemCount NCCorpse 1
            else
                if fGold < 1.0
                    Let fGold := 1.0
                else
                    Let fGold *= fMult
                endif
                PrintD $rActor + " sold " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ") for " + $fGold + "g."
                Let sGold += Ceil fGold
                rItem.RemoveMeIR
            endif
        elseif rItem.GetObjectType == 20
            if rItem.IsScripted && sType == -1
                rActor.Call NCSetItemCount NCCorpse 1
            elseif NCQuest.iSellItems == 1 || sType < 0
                if fGold < 1.0
                    Let fGold := 1.0
                else
                    Let fGold *= fMult
                endif
                PrintD $rActor + " sold " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ") for " + $fGold + "g."
                Let sGold += Ceil fGold
                rItem.RemoveMeIR
            elseif sType != 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                rItem.RemoveMeIR NCChestRef
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        elseif rItem.GetObjectType == 22
            if rItem.IsScripted && sType == -1
                rActor.Call NCSetItemCount NCCorpse 1
            elseif NCQuest.iSellItems == 1 || sType < 0
                if fGold < 1.0
                    Let fGold := 1.0
                else
                    Let fGold *= fMult
                endif
                PrintD $rActor + " sold " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ") for " + $fGold + "g."
                Let sGold += Ceil fGold
                rItem.RemoveMeIR
            elseif sType != 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                rItem.RemoveMeIR NCChestRef
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        elseif rItem.GetObjectType == 38 || rItem.GetObjectType == 42
            if sType != 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                NCChestRef.AddItemNS rBase sNum
                rItem.RemoveMeIR
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        else
            if sType < 0
                PrintD $rActor + " surrendered " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
                NCChestRef.AddItemNS rBase sNum
                rItem.RemoveMeIR
            else
                PrintD $rActor + " did not surrender " + $sNum + "x " + $rItem + " (" + GetFormIDString rBase + ")."
            endif
        endif

    loop

    if sGold > 0
        rActor.AddItemNS Gold001 sGold
        PrintD $rActor + "'s equipment yielded " + $sGold + " gold."
    endif

    SetFunctionValue 1

end
llde commented 1 year ago

Hi @Gaebrial it's possible to have the complete mod to test?

llde commented 1 year ago

I can only partially experience this. What I get is that there are double items, when the items come from the Base Container (and not the reference container) OBjects that are fully present only in the reference container appears only once, without differences between equippable and non equippable objects

@Gaebrial