Author Topic: MoveInventory function - feedback  (Read 324 times)

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
MoveInventory function - feedback
« on: October 24, 2012, 03:14:17 pm »


               I wrote a function which copies the inventory from a source object to a target object. I am interested in getting feedback on it.

The use for this is in Persistent Item storage. I've been using creatures as mules to store items for persistent placeable containers. So when the placeable container needs to have the items in it (on spawn or on open), I move the inventory from the creature to the item. When I need to commit the inventory to the database (on close), I move the inventory to the creature, and save the creature.

I haven't noticed any bugs in testing so far.

[color="#00ff00"]
 // Moves all of inventory from oSource to oTarget - [FILE: aa_inc_util]
 // returns number of items moved
 int MoveInventory(object oSource, object oTarget)
{
  object oItem, oCopy; int nCount;

  // move gold
  if(GetObjectType(oSource)==OBJECT_TYPE_CREATURE)
  {
    int nGold   = GetGold(oSource);
    if(nGold)
    {
      AssignCommand(oTarget, TakeGoldFromCreature(nGold, oSource));
      ++nCount;
    }
  }

  // copy containers
  oItem  = GetFirstItemInInventory(oSource);
  while(GetIsObjectValid(oItem))
  {
    if(GetHasInventory(oItem)&&!GetLocalInt(oItem,"COPIED"))
    {
      nCount++;
      // create a copy of the container
      oCopy   = CreateItemOnObject(GetResRef(oItem), oTarget, 1, GetTag(oItem));
      SetLocalInt(oItem, "COPIED", TRUE); // mark the original as copied
      SetName(oCopy, GetName(oItem));
      SetDescription(oCopy, GetDescription(oItem));
      SetIdentified(oCopy, GetIdentified(oItem));
      // copy contents of container
      nCount  +=  MoveInventory(oItem, oCopy);
      DestroyObject(oItem, 0.1);
    }

    oItem   = GetNextItemInInventory(oSource);
  }
  // copy items
  oItem  = GetFirstItemInInventory(oSource);
  while(GetIsObjectValid(oItem))
  {
    if(!GetHasInventory(oItem)&&!GetLocalInt(oItem,"COPIED"))
    {
      nCount++;
      CopyItem(oItem, oTarget, TRUE);
      SetLocalInt(oItem, "COPIED", TRUE);
      DestroyObject(oItem, 0.1);
    }

    oItem   = GetNextItemInInventory(oSource);
  }

  return nCount;
}[/color]

               
               

               


                     Modifié par henesua, 24 octobre 2012 - 02:18 .
                     
                  


            

Legacy_the.gray.fox

  • Full Member
  • ***
  • Posts: 214
  • Karma: +0/-0
MoveInventory function - feedback
« Reply #1 on: October 24, 2012, 05:02:00 pm »


               I only want to signal a potential weakness in the code.
Not your fault. But it is a weakness you may want to patch all the same.

int nGold = GetGold(oSource);


There is 1 particular scenario in which a call to GetGold() will not return the correct amount of gp possessed by oSource.

Here are the steps to reproduce it:
1) In an Area put a usable placeable with inventory, such as a wooden crate.
2) Place some gp into the placeable's inventory (gp amount is irrelevant).
3) Place an Item container on the ground near the placeable (a Bag of Holding will do).
4) Place one other item on the ground near the placeable (for example a sword).
5) Playtest the module. Ans please use a newborn Lvl1 PC, for the sake of keeping unwanted complications out of this scenario.


During playtest:
1) Have your PC pick up the bag of holding.
2) Left-click on the placeable to open its inventory.
3) Left-click and Hold the mouse onto the gp item inside the placeable. Drag and Drop those gp onto your bag of holding inside your inventory.
4) Now call GetGold() to query your amount of gp.

GetGold() will fail, reporting .you the amount of gp you had _before_ picking the gp off the placeable.
But now pick up the sword item near the placeable.
Then call GetGold() again. This time GetGold() will give you the correct gp amount.

Those gp you took off the placeable were like a ghost item. You had them, but for some reason the engine did not sense them in your possession.
Then picking up an item, any item, caused a disturbance in your inventory. Thanks to that the engine noticed your true amount of gp.


If you care to react to this problem in real time, as it occurs, so ensuring that a call to GetGold() will not fail, you have to write an OnDisturbed event handler for the placeable. Here is a rip off of something I wrote a while back.


