Purchase Clone and Delete?

As I am not a terribly strong coder, I may be going about this in completely the wrong way. I'm creating a system for fishing that requires the use of bait. Each time a fishing attempt is made, it uses one bait. I've got that functionality working just fine, but I can't seem to figure out how to make Pixie's Shoplib work with what I'm doing.

Essentially, in the shop, I want the store to have an unlimited amount of bait for sale. Pixie's Shoplib includes support for an attribute "cloneonpurchase," which as it says on the tin, makes a clone of the purchased item and adds it to the player's inventory. So far so good.

Except what I want it to do is tick up an attribute I created called baitQty, which I have set to an integer on the player object. On the "prototype" bait object I added a script on the Inventory tab which should run when the item is "taken." That script is as follows:

player.baitQty = player.baitQty + 1
RemoveObject (this)
msg ("You pick up the bait and put it in your stash.")

Now, this worked perfectly right up until I implemented the "cloneonepurchase" attribute on the master object. Once I did that, it started adding individual items to the player's inventory rather than removing them from the game. Not only that, but it also fails to tick up the baitQty attribute that was working previously when buying the "prototype" object without cloning. I've read through the cloning tutorial, but I think I still must be missing something about how clones work and how to call on a specific clone when it's introduced to the game. Does anyone have any insight?

Actually, I just fixed this in a roundabout way (whether it's the right way, I can't say). I added a change script to the attribute "hasbeenmoved" on the "prototype" object and pasted the script from my previous comment in there. Now when the item gets moved (purchased), that script executes. Now I can buy as many as I want, and the counter keeps going up, and nothing enters my inventory. So... I guess this is here in case anyone else is struggling with this same issue.

As far as I understand it, cloneonpurchase makes a clone of the object and adds it to the player's inventory. It doesn't run the object's 'take' script, because the object isn't being taken. It's being moved into the inventory by the shop script.

I can see two ways to deal with this.

First option: Don't use cloneonpurchase; you can probably have the player take the original instead, and instead of RemoveObject you make the script return it to the shop's inventory. I don't know the shop system well enough to say how you'd do this, but it shouldn't be too hard to work out.

Second option: instead of the "take" script on the inventory tab (which runs when the player uses the "take" command on an object they can see), create a "changedparent" script (which is run whenever the object is moved to a different location).

If you have the desktop version of Quest, you should be able to find changedparent somewhere on the Attributes tab; possibly next to the parent attribute. I don't know the exact layout, as I only use the online editor.
There will be a default changedparent, which all objects has. It basically says "if this object is the player, display the description for the room they moved into". So for an object like bait, you can just replace that script.

If you're on the web editor, or you can't find changedparent, then go to the 'Features' tab and check "run an initialisation script". Then go to the Initialisation script tab, and enter this:

this.changedparent => {
  insert the script here

The changedparent script would probably be:

if (Got (this)) {
  player.baitQty = player.baitQty + 1
  RemoveObject (this)
  msg ("You pick up the bait and put it in your stash.")

However, this may result in your save files getting quite large. When you call RemoveObject, it doesn't delete the object. It just moves it into space, so it isn't in any room and the player can't find it. So you'll be saving all those deleted clones and their attributes when the player saves. This shouldn't be a problem, but if it gets to be hundreds of items or more, it could start to slow the game down.

So if the player is buying a lot of bait, it might be wise to change RemoveObject (this) to destroy (this.name). Note that sometimes you can't do this. If the shop script attempts to do anything with the clone after moving it to the inventory, this would cause an error. So you might have to test it, to see which method of disposal works better for you.

Ah, you beat me to it :)

The normal way to do things like this is to use the changescript for parent, so that you can confirm that it's being moved to the inventory. But if selling it is the only place you find bait, the changescript for hasbeenmoved works just as well.

Thanks as always mrangel for your wonderful insights. I do anticipate a lot of bait being purchased as fishing ought to be a pretty decent way to get healing items in my game. And I don't want to limit players in terms of the quantity they can carry, so switching out to destroy (this.name) seems like a better option. And I'll probably find that changedparent attribute as well, because I'd like the option to have it be picked up from the world.

Thanks again! Your help is invaluable.

The code I have set up in the store under the "hasbeenmoved" attribute is working perfectly, and it includes the destroy (this.name) bit.

However, when I try to introduce this same code destroy (this.name) to a test object in the environment, it throws the following error:

Error running script: Collection was modified; enumeration operation may not execute.

Any idea what might be causing this or if there's a different way to destroy the object? I'm testing it on a freshly made standalone object I'm picking up from the environment, and I've added that code to the Inventory tab in the "after taking the object" script. I figured I'd deal with any bait objects in the world separately from ones in the store since that's working perfectly at the moment.

Actually, I just found this post from Alex concerning this issue as well, who seems to indicate that destroying doesn't free up as much memory as one might think.

You don't need to use destroy unless for some reason you're dynamically creating a lot of objects. It is quicker just to hide an object. You won't save much memory by destroying - and you won't save any unless the player saves and reloads, because "undo" will keep a reference to the destroyed object anyway.

So for the ones outside of the scope of the shop, I've tried simply using remove object (this), which seems to work as intended. Does this seem sound, or would it be better to use move object and pick a destination instead? I guess I'm not sure where Quest moves an object to when using the remove function or if it even matters.

Error running script: Collection was modified; enumeration operation may not execute.

Basically, if you're using foreach to go over an objectlist and you destroy the object, the foreach complains that its list has changed.
Commands which allow all (for example "take all") use a list of objects in their script. So destroying an object within its 'take' or 'drop' scripts will give an error.

It's probably not necessary to destroy every object unless you have large numbers of cloned objects. But if you want to, one method would be making a turnscript which removes all clones whose parent is null; or even making a list of names and having a turnscript that removes the listed objects.
My usual approach (note: still not really necessary) is a turnscript like:

objects = FilterByAttribute (FilterByNotAttribute (AllObjects(), "prototype", null), "parent", null)
while (ListCount (objects) > 0) {
  obj = ListItem (objects, 0)
  if (not obj.prototype = obj) destroy (obj.name)
  else list remove (objects, obj)

Does this seem sound, or would it be better to use move object and pick a destination instead?

RemoveObject (this) is exactly the same as MoveObject (this, null) or this.parent = null (which is marginally more efficient, but less human-readable)

There's no real advantage to using any particular method. I've seen games that put unnecessary objects in an unreachable container, or just move them to null. I think these approaches are all equivalent.

Awesome! Thanks again. I'm learning more every day about this system. You have been beyond helpful.

Log in to post a reply.