Stacking system: Comparison

Item stacking seems to be a common thing people want to do; and as far as I can see there's three main ways to do it. I thought it might be interesting to compare the pros/cons of each method. Are there any methods I'm missing, or other advantages?

  1. One object, count attribute
    The most obvious solution, maybe the first one you'd think of.

    • ADVANTAGE: Simple data structure; easy to understand.
    • ADVANTAGE: Small number of objects; lower memory use; maybe quicker.
    • ADVANTAGE: Simple attribute to check how many the player has got.
    • DISADVANTAGE: Code ends up in lots of places. There are a lot of things that don't work properly unless you add extra code to deal with them (Including inventory limits, and any script/library that interacts with the player's inventory at all, such as shops), meaning the code quickly ends up being more complex than other methods.
  2. Object as a container
    If you're carrying one object and pick up another, they go inside each other.

    • ADVANTAGE: Can be implemented as a single script, so all the code is in one place.
    • ADVANTAGE: Works effortlessly with inventory limits, and with any shop system etc. that understands the concept of containers.
    • ADVANTAGE: A bit of careful alias changing can let the player input "get 3 bananas" without needing to modify the commands at all.
    • ADVANTAGE: Objects can have unique features and still stack. You could have a bunch of different flowers that stack together, but can be separated in some cases.
    • DISADVANTAGE: Different ways of implementing this, which seems to give a choice between being inefficient or inelegant.
    • DISADVANTAGE: Has more objects in play than method 1, so can be slower.
  3. Separate objects, override FormatObjectList and/or updateList functions
    You don't necessarily need to treat the objects as a stack internally. You could just change the functions that format a list of objects for displaying to the user, such that it only shows each item once.

    • ADVANTAGE: Works with all other systems/scripts/libraries, because it doesn't change the internal representation of the objects at all.
    • ADVANTAGE: Quick, simple.
    • DISADVANTAGE: Player can only interact with one object at a time, unless you add extra code elsewhere (in which case you're almost maintaining two stacking systems at once)

Having implemented all three, I have to say 2 is my favourite so far.
Are there any I'm missing?


Me: Uses standard container code.


Ugh… I had method 2 working just how I wanted it before, and I can't replicate it. I can only get it to work properly with the new GetScope().

Back to a crazy hybrid method:

foreach (c, AllCommands()) {
  if (HasString(c, "scope") and not GetBoolean(c, "supportsStackedObjects")) {
    c.realScript = c.script
    c.script => {
      stackedargs = getStackOptions(game.pov.currentcommandresolvedobjects)
      if (ListCount(stackedargs) = 0) {
        do (this, "realScript", game.pov.currentcommandresolvedelements)
      }
      else {
        foreach (stackmembers, stackedargs) {
          args = DictionaryCombine (game.pov.currentcommandresolvedelements, stackmembers)
          do (this, "realScript", args)
        }
      }
    }
  }
}

My current script is a weird one. My changedparent script looks for all items of the same type in the same location. If it finds any, it loops over them (in object order) changing their aliases to "Bunch of bananas (6)", "5 bananas", "4 bananas", "3 bananas", "2 bananas", and "banana." All objects except the first have their listalias changed to "[STACK=o,n]", where o is the name of the first object in the stack, and n is the quantity assigned to this particular object. I'm playing around with the javascript updateList function so that these extra objects appear with display: none in the sidebar; but when you select the first one, a drop-down-list to select a number appears next to the verb buttons.

Next task is making this work the same in the case of FormatObjectList; which shouldn't be difficult because the JS already has a list of all the reachable objects for the sidebar; so I should be able to make the popup verb list when you click on an object have a number selector at the side.

Putting the hidden objects in a container seems sensible, but ends up being more trouble than it's worth because they need to be in scope. For now they're transparent containers, relying on FormatObjectList and updateList to prevent them actually displaying. But that ends up being unnecessary work; I might as well have them all in the same place if they're hidden anyway.


@@mrangel,
Have you worked out anything further on this issue? I sent you a message pleading for your "take three bananas" but accidently sent it to K.V. (btw) @K.V. thanks for your help.

Here is what I am working on now. It only asks "how many" one time which the player inputs a number (is good, but ain't three bananas good.) Let's use a potion as an example.

This is the drop script for a healing potion to give an idea. The "potion_of_healing" never leaves inventory. It may or may not be visible at times. I start with it visible, letting the player have a little bonus item to start.

The "potion_of_healing1" is the only other healing potion in the game at this time. I have to add a referencing script to more healing potions using (GetChildren(player.parent,Somesort of alias?) otherwise if the player drops a potion in a room and picks one up in another room then the one he/she dropped will be moved.

If you or anyone else has insight on this script, please jump in...

EDITED THIS SCRIPT on 12-16-17

And again on 12-18-17

This is a drop script for 'potion_of_healing'. There needs to be a 'take' script for 'potion_of_healing1' as well. Ask if you want it.
You will se a 'ClearTurn" function being called. it is a custom function to ClearScreen and ShowsRoomDescription.

if (Contains(player.parent.dropdestination, potion_of_healing1)) {
  if (potion_of_healing.volume = 1) {
    MakeObjectInvisible (potion_of_healing)
    potion_of_healing1.volume = potion_of_healing1.volume + 1
    potion_of_healing1.alias = "healing potions"
    ClearTurn
    msg ("<br>You drop the healing potion.")
  }
  else if (potion_of_healing.volume > 1) {
    ClearTurn
    msg ("<br>How many healing potions would you like to drop?")
    msg ("<i>Choose from 0 to " + potion_of_healing.volume + ".")
    get input {
      if (IsInt(result)) {
        result2 = ToInt(result)
        v = potion_of_healing.volume
        if (result2 > 0 and  result2 < v + 1) {
          potion_of_healing.volume = potion_of_healing.volume - result2
          potion_of_healing1.volume = potion_of_healing1.volume + result2
          potion_of_healing1.alias = "healing potions"
          ClearTurn
          msg ("<br>You drop " + result2 + ".")
        }
        if (potion_of_healing.volume <= 0) {
          MakeObjectInvisible (potion_of_healing)
          potion_of_healing.volume = 1
          potion_of_healing.alias = "healing potion"
        }
        if (result2 > v) {
          do (potion_of_healing, "drop")
          msg ("<br><b>You don't have " + result2 + " healing potions!")
        }
        if (result2 = 0) {
          ClearTurn
          msg ("<br>You change your mind.")
        }
      }
      else if (not IsInt(result)) {
        do (potion_of_healing, "drop")
        msg ("<b>That is not a number. Like 1 or 2...sheesh.")
      }
      else {
        do (potion_of_healing, "drop")
        msg ("<br>Really? You have a negative number of healing potions?")
      }
    }
  }
}
else {
  if (potion_of_healing.volume = 1) {
    MakeObjectInvisible (potion_of_healing)
    MoveObject (potion_of_healing1, player.dropdestination)
    ClearTurn
    msg ("<br>You drop the healing potion.")
  }
  else if (potion_of_healing.volume>1) {
    ClearTurn
    msg ("<br>How many healing potions would you like to drop?")
    msg ("<i>Choose from 0 to " + potion_of_healing.volume + ".")
    get input {
      if (IsInt(result)) {
        result2 = ToInt(result)
        v = potion_of_healing.volume
        if (result2 > 0 and  result2 < v + 1) {
          potion_of_healing.volume = potion_of_healing.volume - result2
          potion_of_healing1.volume = potion_of_healing1.volume + result2
          potion_of_healing1.alias = "healing potion"
          ClearTurn
          msg ("<br>You drop " + result2 + ".")
        }
        if (potion_of_healing.volume <= 0) {
          MakeObjectInvisible (potion_of_healing)
          potion_of_healing.volume = 1
          potion_of_healing.alias = "healing potion"
        }
        if (result2 > v) {
          do (potion_of_healing, "drop")
          msg ("<br><b>You don't have " + result2 + " healing potions!")
        }
        else {
          do (potion_of_healing, "drop")
          msg ("<br>Really? You have a negative number of healing potions?")
        }
      }
      else if (not IsInt(result)) {
        do (potion_of_healing, "drop")
        msg ("<b>That is not a number. Like 1 or 2...sheesh.")
      }
    }
  }
}

Any help moving forward would be appreciated!


What?

I didn't do it! I'm innocent, I tell ya'!

Innocent, see?!?!

Oh... You mean directing you here...

No problem, FW. It's my pleasure.

I was actually looking for the same thing a day or few before you messaged me.

The last I heard, mrangel's laptop broke. (A hinge problem, I do believe.)


I've been sharing this link:
https://www.amazon.com/Angel-Wedge/e/B00N5Q5XIK/ref=dp_byline_cont_ebooks_1

...not just to get mrangel back, but because I believe his work deserves attention. (Plus, it helps him out all-around, and helping out is what this forum is all about, right?)


working on game edit...


Yeah, still no laptop :S

I sent you a message pleading for your "take three bananas"

I posted the script here, and got replies telling me I'm stupid, just use an object with the 'count' variable.
Basically, I set it up so that an object's changeparent script checked if there was another object of the same type in the same place. FilterByAttribute on all child objects of the parent to find objects of matching type.

My original system had some flaws. Now tweaked, so it's more complex ): But it works.

Having found all objects of the same type in the same place, we make them containers and put them all inside each other. So the object "Four Bananas" contains "Three Bananas", which contains "Two Bananas", which contains "Banana". Giving them alt names "Bananas (3)" or similar could be useful.
These objects are all inside the last object, which is a container. I was already using a modified FormatObjectList function to show the objects in a room in order; so I just told it to skip showing the contents for containers with the isstack attribute. This means that you only see the top object, but ScopeReachableNotHeld and similar thinks you can see all of those objects. So you type "take bananas"and the system sees multiple matching objects; gives you the default menu for an ambiguous selection asking if you meant "two bananas", "three bananas" or so on. If you type "take three bananas", it takes the object whose alias is currently "three bananas", which conveniently contains two others.

(originally I used a background scope script to put the nested objects into scope; but this has serious flaws. In the end, I tweaked it so that the stackable object is actually a container, making its contents reachable, but prepended a "#" to the start of the nested-objects' listalias, and some modified JS (UpdateList function I think) that skips those items when drawing the inventory. So rather than an object with a count property, we're actually dealing with a separate object for each item, but they have a single entry in the UI.)

Top tip: The disambiguation menu seems to display objects in order of creation, the same order as GetAllChildObjects. So if you're using FilterByAttribute(GetAllChildObjects(parent), "prototype", this.prototype) or similar, then the first object found should be the innermost 'banana', and the last one the outermost container. This means that when the player sees a menu asking them to choose one, they can type a number and it will pick the right one. (Note: for this to work, the single banana needs an alt "1 Bananas", otherwise "Two Bananas" would be the first option for disambiguating "Bananas".

Hope that makes sense; typing through a mini panic at present, and also on a borrowed computer so no access to most of my files.

(Which means that you won't be exposed to my horrible, horrible JSquest implementation for quite a while yet)

And thanks KV :D Not being able to release a new book this month due to laptop issues has meant that my Author Rank sinks, so when I do release a new one it'll be lower down the 'new releases' list, which means it'll probably take a fortune in advertising to get to the point where I'm making enough to cover expenses again. So every view/sale/page/review is a big help in keeping it from falling too far.


@ mrangel:

don't know if you looked at (know of) this or not, but it might give you some ideas/help:

Sora's Stackable Library ( http://textadventures.co.uk/forum/samples/topic/3515/stackable-library )

he seemed to have made a pretty good stacking system, he seems a pretty good coder (way better than me), as I still have some trouble understanding his stacking code, sighs. Your stacking code might be better, but doesn't hurt to take a look at it (if you haven't already), as at least might give some ideas you hadn't thought of with your own coding.


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums