Pain with Panes: Issue 2: Scenery in Containers

If I have a scenery object in a container it is revealed in the Inventory pane but not in Places and Objects. This occurred with a spade implemented as a surface on which there was a scenery handle. That seems an obvious bug but perhaps it has not been reported because what I'm doing is not the usual way to implement a spade? I want the handle to be inspected but never detached.


The scenery flag is ignored in the inventory. I think the issue's been discussed before; but it's not so easy to resolve.


I brought this up a while back. Pixie tried to fix it, but it caused a lot of problems in the web player.


@DavyB

You can call your next post "A Paean to Pain in the Pane (in the glass)".


@Dcoder, When I've completed the series of 'pains', a 'paean' may be in order depending on the responses, ...not just for the title! For the moment, I'm about to put up Pain #3...


I'm going to modify my games to minimise the effect of this issue and of course will leave the thread open in the hope that it can be looked at again.


Here's a workaround, maybe.

<turnscript name="Scenery excluder">
  <enabled />
  <script>
    script = "$(function () {if(!inner_updateList) {inner_updateList = updateList;} updateList = function(n, d) { inner_updateList(n, $.grep(d, function (i) { data = JSON.parse(i);"
    foreach (o, ScopeReachable()) {
      if (GetBoolean(o, "scenery")) {
        script = script + "if (data['ElementId'] == '"+o.name+"') { return false; } "
      }
    }
    script = script + "return true; })); });"
    JS.eval (script)
  </script>
  <changedenabled type="script">
    if (not this.enabled) JS.eval ("if (inner_updateList) {updateList = inner_updateList;}");
  </changedenabled>
</turnscript>

