From Conversation DestroyObject Stack Items

I’m wanting to destroy some gems from a player’s inventory from a conversation, without destroying the whole stack and from multiple stacks.

For example, say the player has 27 Diamonds (2 stacks of 10 and 1 stack of 7). From the conversation, I only want to destroy 25 of those. What’s the best way to do this?

Damned if I know about best, but I’d try something like this:

    object oPC = GetPCSpeaker();
    int nDestroy = 25;
    int nDiamonds;

    // Count the total diamonds in the player's inventory.
    object oDiamonds = GetFirstItemInInventory(oPC);
    while (GetIsObjectValid(oDiamonds))
        {
        if (GetTag(oDiamonds) == "diamond")
            nDiamonds += GetItemStackSize(oDiamonds);

        oDiamonds = GetNextItemInInventory(oPC);
        }

    // Not enough diamonds.
    if (nDiamonds < nDestroy)
        return;

    // As long as the destroy counter isn't emptied out...
    while (nDestroy > 0)
        {
        // ... find a diamonds object:
        object oDiamonds = GetItemPossessedBy(oPC, "diamond");
        // Check it's stack size:
        int nStackSize = GetItemStackSize(oDiamonds);

        // If the stack size is less or exactly the number of diamonds to destroy...
        if (nStackSize <= nDestroy)
            {
            // Destroy the diamonds item and decrease the destroy counter by the amount of diamonds destroyed:
            DestroyObject(oDiamonds);
            nDestroy -= nStackSize;
            }
        // If there are more diamonds than should be destroyed...
        else
            {
            // Subtract the amount to be destroyed from the stack size and set the destroy counter to zero:
            SetItemStackSize(oDiamonds, nStackSize-nDestroy);
            nDestroy = 0;
            }
        }

Have done something similar to this in a crafting system recently, but did not test this specific bit of code. No guarantees.

1 Like

You need to check the tag or resref so that only diamonds are processed, not other items.

You can do it all in one pass through the inventory.

Assuming the target is to remove 25 (rather than leave 2) :

Whenever we find a diamond item - if the stacksize is less than or equal to target (decrement target by stack size, DestroyObject) else (SetItemStacksize to (current - target) , return).

2 Likes

I think checking first is a good idea. You don’t want to destroy 20 only to find out you don’t have 25 at all. That gets messy. You could do it in one pass if you save of all the diamond tagged objects into some sort of list that you then process later.

2 Likes

Added GetTag() check. Oops. Thanks for the catch, @Proleric. :smiley:

1 Like

Did a quick test with 3 stacks of 10 diamonds (30 diamonds total). Running this script should leave me with a stack of 5 diamonds, but instead I’m left with 2 stacks of 10 (20 diamonds total). So, the script is only getting rid of 1 of the stacks and not doing anything with the next 2 stacks. Very strange.

The reason this is happening is that DestroyObject only occurs after the script finishes, so GetItemPossessedBy will always return the same item.

My one-pass method (based on GetFirst/NextItemInInventory) will work.

The thing about GetFirst/Next loops is they’re easily broken by adding or deleting members of the set. The delay on DestroyObject makes it safe to use in that context.

If you need to check that there are 25 (not clear from the OP), by all means do that first.

1 Like

Proleric’s right. Went back and checked; reason this was working via GetItemPossessedBy in the crafting system case I had in the back of my head was that I was destroying the items in a separate looping function there.

void DiminishDeDiamonds(int nDestroy, object oPC)
{
    if (nDestroy <= 0)
        return;

    object oDiamonds = GetItemPossessedBy(oPC, "diamond");
    int nStackSize = GetItemStackSize(oDiamonds);

    if (nStackSize <= nDestroy)
        {
        DestroyObject(oDiamonds);
        nDestroy -= nStackSize;
        }
    else
        {
        SetItemStackSize(oDiamonds, nStackSize-nDestroy);
        nDestroy = 0;
        }

    DelayCommand(0.01, DiminishDeDiamonds(nDestroy, oPC));
}

Like that. Tested ingame, seems to work.

1 Like

Is your one-pass method posted on the vault somewhere, or do you mind posting the whole script here? And yes, a check that there are 25 would be good. The player can have anywhere from 0 - 100s of diamonds (depending how long they farm for them). They turn those into a NPC for rewards (every 25 gives the reward).

Do I need to add this to the new script, or do both scripts work together?

I just moved the code from the destroy-the-diamonds part of the first variant into a separate function, there. They’re basically the same thing.

DiminishDeDiamonds(25, oPC);

in void main would do just fine, for using it. It’d probably make sense to add an additional parameter, to allow you to specify the tag, though, so you can reuse it for any type of collectable, instead of just items tagged “diamond”. Probably should rename it, too. Is a silly name.

And there’s no check for whether the PC actually has enough diamonds, in that function, so you should still check for that before you call it.

1 Like

Sorry for my ignorance and thank you for your patience!

Do they go together and saved as a single .nss script like this?

