Cloning an object with it's original attributes/adding attributes to a random clone?

Hi, complete Quest + coding noob here. For my game I wanted to set up a shop, but I wanted it to restock after a certain amount of time/on trigger. For now I've set the trigger for when the player leaves the shop, and I've made it clone a random object from a room and put it into the stock room. However, the value of the object doesn't follow the clone, and I don't know how to add value to a new object if the name of the new object is random. Sorry if this is worded weirdly, but can anyone help?


Io

Try something like this. And pardon my psuecode:

switch case(ClonedObject.alias){
case("Cheap Thing"){
ClonedObject.value=5
}
case("Medium Thing"){
ClonedObject.value=15
}
case("Expensive Thing"){
ClonedObject.value=9999
}
}

Hope this helps!


the value of the object doesn't follow the clone

Why not? If the value is an attribute of the object, the clone will have the same value.
Is the value stored somewhere other than on the object?

I don't know how to add value to a new object if the name of the new object is random

If you're creating the object using the CloneObject functions, they return a reference to the new clone.

So to set an attribute on the new clone, you can do:

newClone = CloneObjectAndMove (Some Original Object, Stock Room)
newClone.value = 65

or if your shop has a dictionary holding a list of stock and their values (which is a weird thing to do, but it would explain why cloning the objects doesn't retain their value), it would be something like:

newClone = CloneObjectAndMove (Some Original Object, Stock Room)
dictionary add (Shop.pricelist, newObject.name, 35)

Sadly it returned with a

Error running script: Error compiling expression 'ClonedObject.alias': Unknown object or variable 'ClonedObject'

Do you know how to fix it?


"Why not? If the value is an attribute of the object, the clone will have the same value.
Is the value stored somewhere other than on the object?"

When I increase the price of the object in the inventory menu, object.price changes.

When I look in the debugger, object.price does follow through, but for some reason the value doesn't show up in game and I can't buy it.

I'm using

CloneObjectAndMove (PickOneChild (CompleteStockList), Stock1)

To make the clone and move it to the stock room.


Error running script: Error compiling expression 'ClonedObject.alias': Unknown object or variable 'ClonedObject'

That piece of code assumes that you have a variable named ClonedObject to access the clone, and sets its price based on its alias. This is a really strange thing to do and I wouldn't recommend it.

If you have a clone object for which you want to find out the original's name, it's more usual to use object.prototype.

When I look in the debugger, object.price does follow through, but for some reason the value doesn't show up in game and I can't buy it.

The clones will all have the same attributes as the original at the time they were created.

When you increase object.price, are you increasing the price of the original, or a clone that's already been created?
Or do you want to increase the price of both clones and the original?


Okay, I'm going to start from the beginning here and try to find where the issue is.

I have three objects in a room that I want to be randomly cloned. These three objects have the name VibrantBlueRaincoat, CanaryYellowRaincoat, and ScuffedRedSneakers. The aliases are the same, but with spaces in between. Each of these originals have their .price set to 5, which was decided by me adjusting the value in the inventory tab. The shop system I'm using is the standard one described in the quest documentation, with a stock room devoid of anything but the stuff that I'm immediately selling.

I used CloneObjectAndMove (PickOneChild (CompleteStockList), Stock1) to clone one original object with the .price attribute of 5. When I go ingame and trigger the script, the cloned object appears in the correct place, but no price is shown in the UI and the item cannot be bought.

See here: https://imgur.com/LpO7t5B and https://imgur.com/NjzGQvQ for debugger looking at a cloned object.


Did you run the SetUpMerchandise function on the cloned objects?

I'm not sure if this should be necessary for clones or not; but it's the first thing that jumps out at me.

It's hard to spot a problem when I can't see your code, but that's the first thing I'd try to check.


The only modification I made to the Shop functions was in BuyObject and I changed it so that it wouldn't immediately clone and replace with exactly the same copy. Also, how do I run SetUpMerchandise on an object that doesn't exist yet?


That makes sense, then.

The SetUpShop function calls SetUpMerchandise on all the objects in the stock room. This modifies the object's listalias so that it shows the price, and adds the "buy" verb.

The player buys the object, which removes the price from the alias, and removes the buy/steal scripts so that the player can drop and pick it up again. Because you've disabled cloning, it makes these changes to the original object.

Then you clone the object. Cloning copies the object's current values, so as far is the shop is concerned the object has already been sold.

If you want to make the object sellable again, you need to run SetUpMerchandise on the clone.

For example, change CloneObjectAndMove (PickOneChild (CompleteStockList), Stock1)
to SetUpMerchandise (CloneObjectAndMove (PickOneChild (CompleteStockList), Stock1))


mrangel has already covered/helped with all of this, but just here's a hopefully simple explanation of cloning:

let's say we create an Object:

<object name="ball">

  <inherit name="editor_object" />

  <attr name="alias" type="string">ball</attr>

  <attr name="shape" type="string">sphere</attr>

  <attr name="color" type="string">red</attr>

  <attr name="radius" type="int">2</attr>

  <attr name="deflated" type="boolean">false</attr>

  <attr name="kick" type="script">

    msg ("you kick the ball")

  </attr>

  <displayverbs type="stringlist">

    <value>kick</value>

  </displayverbs>

  <inventoryverbs type="stringlist">

    <value>kick</value>

  </inventoryverbs>

</object>

<verb>

  <property>kick</property>
  <pattern>kick</pattern>

  <defaultexpression>You can't kick that!</defaultexpression>

</verb>

then, when you clone it, another Object is created, with everything (all attributes) the same, except for its 'name' String Attribute, as this has to be unique (it adds an increasing number to the end of the name, each time you clone), due to the 'name' String Attribute, being its ID for the quest engine:

// original 'ball' Object:

<object name="ball">

  <inherit name="editor_object" />

  <attr name="alias" type="string">ball</attr>

  <attr name="shape" type="string">sphere</attr>

  <attr name="color" type="string">red</attr>

  <attr name="radius" type="int">2</attr>

  <attr name="deflated" type="boolean">false</attr>

  <attr name="kick" type="script">

    msg ("you kick the ball")

  </attr>

  <displayverbs type="stringlist">

    <value>kick</value>

  </displayverbs>

  <inventoryverbs type="stringlist">

    <value>kick</value>

  </inventoryverbs>

</object>

-----------------------------------------------------------

// clones (3 of them in this example) of the 'ball' Object:

<object name="ball1">

  <inherit name="editor_object" />

  <attr name="alias" type="string">ball</attr>

  <attr name="shape" type="string">sphere</attr>

  <attr name="color" type="string">red</attr>

  <attr name="radius" type="int">2</attr>

  <attr name="deflated" type="boolean">false</attr>

  <attr name="kick" type="script">

    msg ("you kick the ball")

  </attr>

  <displayverbs type="stringlist">

    <value>kick</value>

  </displayverbs>

  <inventoryverbs type="stringlist">

    <value>kick</value>

  </inventoryverbs>

</object>

<object name="ball2">

  <inherit name="editor_object" />

  <attr name="alias" type="string">ball</attr>

  <attr name="shape" type="string">sphere</attr>

  <attr name="color" type="string">red</attr>

  <attr name="radius" type="int">2</attr>

  <attr name="deflated" type="boolean">false</attr>

  <attr name="kick" type="script">

    msg ("you kick the ball")

  </attr>

  <displayverbs type="stringlist">

    <value>kick</value>

  </displayverbs>

  <inventoryverbs type="stringlist">

    <value>kick</value>

  </inventoryverbs>

</object>

<object name="ball3">

  <inherit name="editor_object" />

  <attr name="alias" type="string">ball</attr>

  <attr name="shape" type="string">sphere</attr>

  <attr name="color" type="string">red</attr>

  <attr name="radius" type="int">2</attr>

  <attr name="deflated" type="boolean">false</attr>

  <attr name="kick" type="script">

    msg ("you kick the ball")

  </attr>

  <displayverbs type="stringlist">

    <value>kick</value>

  </displayverbs>

  <inventoryverbs type="stringlist">

    <value>kick</value>

  </inventoryverbs>

</object>

-----------------

<verb>

  <property>kick</property>
  <pattern>kick</pattern>

  <defaultexpression>You can't kick that!</defaultexpression>

</verb>

so, if doing totally random/dynamic, the only way to access a specific clone is to store it's address/memory location within an Attribute, upon the clone's creation:

this is known as an Object (reference/pointer) Attribute, an example:

create ("katana") // scripting that creates a 'katana' Object

player.weapon = katana // storing the memory address/location of the 'katana' Object into the Object (reference/pointer) custom (my own named/created in this example) 'player.weapon' Object (reference/pointer) Attribute

so, now, whenever we use 'player.weapon', we are using the 'katana'

for an example:

create ("katana") // scripting that creates a 'katana' Object
create ("short_sword") // scripting that creates a 'short_sword' Object

katana.damage = 50 // setting/creating a custom 'damage' Integer Attribute on the 'katana' Object
short_sword.damage = 10 // setting/creating a custom 'damage' Integer Attribute on the 'short_sword' Object

player.weapon = katana
orc.life = orc.life - player.weapon.damage
// the orc's life will be reduced by the 'damage' (50) of the 'katana' Object

player.weapon = short_sword
orc.life = orc.life - player.weapon.damage
// the orc's life will be reduced by the 'damage' (10) of the 'short_sword' Object


now, for random/dynamic usage, we need to store it (a Clone of the 'ball' Object in this example) immediately upon/along with its creation, an example:

create ("ball")

create ("example_object")

example_object.clone_1 = CloneObject (ball) // 'ball1' clone object
example_object.clone_2 = CloneObject (ball) // 'ball2' clone object
example_object.clone_3 = CloneObject (ball) // 'ball3' clone object

and now, we can access the clones of the 'ball' Object, via using: 'example_object.clone_1', 'example_object.clone_2', 'example_object.clone_3'

an example:

msg ("original object: " + ball.name)
msg ("clone object 1: " + example_object.clone_1.name)
msg ("clone object 2: " + example_object.clone_2.name)
msg ("clone object 3: " + example_object.clone_3.name)

// output/display:

original object: ball
clone object 1: ball1
clone object 2: ball2
clone object 3: ball3


all Objects can be referenced/accessed via their 'names' (ID) String Attributes, their other Attributes (but if you got Objects with the same Attributes and/or Values, then you can NOT reference/access a specific Object, obviously), and/or their Object Types / Types / Inherited Attributes (but if you got Objects with the same Object Types / Types / Inherited Attributes, then you can NOT reference/access a specific Object, obviously)

(also, with Strings, there's a lot of cool/neat string manipulation stuff, such as using concatenation as one example, that you can do with them, for getting/doing dynamic matching)

( the string manipulation functions: https://docs.textadventures.co.uk/quest/functions/#string )

some examples:

(most of my examples only show checking for String Attributes, but you can check for the other types of Attributes too: Integers/ints, Doubles, Booleans, Scripts, Lists, Dictionaries, etc)

foreach (object_variable, AllObjects ()) {
  if (object_variable.name = "WHATEVER") {
  } else if (object_variable.alias = "WHATEVER") {
  } else if (HasString (object_variable, "color")) {
  } else if (GetString (object_variable, "color") = "red") {
    // yes, I know that this script ('GetString') won't ever be checked (due to my setup example in the 'HasString' script before it), as I'm just giving examples of various ways of referencing/accessing an Object, so I don't care about this incorrect scripting ordering logic
  } else if (DoesInherit (object_variable, "ball_type")) {
  } else if (ListContains (object_variable.color_stringlist, "red")) {
  } else if (Contains (object_variable, water)) {
  }
}

PS

forgot to explain this, you can change a clone's Attribute's values (but NOT if its an Inherited Attribute, unless you recreate the Attribute on the clone Object), by storing it's address/memory location within an Object (reference/pointer) Attribute

for example:

create ("example_object")

create ("katana")
katana.damage = 10
msg (katana.damage)
// output: 10

example_object.clone = CloneObject (katana)

msg (example_object.clone.damage)
// output: 10

example_object.clone.damage = 50

msg (example_object.clone.damage)
// output: 50


PSS

the recent version of quest has changed the clone feature a bit, so that when you clone something, it's original Object's address/memory location gets stored into the 'prototype' Object (reference/pointer) Attribute, for all clones being made, which is extremely useful

// original objects:

<object name="ball">
</object>

<object name="food">
</object>

---------------

// clones

<object name="ball1">

  <attr name="prototype" type="object">ball</attr>

</object>

<object name="ball2">

  <attr name="prototype" type="object">ball</attr>

</object>

<object name="ball3">

  <attr name="prototype" type="object">ball</attr>

</object>

<object name="food1">

  <attr name="prototype" type="object">food</attr>

</object>

<object name="food2">

  <attr name="prototype" type="object">food</attr>

</object>

<object name="food3">

  <attr name="prototype" type="object">food</attr>

</object>

Edit: Ah, you beat me to it.

when you clone it, another Object is created, with everything (all attributes) the same, except for its 'name' String Attribute

I would advise against using Clone or ShallowClone directly unless you've got a specific reason for doing so.

For a new user, unless there's some specific reason not to, you should be using CloneObject, CloneObjectAndMove, or CloneObjectAndMoveHere depending where you want the clone to be. (There's also a CloneObjectAndInitialise, which has a few rather specialised uses)

There may be 3 attributes different from the original when you use these clone functions (which makes them a lot easier to use):

  • The name attribute of each clone will be different, usually adding a number to the end. You shouldn't use the name in any case.
  • The alias attribute will be set to the original object's alias if it had one, and the original object's name otherwise. This means that if you clone an object named "ball" which has no alias, the player will see the alias "ball" instead of the name "ball2" for clones.
  • The prototype attribute will be set to the original object's prototype if it has one, or the original object otherwise. This means that you have an attribute which lets you find the original object a clone was created from, even if it's a clone of a clone of a clone.

So, while "everything except for its name" may be true for objects cloned with Clone, it's probably better to be aware of the other changes made by the CloneObject functions.

prototype especially is very useful. It lets you use expressions like FilterByAttribute (AllObjects(), "prototype", ball) which returns an objectlist containing all the clones of ball, or if (object.prototype = ball) { which lets you find out quickly if an object is a ball.


It's worth pointing out that Clone() and ShallowClone() don't set prototype by default. It only exists in the CloneObject functions.

Also that as of Quest 5.8, cloned children are handled differently. If I remember correctly:

  • If you clone an object using Clone, it will be created in the same location as the original. Any children it has will be created inside it.
    • If you clone an object with children (for example a bag of apples) using Clone, the children are also cloned. The cloned apple is created inside the cloned bag.
  • If you clone an object using ShallowClone, its children are not cloned, so the cloned bag will be empty.
    • As far as I know, this is new in Quest 5.8
    • If you're using a stacking system, this could cause unexpected behaviour
  • If you clone an object using CloneObject, the object will be cloned using ShallowClone, and its alias set, and then its children are cloned and moved.
    • This means that the cloned apples are created inside the original bag, and then moved to the cloned bag. The apples' changedparent script will be run, which didn't happen in Quest 5.7
    • This means that the children of a cloned object will now have their hasbeenmoved flag set to true even if the object has never been moved
    • This might cause some stacking libraries to behave unexpectedly
  • If you clone an object with CloneObjectAndMove or CloneObjectAndMoveHere, the changedparent scripts will be run for the object and its children.
    • I think this is different from Quest 5.7, which would only run changedparent for the top cloned object.

This mostly doesn't matter. But some stacking libraries, or (for example) a bag whose alias changes to "empty bag", "bag", or "overloaded bag" depending on the weight of stuff inside it, may behave differently from how they did in Quest 5.8

(I'm not sure, but I think cloning a limited container which contains another container might now have a bug. I'll take a look at the code again at some point)


Log in to post a reply.

Support

Forums