// -----------------------------------------------------------------------------
// This goes into the OnDisturbed event of a Placeable with inventory.
//
// It detectes the otherwise stealth acquisition of a stack of GP dragged off a
// Placeable with inventory onto a Container item (such as a Bag of Holding) in
// the inventory of the Player disturbing the Placeable.
// -----------------------------------------------------------------------------
void main ()
{
    ////////////////////////////////////////////////////////////////////////////
    // This function must execute on behalf of a Placeable with an Inventory.
    // The Disturb event type must be a "Removed", and the removed Item must be
    // Invalid. Finally, the Disturber must be an Item with Inventory.
    //
    // If all of the above conditions are met, then a Player did drag a stack of
    // GP from the Placeable inventory directly onto a Bag of Holding (or other
    // container) in his possession. Thus the Player has acquired an amount of
    // GP that eludes any GetGold() check, no matter WHEN it is made. To reveal
    // this clandestine amount of GP we have to "wake up" the Player inventory
    // by disturbing it ourselves, for example with the introduction of an item
    // that we can remove immediately. Doing so a new call to GetGold() shall
    // correctly notice the previously unsensed amount of GP.
    //
    // -so sayeth the fox
    ////////////////////////////////////////////////////////////////////////////

    object oSelf = OBJECT_SELF;

    if (   (GetObjectType (oSelf) == OBJECT_TYPE_PLACEABLE)
        && GetHasInventory (oSelf)
        && (GetInventoryDisturbType () == INVENTORY_DISTURB_TYPE_REMOVED)
        && !GetIsObjectValid (GetInventoryDisturbItem ()))
    {
        object oDisturber = GetLastDisturbed ();

        if (   (GetObjectType (oDisturber) == OBJECT_TYPE_ITEM)
            && GetHasInventory (oDisturber))
        {
            ////////////////////////////////////////////////////////////////////
            // We first try to spawn the item inside oPossessor main inventory.
            // If it fails, the main inventory is full -- in which case we try
            // to spawn the item into any of the Containers of oPossessor, among
            // which we find the very oDisturber.
            ////////////////////////////////////////////////////////////////////

            // For the record: oPossessor should always be a PC.
            object oPossessor = GetItemPossessor (oDisturber);

            // And from now on: oDisturber becomes the item we try to spawn
            // inside the inventory of oPossessor, to force an update from the
            // engine, thus making the acquired GP "visible" to GetGold().
            oDisturber = OBJECT_INVALID;

            // Try to spawn our disturber item in the main inventory.
            oDisturber = CreateItemOnObject (sFOX_GOLD_RESREF_DISTURBER, oPossessor, 1, "");
            if (GetIsObjectValid (oDisturber))
            {
                // Success! We are done, here :-)
                DestroyObject (oDisturber, 0.0f);
                return;
            }

            // We here? The main inventory is choked full. We try now with the
            // Container items of oPossessor. Any Container will do. We know he
            // has at least one -- or we would not be here now.
            object oScan = GetFirstItemInInventory (oPossessor);
            while (GetIsObjectValid (oScan))
            {
                if (GetHasInventory (oScan))
                {
                    // Try to spawn our disturber item in this Container.
                    oDisturber = CreateItemOnObject (sFOX_GOLD_RESREF_DISTURBER, oScan, 1, "");
                    if (GetIsObjectValid (oDisturber))
                    {
                        // Success! We are done, here :-)
                        DestroyObject (oDisturber, 0.0f);
                        return;
                    }
                }

                oScan = GetNextItemInInventory (oPossessor);
            }

            ////////////////////////////////////////////////////////////////////
            // We here? This means that oPossessor has the inventory choked full
            // of items. We *could* still attempt the _removal_ of an item from
            // him, or even his Containers... but would not we exagerate? :-/
            //
            // We prefer to leave this thing as-is, even though it is left open
            // to a bug, albeit the chance is remote. We mark it TODO: and BUG:
            //
            // -fox
            ////////////////////////////////////////////////////////////////////
        }
    }
}

The token sFOX_GOLD_RESREF_DISTURBER is just a ResRef string of an item I elect as disturber.
This disturber item, whatever you pick, must not be a stackable (no potions, no gems, no darts, shurikens, nothing that could "pile up"), since it may be absorbed in a partial stack, bugging the workaround. Also the item should be small (1x1 size) to give it best chances to find room within a crowded inventory.

Its momentary appearance in your inventory will show up in the console. To minimize the impact of it (at least to avoid disorienting your players) you can give this item a blank name, so the console output will not mislead an observer.

Notice that the call to CreateItemOnObject() will trigger a module OnAcquireItem event. By the time that event is processed (that is, right after the OnDisturbed event has run), GetGold() shall already be able to see the correct amount of gp.


-fox
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
MoveInventory function - feedback
« Reply #2 on: October 24, 2012, 05:40:38 pm »


               Thanks, Fox. Thats good to know.

In this case, I don't think it is possible to run into the bug. Placeables don't have a gold count. And the player is only interacting with the placeable, not the "Mule". And thus far no matter what I try I can't get the gold count to trip up on the "mule".

I'll run a few more tests, and see if it is possible for the mule to get tripped up (maybe when a DM gives Gold to the mule).