void main()
{
object oPC = GetPCSpeaker();
int nDestroy = 25;
int nDiamonds;

// Count the total diamonds in the player's inventory.
object oDiamonds = GetFirstItemInInventory(oPC);
while (GetIsObjectValid(oDiamonds))
    {
    if (GetTag(oDiamonds) == "diamond")
        nDiamonds += GetItemStackSize(oDiamonds);

    oDiamonds = GetNextItemInInventory(oPC);
    }

// Not enough diamonds.
if (nDiamonds < nDestroy)
    return;

// As long as the destroy counter isn't emptied out...
while (nDestroy > 0)
    {
    // ... find a diamonds object:
    object oDiamonds = GetItemPossessedBy(oPC, "diamond");
    // Check it's stack size:
    int nStackSize = GetItemStackSize(oDiamonds);

    // If the stack size is less or exactly the number of diamonds to destroy...
    if (nStackSize <= nDestroy)
        {
        // Destroy the diamonds item and decrease the destroy counter by the amount of diamonds destroyed:
        DestroyObject(oDiamonds);
        nDestroy -= nStackSize;
        }
    // If there are more diamonds than should be destroyed...
    else
        {
        // Subtract the amount to be destroyed from the stack size and set the destroy counter to zero:
        SetItemStackSize(oDiamonds, nStackSize-nDestroy);
        nDestroy = 0;
        }
    }

}

void DiminishDeDiamonds(int nDestroy, object oPC)
{
if (nDestroy <= 0)
return;

object oDiamonds = GetItemPossessedBy(oPC, "diamond");
int nStackSize = GetItemStackSize(oDiamonds);

if (nStackSize <= nDestroy)
    {
    DestroyObject(oDiamonds);
    nDestroy -= nStackSize;
    }
else
    {
    SetItemStackSize(oDiamonds, nStackSize-nDestroy);
    nDestroy = 0;
    }

DelayCommand(0.01, DiminishDeDiamonds(nDestroy, oPC));

}

1 Like

Pfh! Don’t apologize for not being all-knowing. Very very secretly, we’re all in the same boat, there.

Very nearly. :smiley: OK, custom functions lesson:

You can write new functions for pretty much anything you want. The function does not have to be in the script where it’s being called, per se; you could save it into a library which you #include at the top of the script where you’re using the function.

That way, you can access the function from different scripts, and can spare yourself a lot of bloat from repeat code. Plus, if it turns out there’s a bug with the code or you want to expand it somehow, you only have to edit one instance of it.

So, anytime you’ve got a bit of code that you’ll be using repeatedly in different scripts, it’d make a lot of sense to write it into a custom function library.

Make a new script. Delete void main from it, and copypaste these two functions into it:

// Removes nAmount stackable items tagged with sTag from oPC's inventory,
// by destroying them. Should be used in conjunction with CollectableAmountCheck().
void TakeCollectables(string sTag, int nAmount, object oPC);
void TakeCollectables(string sTag, int nAmount, object oPC)
{
    if (nAmount <= 0)
        return;

    object oCollectable = GetItemPossessedBy(oPC, sTag);
    int nStackSize = GetItemStackSize(oCollectable);

    if (nStackSize <= nAmount)
        {
        DestroyObject(oCollectable);
        nAmount -= nStackSize;
        }
    else
        {
        SetItemStackSize(oCollectable, nStackSize-nAmount);
        nAmount = 0;
        }

    DelayCommand(0.01, TakeCollectables(sTag, nAmount, oPC));
}

// Goes through all items in oPC's inventory, counting stack sizes of all
// items tagged sCollectableTag. Returns TRUE if the total amount is equal or
// greater than nAmount. Otherwise, returns FALSE.
int CollectableAmountCheck(string sCollectableTag, int nAmount=1, object oPC=OBJECT_SELF);
int CollectableAmountCheck(string sCollectableTag, int nAmount=1, object oPC=OBJECT_SELF)
{
    int nCollectables;

    // Count the total collectables in the player's inventory.
    object oCollectable = GetFirstItemInInventory(oPC);
    while (GetIsObjectValid(oCollectable))
        {
        if (GetTag(oCollectable) == sCollectableTag)
            nCollectables += GetItemStackSize(oCollectable);

        oCollectable = GetNextItemInInventory(oPC);
        }

    return (nCollectables >= nAmount);
}

Save it as, for instance, “inc_collect”, and then go to your conversation node script.

Include inc_collect in it, at the very top.

#include "inc_collect"

void main()
{
    object oPC = GetPCSpeaker();

    string sCollectableName = "diamonds";
    string sCollectableTag = "diamond";
    int nAmount = 25;

    // Not enough, so abort.
    if (!CollectableAmountCheck(sCollectableTag, nAmount))
        {
        FloatingTextStringOnCreature("You don't have enough "+sCollectableName+" for that.", oPC);
        return;
        }

    // Destroy the collectables:
    TakeCollectables(sCollectableTag, nAmount, oPC);
}

Fingers crossed I didn’t write any bugs into that while moving stuff around.

Mind, I don’t know about best method. :thinking: I think it’s very possible if not altogether likely that doing it in one go-through the inventory would be less performance-heavy. It’s relatively small stuff, but… in the long run, efficiency pays off in that it doesn’t cause the same lagginess problems inefficiency does.

Also, note: You can have a function implementation below void main as long as the prototype is above it.

Example:

// Function prototype.
void HelloIamAFunction(int i=1);

void main()
{
    // Calling the function from void main here.
    HelloIamAFunction();
}

// Function implementation.
void HelloIamAFunction(int i=1)
{
    SpeakString(IntToString(i));
}
1 Like

Thank you so much for your effort! I’ve got it working!

1 Like