(off the top of my head; don't blame me for mismatched brackets. Basically, it replaces the javascript function updateList with one that removes scenery objects from the list before passing it on to the real updateList)

Or if you have a scenery-container and want to hide its children, you could do…

<turnscript name="Scenery excluder">
  <enabled />
  <script>
    hiddenobjects = NewObjectList()
    foreach (o, ScopeReachable()) {
      if (GetBoolean(o, "scenery")) {
        list add (hiddenobjects, o)
        hiddenobjects = ListCombine (hiddenobjects, GetAllChildObjects(o))
      }
    }
    script = "$(function () {if(!inner_updateList) {inner_updateList = updateList;} updateList = function(n, d) { inner_updateList(n, $.grep(d, function (i) { data = JSON.parse(i);"
    foreach (o, CompactList(hiddenobjects)) {
      script = script + "if (data['ElementId'] == '"+o.name+"') { return false; } "
    }
    script = script + "return true; })); });"
    JS.eval (script)
  </script>
  <changedenabled type="script">
    if (not this.enabled) JS.eval ("if (inner_updateList) {updateList = inner_updateList;}");
  </changedenabled>
</turnscript>

Sorry @mrangle, I'm mostly at 'cut and paste' level for JavaScript. Tried this and it gave the error message:

Error compiling expression 'CompactList(hiddenobjects)': FunctionCallElement: Could find not function 'CompactList(QuestList`1)'

Apologies if this is just a missing bracket!


Sorry, my mistake. CompactList should have been ListCompact.
Too many languages, with different conventions for function names.


Sorry @mrangle, no difference? No errors reported but scenery objects in containers are still being shown.


Ugh ... $.each works on enumerable objects, $.grep doesn't.

<turnscript name="Scenery excluder">
  <enabled />
  <script>
    firsttime {
      JS.eval ("inner_updateList = updateList")
    }
    hiddenobjects = NewObjectList()
    foreach (o, ScopeReachable()) {
      if (GetBoolean(o, "scenery")) {
        list add (hiddenobjects, o)
        hiddenobjects = ListCombine (hiddenobjects, GetAllChildObjects(o))
      }
    }
    script = "updateList = function(n, d) { $.each(d, function (i, x) { x = JSON.parse(x);"
    foreach (o, ListCompact(hiddenobjects)) {
      script = script + "if (x['ElementId'] == '"+o.name+"') { delete d[i]; } "
    }
    script = script + "}); inner_updateList(n, d) };"
    JS.eval (script)
  </script>
  <changedenabled type="script">
    if (not this.enabled) JS.eval ("if (inner_updateList) {updateList = inner_updateList;}");
  </changedenabled>
</turnscript>

Haven't tested in Quest; but have tested the JS it creates in the developer console in my browser.

Does that work any better?


Excellent! That does the job. I think I may be at the bottom of my issues list for panes. That's a pleasant surprise!!! I imagined the scenery in containers problem would be the easiest to sort out as I'd assumed that pretty much the same code would (should?) be used for both the Inventory and the Places and Objects panes.

Thanks again!


KV says:

There is no reason to fool around with scenery in containers. If something is scenery, it shouldn't be in the player's inventory (or inside of anything in the player's inventory).
If we want to hide something until a certain event has taken place, we need to set visible to false.
If we want something to exist just in case the player tries to interact with it, we need to make it scenery. (Sidenote: Once something set as scenery is directly taken, its scenery attribute is set to false.)

Can you say something more about the sidenote? Why is the attribute set to false? Is that the underlying issue?


image


Scenery is something which is either mentioned in the prose or something which would be expected to exist (floor, wall, sky, etc.). We don't want scenery listed in the objects list, but we do want the player to be able to interact with it. In other words, it is there, and you can see it and touch it, but it is unimportant.

Things which should be scenery in this scene:

  • counter
  • moldy food
  • dead insects
  • floor
  • fungus
  • fridge (scenery here because it is stuck closed and pointless)
  • kitchen (optional)

If something which is scenery can be taken, it should no longer be scenery once it is taken. Otherwise, you'd be carrying something which didn't show up in your inventory, which would be counter-productive.

Since that is the case, why script the code which updates the inventory lists to ignore scenery?


visible is an attribute which actually decides whether an object is in play or not.

If we want an object to appear after some specific event during play, we want to make it invisible at first by setting visible to false.

This is not scenery. Scenery is something which the player should be able to interact with but isn't important enough to list in object lists.

Once it's time to add our invisible object into play, we set visible to true and print a message letting the player know that something new has just been added to the location.


EXAMPLE GAME

There is a sticky note "underneath" the Squiffy Tart in the lunchbox. I don't want the player to see it or interact with it until the Squiffy Tart has been handled, so it starts off with visible set to false. Once the Squiffy Tart's parent object changes, the sticky note's visible attribute is set to true, and a message prints to let you know this.

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Scenery VS Visible">
    <gameid>89164b33-2b4a-4175-acaa-8ab6b3b246a5</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <attr name="autodescription_youarein_useprefix" type="boolean">false</attr>
    <attr name="autodescription_youcango" type="int">4</attr>
    <attr name="autodescription_youcansee" type="int">3</attr>
    <attr name="autodescription_description" type="int">2</attr>
    <start type="script">
      foreach (o, FilterByNotAttribute(AllObjects(),"feature_usable", true)) {
        o.inventoryverbs = ListExclude(o.inventoryverbs,"Use")
      }
      foreach (o, FilterByNotAttribute(AllObjects(),"take", true)) {
        o.displayverbs = ListExclude(o.displayverbs,"Take")
      }
    </start>
  </game>
  <object name="living room">
    <inherit name="editor_room" />
    <usedefaultprefix type="boolean">false</usedefaultprefix>
    <description><![CDATA[This room is completely bare. No furniture at all.<br/><br/>Please proceed to the next location.<br/>]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <exit alias="east" to="kitchen">
      <inherit name="eastdirection" />
    </exit>
  </object>
  <object name="kitchen">
    <inherit name="editor_room" />
    <description><![CDATA[This may be the nastiest kitchen you've ever seen. The counter is covered in moldy food and dead insects. The floor is so greasy, you could skate on it. And there is some strange fungus growing on the outside of the fridge.<br/>]]></description>
    <exitslistprefix>You can see an exit to the</exitslistprefix>
    <usedefaultprefix type="boolean">false</usedefaultprefix>
    <exit alias="west" to="living room">
      <inherit name="westdirection" />
    </exit>
    <object name="counter">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <scenery />
      <feature_container />
      <listchildren />
      <look>Its very nasty!  The dead bugs are the worst part.</look>
      <takemsg>It is fixed in place.</takemsg>
      <addscript type="script">
        msg ("Putting anything on this funky counter would be unwise.")
      </addscript>
    </object>
    <object name="moldy food">
      <inherit name="editor_object" />
      <inherit name="edible" />
      <scenery />
      <look>You can't even tell what sort of food this used to be!</look>
      <take />
      <feature_edible />
    </object>
    <object name="dead insects">
      <inherit name="editor_object" />
      <inherit name="plural" />
      <scenery />
      <look>Eww... Cockroaches!</look>
      <takemsg>You'd rather not touch the dead insects.{once:  Plus, there's nothing to be done with them, anyway.}</takemsg>
    </object>
    <object name="floor">
      <inherit name="editor_object" />
      <scenery />
      <look>It is completely covered in grease and something which looks a lot like dried-up chicken blood.</look>
      <takemsg>You can't take the floor.{once:  Good try, though!}</takemsg>
    </object>
    <object name="kitchen1">
      <inherit name="editor_object" />
      <alias>kitchen</alias>
      <scenery />
      <takemsg>Impossible.</takemsg>
      <look type="script">
        ShowRoomDescription
      </look>
    </object>
    <object name="fridge">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <inherit name="container_lockable" />
      <alt type="stringlist">
        <value>refrigerator</value>
      </alt>
      <feature_container />
      <open />
      <nokeymessage>It is stuck closed.</nokeymessage>
      <takemsg>It's much too heavy to pick up.</takemsg>
      <scenery />
    </object>
    <object name="lunchbox">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <alias>He-Man lunchbox</alias>
      <look><![CDATA[A red, plastic lunchbox featuring an image of the toy once meant to be Conan.<br/>]]></look>
      <feature_container />
      <take />
      <listchildren />
      <object name="Squiffy Tart">
        <inherit name="editor_object" />
        <inherit name="edible" />
        <take />
        <feature_edible />
        <eatmsg>Mm.  Tastes just like heaven!</eatmsg>
        <look><![CDATA[Mmmm...  A "Heaven-flavored" Squiffy Tart...<br/><br/>It looks quite tasty!]]></look>
        <ontake type="script"><![CDATA[
          firsttime {
            sticky note.visible = true
            msg ("<br/>You notice a {object:sticky note} which was previously hidden underneath the Squiffy Tart.")
          }
        ]]></ontake>
        <changedparent type="script"><![CDATA[
          if (game.pov = this) {
            if (IsDefined("oldvalue")) {
              OnEnterRoom (oldvalue)
            }
            else {
              OnEnterRoom (null)
            }
            if (game.gridmap) {
              MergePOVCoordinates
            }
          }
          this.hasbeenmoved = true
          firsttime {
            sticky note.visible = true
            msg ("<br/>You notice a {object:sticky note} which was previously hidden underneath the Squiffy Tart.")
          }
        ]]></changedparent>
        <eat type="script">
          if (HasString(this, "eatmsg")) {
            msg (this.eatmsg)
          }
          else {
            msg (DynamicTemplate("Eaten", this))
          }
          if (HasInt(game.pov, "health")) {
            game.pov.health = game.pov.health + this.eathealth
          }
          RemoveObject (this)
        </eat>
        <move type="script">
          parent = this.parent
          if (not parent = game.pov) {
            msg ("You move it a little.")
            MoveObject (this, game)
            MoveObject (this, parent)
          }
          else {
            msg ("You wave it around in the air a little.")
          }
        </move>
      </object>
      <object name="sticky note">
        <inherit name="editor_object" />
        <look><![CDATA[A small sticky-note.<br/><br/>It reads:<br/><br/>"If you can read this, you moved the Squiffy Tart!"]]></look>
        <visible type="boolean">false</visible>
        <takemsg>It is stuck to the lunchbox.</takemsg>
        <read>"If you can read this, you moved the Squiffy Tart!"</read>
      </object>
    </object>
    <object name="fungus">
      <inherit name="editor_object" />
      <look>The fridge has begun to grow hair!</look>
      <takemsg>You try to scrape off some fungus, but it's too thick and tough.{once:  (Perhaps you should just leave it be.)}</takemsg>
      <usedefaultprefix type="boolean">false</usedefaultprefix>
      <prefix>some</prefix>
      <scenery />
    </object>
  </object>
</asl>

J_J

Because of how scenery works when it's not in the player's inventory, I also initially was using scenery as details or parts of a visible object. It quickly became clear that if I tried to move it into the inventory with the object, it no longer functioned as scenery. Not a big deal, but a bit confusing for a beginner.

One way to do what you want is to delete the handle object. Then, make a command that is "Look at handle; look at spade handle; look at spade's handle" then add the "if player is carrying spade" or "if player is in (room with spade)" and then have the command run a description of the spade's handle. Not super elegant, but does work.


J_J,

I've run into and tried the same things, and it drove me crazy, too! (But I'm much better now! (maniacal laugh))

...and mrangel just said the same thing on another thread where I mentioned scenery.

Creating parts of objects as scenery seems to be the first time most authors notice this unusual behavior. Should we start up a club? We could call it the "Weird Things Fan-club", aka: WTF.


For the record, I was trying to explain how Quest handles scenery and visible objects, not how I believe it should actually work.


@KV, I meant comment specifically on "Once something set as scenery is directly taken, its scenery attribute is set to false."

This would explain the inconsistency between seeing the handle in the inventory but not seeing it on the ground before it is picked up. So why is scenery set to false and then, presumably, back to true when the spade is dropped again? Why bother with this change?


So why is scenery set to false and then, presumably, back to true when the spade is dropped again?

It doesn't get changed back.

Once you take it, it is no longer scenery.

image


Scenery objects inside of carried objects

The inventory list does not check for scenery. As far as Quest is concerned, scenery shouldn't be held, only examined. Therefore, it doesn't "know" to check for it in the inventory list. It simply displays the list.


Testing this code.

image


image


When I move the code which declares inner_updateList to the User Interface Initialisation script, the apple (scenery) shows up in a loaded save until I do something that alters the inventory list's contents.

image


NOTE:

The only method I've tried that behaves correctly online and in the desktop player is the one that hides the list elements with a setTimeout, and I agree with mrangel: we should be doing something in updateList instead. And modifying updateList works when including a Javascript file, because that file is loaded before any of the game's code, therefore all variables and functions exist. This cannot be done in inituserinterface, though, because Quest is loading the list before it runs that when it loads a saved game online. (Online authors can't add JS files, so I try to avoid things which depend on them.)


Oh, I didn't know that was possible...I don't like it...but there are enough open issues at the moment! :)

As the 'displaying scenery in the inventory' issue has been brought up before, probably the best I can do is add encouragement for it to be resolved. In the meantime, I now know about it and can work around it until a fix has been found.


Oh, I didn't know that was possible...I don't like it...but there are enough open issues at the moment! :)

...sorry KV, this refers to an earlier message talking about picking up scenery and it being converted to non-scenery in the process.


You're cool, DavyB!


When picking up scenery, if it can be taken, I think Quest has it right.

If it can be taken, it should show up in your inventory when carried.

If it can be moved, it does not need to be scenery, because scenery is not listed in the room description. If you go through the trouble of making an object takeable, it must serve some purpose. So, if it is dropped in a random location while still flagged scenery, it would be lost forever if the player didn't remember where he or she dropped it.


Now, I do agree with everyone else about scenery in the Inventory pane.

If something is carried and is still flagged scenery, this can only mean that the object was not directly taken. (It is part of something carried, or a child object of something carried.)

MrAngel came up with a fix, but it can't really be added to games in the web editor. It has attracted Pixie's attention, though. So, it will probably end up in Quest's code in a future release.


Another thing that seems to put players off is the lack of a Take verb on objects inside of carried containers. (I don't want anyone to try to change this behavior or anything. I'm just rambling on incessantly at this point.)

If I there is a plate with an egg on it, and I pick up the plate, the pane list shows both plate and egg in the inventory. Since 'Take' is a display verb and not an inventory verb, it doesn't display the 'Take' option on the egg, even though some games will say, "You're not holding that." when trying to eat an object which isn't held.

Now, I know I can just enter TAKE EGG, but not all players know that. Most players seem to think they can't use verbs which aren't listed on the objects.


@DavyB

My take on the "Once something set as scenery is directly taken, its scenery attribute is set to false" is thinking about a sandwich.

For example…

==> examine sandwich
It's a half-eaten toasted cheese and broccoli sub

==> take sandwich
You pick it up

At this point, the "cheese" object is still scenery. You want to be able to type "examine cheese", so you can find out if it's blue cheese or not. But you don't want it to appear in your inventory. At present, the cheese and broccoli would clutter your inventory list because Quest ignores the scenery attribute when displaying an inventory list. That's what the code I posted (somewhere) would change.

But...

==> take broccoli
You take a piece of green stuff out of the sandwich. Why would you do this?!?

==> take cheese
You now have a pocket full of stringy, melted cheese. Why would you do this?!?

==> drop broccoli
You put broccoli on the floor. Best place for it

Now, you want the cheese to show in the inventory pane. Especially if you grabbed the broccoli/cheese and left the sandwich behind. Because it's reasonable to assume that the sandwich contains cheese, and to type "examine cheese" even if it isn't in the "You can see:" list. But it isn't reasonable for the player to assume their pockets contain cheese and broccoli. So, if you directly take a scenery object, it ceases to be scenery, and will be displayed. The same for the broccoli on the floor; it is no longer scenery once you've moved it, so appears in the objects list for the room you dropped it in.

Hmm...

==> put dandruff on sandwich
Ewwww!

Here's a scenery object that starts in the inventory; the player shouldn't be alerted to its presence unless they actually look for it or try to interact with it. But as soon as you put it on the sandwich, it should change to non-scenery, because it's out of place and therefore obvious.

Does Quest automatically clear the scenery attribute when you drop things, or just when you pick them up?
I think it should probably be both; because if the player moves a scenery object, they should probably be able to see where they put it.


That's what the code I posted (somewhere) would change.

This is the latest, isn't it?

http://textadventures.co.uk/forum/quest/topic/khcqc--pfuyspiyacrpvvg/pain-with-panes-issue-2-scenery-in-containers#4cb10778-22c3-462b-842d-7b4fd5a44e38

This is what I found while testing it:

http://textadventures.co.uk/forum/quest/topic/khcqc--pfuyspiyacrpvvg/pain-with-panes-issue-2-scenery-in-containers#f32b6b42-468f-49fc-942d-2f0c1548c7b2


I think your method (modifying updateList) is better than the way I'm making it work with the call to setTimeout, but I can't make the former work when loading a saved game online without something being messed up.


but I can't make the former work when loading a saved game online without something being messed up.

Hmm... each time you send the new list to the browser, you could do:

var json = $("#divOutput #ignoreScenery");
if (!json.length) { json = $("<div>", {id: "ignoreScenery"}).appendTo("#divOutput").hide(); }
json.attr("scenery", JSON.stringify(ignoreScenery));

and on loading a saved game (within the JS file? In a 'ready' block? In the UI initialisation script? I don't know what order things are loaded in) you could do:

var json = $("#divOutput #ignoreScenery");
if (json.length) {
  ignoreScenery = JSON.parse(json.attr("scenery"));
  $("#lstInventory").children().each(function () {
    if ($.inArray ($(this).data("elementid"), ignoreScenery) >= 0) {
      $(this).remove();
    }
  });
}

If the 'load' script is included in the same file as the modified updateList, it should work I think. If it's loaded before the lists are restored to their pre-save contents, then ignoreScenery (I forget if that's what we called the array, you know the one I mean) will be loaded, and the modified updateList will work correctly. If the built-in updateList is used to restore the inventory, then that function should remove the scenery items again.

Am I missing something?


(if our scripts are being loaded before the output text is restored, the <div> created in the former code chunk could be replaced by a <script> containing the latter. Unless Quest has some special behaviour to prevent users doing this (and why should it?), it could run as soon as the saved divOutput content is restored)

Disclaimer: I'm overstressing, and haven't bothered looking up the actual element IDs. You know the ones I mean, I think.


First off, there is no need to fool with this until everything is nice and calm. I'm only posting it now because I'll forget it later.


This is the order in which things fire in the desktop player:

1: LOADING PLAYERCORE.JS

2: LOADING DESKTOPPLAYER.JS

3: javascript.js function

4: User interface initialisation.

5: Start script.

6: script ELEMENT IN divOutput

7: library_object initialisation

8: library_object js string attribute

9: in-game_object initialisation

10: in-game_object js string attribute

11: before entering room first time

12: before entering room

13: after entering room first time

14: after entering room


...and this is loading a saved game:

1: LOADING PLAYERCORE.JS

2: LOADING DESKTOPPLAYER.JS

3: javascript.js function

4: User interface initialisation.

5: script ELEMENT IN divOutput


I'm off to try your latest suggestions, mrangel!


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

Support

Forums