Pain with Panes: Issue 1: Handling Containers

When I first saw game panes in about 2011, I was very enthusiastic about them. Since then, however, the introduction of the map and hyperlinks has made the panes less useful, made worse by them not keeping pace with other developments. Perhaps the most obvious issue is the handling of containers, where the contents of a box (say) are shown at the same level as the box itself, in both the Inventory and Places and Objects panes. Has anyone looked at making this a bracketed structure: box (item1, item2 (item3)) ... or having items indented below their container name to show their relationship?

box
  item1 (within box)
  item2 (within box)
    item3 (within item2)

There is code for this somewhere.

Give me a bit. I shall return.


My solution (a bit wonky) was to use a turnscript:

foreach (o, GetAllChildObjects(game.pov.parent)) {
  o.listalias = "[["+o.parent.name+"]]"+GetDisplayAlias(o)
}

Then modified the updateList JS function to order and indent the items.
(rather than indenting, I used unicode box-drawing characters to make a kind of sketched tree; but that's a bit overcomplex)


I know I saw some code for this somewhere before...

This is the best I've come up with:

OLD CODE

indent_kids (turn script / enabled when play begins)

// version 2 (using symbol instead of hyphen)
foreach (o, ScopeReachable()) {
  if (not o.parent = game.pov and not o.parent = game.pov.parent) {
    o.listalias = "↳  " +GetDisplayAlias(o)
  }
}

In the start script:

invoke(indent_kids.script)

image


EDIT

I think mrangel posted that code I'm searching for... (He'll probably drop in soon.)

Ah.. I see mrangel already dropped in (while this post was in progress). Hrmm... Maybe I dreamed that code up...


Note that my code will not account for object inside of objects inside of objects.


Thanks @KV and @mrangel, as usual my goal is to identify problems that might get sorted out in some future release if a good general solution can be found. My next issue is a bug which should be easy to fix but there is also a general question attached to it.


@KV
Does that code work neatly? I don't remember the inventory items being sorted by default, in which case your indented item might not appear after the right container.

Another thought that occurs to me might be changing the listalias to "container ↳ alias" or "container ↳ container ↳ alias".

foreach (o, ScopeReachable()) {
  parent = o.parent
  o.listalias  = Right (o.listalias, LengthOf (o.listalias) - InstrRev (o.listalias, "↳" - 5)
  while (not parent = game.pov and not parent = game.pov.parent) {
    o.listalias = GetDisplayAlias(parent) + "↳ " + o.listalias
    parent = parent.parent
  }
}

Then, updateList can sort the whole list alphabetically, then use a simple regexp to remove the container names as it displays them (string.replace(/^(↳)*.*?(↳)*/, "$1$2")). (Maybe this is unnecessary; I don't think the lists were in a useful order last time I played with it, but I can't be sure. I'm on holiday now, so don't have the time to play around with it)


Does that code work neatly? I don't remember the inventory items being sorted by default, in which case your indented item might not appear after the right container.

I didn't think it would, but it seems to. I tried to make it mess up, but not for very long. Plus, I didn't create enough objects to thoroughly test it, but I will today.

I'm thinking the same thing you are, though, mrangel. The order will become disorder eventually. You last suggestion will probably be the best way to go.


I tested my code out some more, and I still couldn't break it, but I did find that I needed to change the list alias back when an object was not inside of another object.

OLD CODE

      foreach (o, ScopeReachable()) {
        if (not o.parent = game.pov and not o.parent = game.pov.parent) {
          o.listalias = "↳  " +GetDisplayAlias(o)
        }
        else {
          if (HasAttribute(o, "listalias")) {
            o.listalias = Replace(o.listalias, "↳  ", "")
          }
        }
      }

Here's the test game:

<!--Saved by Quest 5.8.6705.22622-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="List Indention">
    <gameid>fcb3ab77-3691-4646-a6f8-8f8e4fb0d73b</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <start type="script">
      invoke (indent_kids.script)
    </start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="basket">
      <inherit name="editor_object" />
      <inherit name="container_open" />
      <feature_container />
      <open type="boolean">false</open>
      <close type="boolean">false</close>
      <listchildren />
      <take />
      <object name="egg">
        <inherit name="editor_object" />
        <take />
      </object>
      <object name="stick">
        <inherit name="editor_object" />
        <take />
      </object>
    </object>
    <object name="box">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <feature_container />
      <listchildren />
      <take />
      <object name="ball">
        <inherit name="editor_object" />
        <take />
      </object>
      <object name="toothpick">
        <inherit name="editor_object" />
        <take />
      </object>
    </object>
  </object>
  <turnscript name="indent_kids">
    <enabled />
    <script><![CDATA[
      foreach (o, ScopeReachable()) {
        if (not o.parent = game.pov and not o.parent = game.pov.parent) {
          o.listalias = "&#8627;  " +GetDisplayAlias(o)
        }
        else {
          if (HasAttribute(o, "listalias")) {
            o.listalias = Replace(o.listalias, "&#8627;  ", "")
          }
        }
      }
    ]]></script>
  </turnscript>
</asl>

EDIT

It still doesn't handle object in objects in objects.


It still doesn't handle object in objects in objects.

If there's no worry about ordering, then you can change the 'if' to a 'while' to count how many levels down you are:

      reachable = ScopeReachable()
      foreach (o, reachable) {
        if (HasString(o, "listalias")) {
          o.listalias = Replace(o.listalias, "&#8627;  ", "")
        }
        else {
          o.listalias = GetDisplayAlias (o)
        }
        parent = o.parent
        while (ListContains (reachable, parent) and not parent = game.pov) {
          o.listalias = "&#8627;  " + o.listalias
          parent = parent.parent
        }
      }

Edit: changed the condition on the while loop; so it behaves sanely in odd cases where there are reachable objects not contained by the player's direct parent.


I didn't think it would, but it seems to.

It probably arranges them in the order they appear in the game code, and that is probably - coincidentally - the order you want if this is a simple test. Make sure you test it properly by putting an item that is higher in the game code in to the container during play.


I shuffled all the objects around, dropped some, picked stuff back up, put things in containers, dropped stuff, picked stuff up again, moved things from container to container, and it always kept the stuff in a container listed after the container.

It looks like they stay in the same order as they appear in the code in 5.7.2, except for being listed directly after the container in the list. In 5.8, I think it was in alphabetical order, but still listing children directly after their parent object.

I had to do some running around, but I'm about to see how it works with mrangel's code, about 10 extra objects, and whatever other monkey wrenches I can think of to throw into the mix.


Thanks everyone. The latest version of Giantkiller Too incorporates this feature, using two dashes for each level of indentation, (though there is only one level needed). To my eye, it makes the inventory much easier to follow. I'll not close this thread, however, in the hope that this or some similar presentation becomes standard in a future release.


Check this one out:

OLD CODE

Version 2 (adds two spaces instead of one)

  <function name="UpdateContentsInLists"><![CDATA[
    foreach (o, AllObjects()) {
      o.listalias = GetDisplayAlias(o)
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
    }
  ]]></function>

Add it to the start script.

Put it in an enabled turn script.

If you prefer leading hyphens to leading whitespace, change "&nbsp;&nbsp;" to "--".



Nice one KV :)

Not 100% sure, but might be more efficient to combine our methods. Something like:

  <function name="UpdateContentsInLists"><![CDATA[
    visible = ListExclude (ScopeVisible(), game.pov)
    foreach (o, visible) {
      o.listalias = GetDisplayAlias(o)
      foreach (c, ListParents(o)) {
        if (ListContains (visible, c)) {
          o.listalias = "&nbsp;&nbsp;" + o.listalias
        }
      }
    }
  ]]></function>

(I only care about visible objects, because those are the only things that will be passed to updateList. And once I've got a list of those objects, all I need to do for each object is count how many of its parents are also in that list)

I still think that if some other script is modifying listalias, such as putting "(worn)" on the end, this function shouldn't revert it to the display alias. So maybe start by checking if listalias already exists, and if so do while (StartsWith(o.listalias, "&nbsp;")) o.listalias = Mid(o.listalias, 7) instead of falling back on GetDisplayAlias().


Yes KV, that looks tidier, I'll go with that ...though have pushed the indent a little more to three spaces!


Just saw these posts. I will try mrangel's method next, but this is what I have right now (it handles scenery, and I've made the indent 2 spaces):

OLD CODE

  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("if (typeof(ignoreScenery) == 'undefined'){var ignoreScenery = [];}")
    foreach (o, AllObjects()) {
      o.listalias = GetDisplayAlias(o)
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+o.listalias+"');")
      }
      else {
        JS.eval ("if (typeof(ignoreScenery) != 'undefined'){var index = ignoreScenery.indexOf('"+o.listalias+"');	if (index > -1) {ignoreScenery.splice(index, 1);	}}")
      }
    }
    JS.eval ("setTimeout(function(){for(var i in ignoreScenery){$('#lstInventory').children().each(function(){if ($(this).html() == ignoreScenery[i] ){$(this).hide();}});};},1);")
  ]]></function>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script">
      SetTimeout (2) {
        UpdateContentsInLists
      }
    </inituserinterface>

http://textadventures.co.uk/games/view/59edxksdekis-korhnz5-q/contents-in-lists


Here's the combined function mrangel and I came up with:

OLD CODE

Version 3

  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("var ignoreScenery = [];")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+GetDisplayAlias(o)+"');")
      }
    }
    JS.eval ("setTimeout(function(){for(var i in ignoreScenery){$('#lstInventory').children().each(function(){if ($(this).data('elementid') == ignoreScenery[i] ){$(this).hide();}});};},1);")
  ]]></function>

It works!

I'm still using these, too:

  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script">
      firsttime {
      }
      otherwise {
        UpdateContentsInLists
        SetTimeout (2) {
          UpdateContentsInLists
        }
      }
    </inituserinterface>

Entire Example Game:

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Contents in Lists">
    <gameid>edd4c8c3-a135-448a-beb2-159be0998b30</gameid>
    <version>1.3</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script">
      firsttime {
      }
      otherwise {
        UpdateContentsInLists
        SetTimeout (2) {
          UpdateContentsInLists
        }
      }
    </inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="table">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <feature_container />
      <listchildren />
      <listchildrenprefix>On it, you see</listchildrenprefix>
      <takemsg>It's too big to take.</takemsg>
      <look><![CDATA[An oak dining table.<br/>]]></look>
      <displayverbs type="stringlist">
        <value>Look at</value>
      </displayverbs>
      <object name="bowl">
        <inherit name="editor_object" />
        <inherit name="container_open" />
        <feature_container />
        <close type="boolean">false</close>
        <open type="boolean">false</open>
        <displayverbs type="stringlist">
          <value>Look at</value>
          <value>Take</value>
        </displayverbs>
        <take />
        <listchildren />
        <look><![CDATA[A plastic bowl.<br/>]]></look>
        <object name="apple">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <scenery />
          <look><![CDATA[A red apple.<br/>]]></look>
        </object>
        <object name="orange">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <look><![CDATA[A navel orange.<br/>]]></look>
          <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
            }
            destroy (this.name)
            apple.scenery = false
            if (ListContains(ScopeReachable(),bowl)) {
              msg ("By some strange (but much appreciated) coincidence, an apple has magically appeared in the bowl!")
            }
          </eat>
        </object>
      </object>
      <object name="lazy susan">
        <inherit name="editor_object" />
        <inherit name="surface" />
        <feature_container />
        <listchildren />
        <listchildrenprefix>On it, you see</listchildrenprefix>
        <look>A lazy susan.  You can spin it to move items around the table.  (Your grandparents used to have one.)</look>
        <spin>You spin it.</spin>
        <displayverbs type="stringlist">
          <value>Look at</value>
        </displayverbs>
        <takemsg>It {once:appears to be}{notfirst:is} attached to the table.</takemsg>
      </object>
      <object name="hat">
        <inherit name="editor_object" />
        <inherit name="wearable" />
        <take />
        <look><![CDATA[It's just like Indiana Jones's hat.<br/>]]></look>
        <feature_wearable />
      </object>
    </object>
  </object>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
  <verb>
    <property>spin</property>
    <pattern>spin</pattern>
    <defaultexpression>"You can't spin " + object.article + "."</defaultexpression>
  </verb>
  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("if (typeof(ignoreScenery) == 'undefined'){var ignoreScenery = [];}")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+o.listalias+"');")
      }
      else {
        JS.eval ("if (typeof(ignoreScenery) != 'undefined'){var index = ignoreScenery.indexOf('"+o.listalias+"');	if (index > -1) {ignoreScenery.splice(index, 1);	}}")
      }
    }
    JS.eval ("setTimeout(function(){for(var i in ignoreScenery){$('#lstInventory').children().each(function(){if ($(this).html() == ignoreScenery[i] ){$(this).hide();}});};},1);")
  ]]></function>
</asl>

http://textadventures.co.uk/games/view/59edxksdekis-korhnz5-q/contents-in-lists


Wait...

I didn't test that last one enough. It is buggy. It adds lots of leading whitespace every turn.

I made a typographical error. This works!


Your JS is a bit wobbly. You're creating an array, and then every turn you add the scenery objects to it and remove the non-scenery objects. This means that after 10 turns, your array contains 10 copies of all the listaliases of scenery objects. (If an object that was scenery ceases to be scenery, I believe that code will only remove the first copy of it from the list.)

If you're taking that approach, the first line if (typeof(ignoreScenery) == 'undefined'){var ignoreScenery = [];} could be changed to just var ignoreScenery = [];, resetting the list each time the function is run. Then you don't need to remove non-scenery objects from the list.

There's still a potential issue if you have two objects with the same listalias in this case. It's unlikely to happen in a real game, but you could change $(this).html() to $(this).data('elementid') to get the object's name rather than it's alias, and guard against hard-to-diagnose bugs in the future.


Good stuff, mrangel!

I shall return!


Ah, you posted again while I was typing :p

(I think modifying updateList to discard scenery objects on-the-fly is likely a better solution than using a timeout to remove them after they've been added. I would have made a list of scenery objects to remove; but in my solution to that problem I was aiming for smaller code rather than runtime efficiency)


I just updated it again, mrangel. Now it checks the elementid. Good call!


I tried modifying updateList from the beginning, but it messed up the entire pane when removing something. Now that we're not removing anything, I'll try it again.

UPDATE

It works!

JS style:

function updateList(listName, listData) {
    var listElement = "";
    var buttonPrefix = "";

    if (listName == "inventory") {
        listElement = "#lstInventory";
        inventoryVerbs = new Array();
        buttonPrefix = "cmdInventory";
    }

    if (listName == "placesobjects") {
        listElement = "#lstPlacesObjects";
        placesObjectsVerbs = new Array();
        buttonPrefix = "cmdPlacesObjects";
    }

    var previousSelectionText = "";
    var previousSelectionKey = "";
    var foundPreviousSelection = false;

    var $selected = $(listElement + " .ui-selected");
    if ($selected.length > 0) {
        previousSelectionText = $selected.first().text();
        previousSelectionKey = $selected.first().data("key");
    }

    $(listElement).empty();
    var count = 0;
    $.each(listData, function (key, value) {
        var data = JSON.parse(value);
        for (var i in ignoreScenery){
          	if (data['ElementName'] == ignoreScenery[i]){
          		return;
        	}
        }
        var objectDisplayName = data["Text"];
        var verbsArray, idPrefix;

        if (listName == "inventory") {
            verbsArray = inventoryVerbs;
            idPrefix = "cmdInventory";
        } else {
            verbsArray = placesObjectsVerbs;
            idPrefix = "cmdPlacesObjects";
        }
        verbsArray.push(data);
        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
        	
         var $newItem = $("<li/>").data("key", key).data("elementid", data["ElementId"]).data("elementname", data["ElementName"]).data("index", count).html(objectDisplayName);

         if (objectDisplayName == previousSelectionText && key == previousSelectionKey) {
                $newItem.addClass("ui-selected");
                foundPreviousSelection = true;
                updateVerbButtons($newItem, verbsArray, idPrefix);
            }
   			$(listElement).append($newItem);
            count++;
        }
    });

    var selectSize = count;
    if (selectSize < 3) selectSize = 3;
    if (selectSize > 12) selectSize = 12;
    $(listElement).attr("size", selectSize);
    
    if (!foundPreviousSelection) {
        for (var i = 1; i <= verbButtonCount; i++) {
            var target = $("#" + buttonPrefix + i);
            target.hide();
        }
    }
}

JS.eval style:

JS.eval ("function updateList(listName, listData) {     var listElement = \"\";     var buttonPrefix = \"\";      if (listName == \"inventory\") {         listElement = \"#lstInventory\";         inventoryVerbs = new Array();         buttonPrefix = \"cmdInventory\";     }      if (listName == \"placesobjects\") {         listElement = \"#lstPlacesObjects\";         placesObjectsVerbs = new Array();         buttonPrefix = \"cmdPlacesObjects\";     }      var previousSelectionText = \"\";     var previousSelectionKey = \"\";     var foundPreviousSelection = false;      var $selected = $(listElement + \" .ui-selected\");     if ($selected.length > 0) {         previousSelectionText = $selected.first().text();         previousSelectionKey = $selected.first().data(\"key\");     }      $(listElement).empty();     var count = 0;     $.each(listData, function (key, value) {         var data = JSON.parse(value);         for (var i in ignoreScenery){           	if (data['ElementName'] == ignoreScenery[i]){           		return;         	}         }         var objectDisplayName = data[\"Text\"];         var verbsArray, idPrefix;          if (listName == \"inventory\") {             verbsArray = inventoryVerbs;             idPrefix = \"cmdInventory\";         } else {             verbsArray = placesObjectsVerbs;             idPrefix = \"cmdPlacesObjects\";         }         verbsArray.push(data);         if (listName == \"inventory\" || $.inArray(objectDisplayName, _compassDirs) == -1) {         	          var $newItem = $(\"<li/>\").data(\"key\", key).data(\"elementid\", data[\"ElementId\"]).data(\"elementname\", data[\"ElementName\"]).data(\"index\", count).html(objectDisplayName);           if (objectDisplayName == previousSelectionText && key == previousSelectionKey) {                 $newItem.addClass(\"ui-selected\");                 foundPreviousSelection = true;                 updateVerbButtons($newItem, verbsArray, idPrefix);             }    			$(listElement).append($newItem);             count++;         }     });      var selectSize = count;     if (selectSize < 3) selectSize = 3;     if (selectSize > 12) selectSize = 12;     $(listElement).attr(\"size\", selectSize);         if (!foundPreviousSelection) {         for (var i = 1; i <= verbButtonCount; i++) {             var target = $(\"#\" + buttonPrefix + i);             target.hide();         }     } }")

You have JS.eval ("ignoreScenery.push('"+GetDisplayAlias(o)+"');").
As far as I can tell, data('elementid') gives the name, while data('elementname') is the alias. So you should probably be using "ignoreScenery.push('"+o.name+"');".

Also, your timeout function:

function(){
  for(var i in ignoreScenery){
    $('#lstInventory').children().each(function(){
      if ($(this).data('elementid') == ignoreScenery[i] ){
        $(this).hide();
      }
    });
  };
}

could be shrunk down:

function(){
  $('#lstInventory').children().each(function(){
    if(ignoreScenery.indexOf($(this).data('elementid')) >= 0 ){
      $(this).hide();
    }
  });
}

(or, using a modified version of my script from the other thread. Edited to be more readable, and to use the ignoreScenery array rather than redefining the function every turn)

var realUpdateList = updateList;
updateList = function (listName, listData) {
  if (ignoreScenery) {
    $.each(listData, function (key, value) {
      var data = JSON.parse(value);
      if (ignoreScenery.indexOf( data["elementid"] ) >= 0) {
        delete listData[key];
      }
    });
  }
  realUpdateList (listName, listData);
};

Example Game:

http://textadventures.co.uk/games/view/59edxksdekis-korhnz5-q/contents-in-lists

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Contents in Lists">
    <gameid>edd4c8c3-a135-448a-beb2-159be0998b30</gameid>
    <version>1.4</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script"><![CDATA[
      JS.eval ("function updateList(listName, listData) {     var listElement = \"\";     var buttonPrefix = \"\";      if (listName == \"inventory\") {         listElement = \"#lstInventory\";         inventoryVerbs = new Array();         buttonPrefix = \"cmdInventory\";     }      if (listName == \"placesobjects\") {         listElement = \"#lstPlacesObjects\";         placesObjectsVerbs = new Array();         buttonPrefix = \"cmdPlacesObjects\";     }      var previousSelectionText = \"\";     var previousSelectionKey = \"\";     var foundPreviousSelection = false;      var $selected = $(listElement + \" .ui-selected\");     if ($selected.length > 0) {         previousSelectionText = $selected.first().text();         previousSelectionKey = $selected.first().data(\"key\");     }      $(listElement).empty();     var count = 0;     $.each(listData, function (key, value) {         var data = JSON.parse(value);         for (var i in ignoreScenery){           	if (data['ElementName'] == ignoreScenery[i]){           		return;         	}         }         var objectDisplayName = data[\"Text\"];         var verbsArray, idPrefix;          if (listName == \"inventory\") {             verbsArray = inventoryVerbs;             idPrefix = \"cmdInventory\";         } else {             verbsArray = placesObjectsVerbs;             idPrefix = \"cmdPlacesObjects\";         }         verbsArray.push(data);         if (listName == \"inventory\" || $.inArray(objectDisplayName, _compassDirs) == -1) {         	          var $newItem = $(\"<li/>\").data(\"key\", key).data(\"elementid\", data[\"ElementId\"]).data(\"elementname\", data[\"ElementName\"]).data(\"index\", count).html(objectDisplayName);           if (objectDisplayName == previousSelectionText && key == previousSelectionKey) {                 $newItem.addClass(\"ui-selected\");                 foundPreviousSelection = true;                 updateVerbButtons($newItem, verbsArray, idPrefix);             }    			$(listElement).append($newItem);             count++;         }     });      var selectSize = count;     if (selectSize < 3) selectSize = 3;     if (selectSize > 12) selectSize = 12;     $(listElement).attr(\"size\", selectSize);         if (!foundPreviousSelection) {         for (var i = 1; i <= verbButtonCount; i++) {             var target = $(\"#\" + buttonPrefix + i);             target.hide();         }     } }")
      firsttime {
      }
      otherwise {
        UpdateContentsInLists
        SetTimeout (2) {
          UpdateContentsInLists
        }
      }
    ]]></inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="table">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <feature_container />
      <listchildren />
      <listchildrenprefix>On it, you see</listchildrenprefix>
      <takemsg>It's too big to take.</takemsg>
      <look><![CDATA[An oak dining table.<br/>]]></look>
      <displayverbs type="stringlist">
        <value>Look at</value>
      </displayverbs>
      <object name="bowl">
        <inherit name="editor_object" />
        <inherit name="container_open" />
        <feature_container />
        <close type="boolean">false</close>
        <open type="boolean">false</open>
        <displayverbs type="stringlist">
          <value>Look at</value>
          <value>Take</value>
        </displayverbs>
        <take />
        <listchildren />
        <look><![CDATA[A plastic bowl.<br/>]]></look>
        <object name="apple">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <scenery />
          <look><![CDATA[A red apple.<br/>]]></look>
        </object>
        <object name="orange">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <look><![CDATA[A navel orange.<br/>]]></look>
          <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
            }
            destroy (this.name)
            apple.scenery = false
            if (ListContains(ScopeReachable(),bowl)) {
              msg ("By some strange (but much appreciated) coincidence, an apple has magically appeared in the bowl!")
            }
          </eat>
        </object>
      </object>
      <object name="lazy susan">
        <inherit name="editor_object" />
        <inherit name="surface" />
        <feature_container />
        <listchildren />
        <listchildrenprefix>On it, you see</listchildrenprefix>
        <look>A lazy susan.  You can spin it to move items around the table.  (Your grandparents used to have one.)</look>
        <spin>You spin it.</spin>
        <displayverbs type="stringlist">
          <value>Look at</value>
        </displayverbs>
        <takemsg>It {once:appears to be}{notfirst:is} attached to the table.</takemsg>
      </object>
      <object name="hat">
        <inherit name="editor_object" />
        <inherit name="wearable" />
        <take />
        <look><![CDATA[It's just like Indiana Jones's hat.<br/>]]></look>
        <feature_wearable />
      </object>
    </object>
  </object>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
  <verb>
    <property>spin</property>
    <pattern>spin</pattern>
    <defaultexpression>"You can't spin " + object.article + "."</defaultexpression>
  </verb>
  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("var ignoreScenery = [];")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+GetDisplayAlias(o)+"');")
      }
    }
  ]]></function>
</asl>

Ah, you beat me to it again :p

Looks good. But I notice that function includes the line:

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {

to make it skip over any elements whose names are in a certain array. That seems like a perfect place to put another if statement (or an 'and' clause) to make it skip elements that are in a different array.

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1 && $.inArray(data['ElementId'], ignoreScenery) == -1) {

maybe?


Darn it!

It breaks the pane lists in save games online when I modify updateList()...


Ah, you beat me to it again :p

Ditto!

I was going back and trying what you posted. I forgot a bit, came back to look, and it's all different now. (Ha ha ha!)

It's all good, though. I just applied the changes you just suggested. It works in the desktop. I'm about to test it out online.


NOTE:

You guys better never let mrangel and I end up in the same room! We'd get all kinds of stuff done in a timely manner if there were no posts to get crossed up!


Okay...

I tried it countless ways with a modified updateList(). Each variation worked, until I loaded a saved game online. Then, either a scenery object showed up on load, or nothing showed up in the panes at all on load. The lists displayed correctly after taking 1 turn after loading a saved game in each instance.

UPDATE

See the next post for the example game which modifies updateList().


This method does not modify updateList().

It uses a setTimeout() to remove the scenery object(s), and I can't see it happen, so I'm thinking it may be okay.

The only issue I've found with this method is that scenery objects show up if you switch to the lists in the mobile browser. BUT using the alternate method totally breaks the lists, so pick your poison. (If I were you, I'd honestly just avoid putting scenery objects in containers which can end up in the inventory!)

Example Game (3.0)

http://textadventures.co.uk/games/view/59edxksdekis-korhnz5-q/contents-in-lists

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Contents in Lists">
    <gameid>edd4c8c3-a135-448a-beb2-159be0998b30</gameid>
    <version>3.0</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script">
      if (not game.timeelapsed = 0) {
        UpdateContentsInLists
      }
    </inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="table">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <feature_container />
      <listchildren />
      <listchildrenprefix>On it, you see</listchildrenprefix>
      <takemsg>It's too big to take.</takemsg>
      <look><![CDATA[An oak dining table.<br/>]]></look>
      <displayverbs type="stringlist">
        <value>Look at</value>
      </displayverbs>
      <object name="bowl">
        <inherit name="editor_object" />
        <inherit name="container_open" />
        <feature_container />
        <close type="boolean">false</close>
        <open type="boolean">false</open>
        <displayverbs type="stringlist">
          <value>Look at</value>
          <value>Take</value>
        </displayverbs>
        <take />
        <listchildren />
        <look><![CDATA[A plastic bowl.<br/>]]></look>
        <object name="apple">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <scenery />
          <look><![CDATA[A red apple.<br/>]]></look>
        </object>
        <object name="orange">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <look><![CDATA[A navel orange.<br/>]]></look>
          <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
            }
            destroy (this.name)
            apple.scenery = false
            if (ListContains(ScopeReachable(),bowl)) {
              msg ("By some strange (but much appreciated) coincidence, an apple has magically appeared in the bowl!")
            }
          </eat>
        </object>
      </object>
      <object name="lazy susan">
        <inherit name="editor_object" />
        <inherit name="surface" />
        <feature_container />
        <listchildren />
        <listchildrenprefix>On it, you see</listchildrenprefix>
        <look>A lazy susan.  You can spin it to move items around the table.  (Your grandparents used to have one.)</look>
        <spin>You spin it.</spin>
        <displayverbs type="stringlist">
          <value>Look at</value>
        </displayverbs>
        <takemsg>It {once:appears to be}{notfirst:is} attached to the table.</takemsg>
      </object>
      <object name="hat">
        <inherit name="editor_object" />
        <inherit name="wearable" />
        <take />
        <look><![CDATA[It's just like Indiana Jones's hat.<br/>]]></look>
        <feature_wearable />
      </object>
    </object>
  </object>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
  <verb>
    <property>spin</property>
    <pattern>spin</pattern>
    <defaultexpression>"You can't spin " + object.article + "."</defaultexpression>
  </verb>
  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("var ignoreScenery = [];")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+o.name+"');")
      }
      else {
        JS.eval ("if (typeof(ignoreScenery) != 'undefined'){var index = ignoreScenery.indexOf('"+o.listalias+"');	if (index > -1) {ignoreScenery.splice(index, 1);	}}")
      }
    }
    JS.eval ("setTimeout(function(){for(var i in ignoreScenery){$('#lstInventory').children().each(function(){if ($(this).data('elementid') == ignoreScenery[i] ){$(this).hide();}});};},1);")
  ]]></function>
</asl>

Hmm... maybe after the turnscript that sends the list of scenery objects to hide, we could have JS to serialise that data into a hidden element within the output div. Then have JS to hide it again when a game is loaded. (I assume javascript files within Quest are loaded after the output div has been restored?)


I got it. I had something messed up somewhere (don't know where).

NOTE: This is not perfect. This breaks the lists in the mobile browser. The turn script method shows the scenery objects in the lists in the mobile browser, but the lists are indented properly and work correctly with the turn script stuff (see my previous post). Honestly, we shouldn't worry with scenery objects at all (see my next post).


The code:


javascript.js (Includes modified updateList()):

var ignoreScenery = [];

setTimeout(function(){
 ASLEvent("CallUpdateContentsInLists","");
},2000);

function updateList(listName, listData) {
    var listElement = "";
    var buttonPrefix = "";

    if (listName == "inventory") {
        listElement = "#lstInventory";
        inventoryVerbs = new Array();
        buttonPrefix = "cmdInventory";
    }

    if (listName == "placesobjects") {
        listElement = "#lstPlacesObjects";
        placesObjectsVerbs = new Array();
        buttonPrefix = "cmdPlacesObjects";
    }

    var previousSelectionText = "";
    var previousSelectionKey = "";
    var foundPreviousSelection = false;

    var $selected = $(listElement + " .ui-selected");
    if ($selected.length > 0) {
        previousSelectionText = $selected.first().text();
        previousSelectionKey = $selected.first().data("key");
    }

    $(listElement).empty();
    var count = 0;
    $.each(listData, function (key, value) {
        var scenery = false;
        var data = JSON.parse(value);
        for (var i in ignoreScenery){
           if (data['ElementName'] == ignoreScenery[i]){
            scenery = true;
         }
        }
        var objectDisplayName = data["Text"];
        var verbsArray, idPrefix;

        if (listName == "inventory") {
            verbsArray = inventoryVerbs;
            idPrefix = "cmdInventory";
        } else {
            verbsArray = placesObjectsVerbs;
            idPrefix = "cmdPlacesObjects";
        }
        verbsArray.push(data);
        if ((listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) && !scenery) {
         var vis = '';
         if (scenery){vis = 'style=\'display:none;\'';}
         var $newItem = $("<li "+vis+"/>").data("key", key).data("elementid", data["ElementId"]).data("elementname", data["ElementName"]).data("index", count).html(objectDisplayName);

         if (objectDisplayName == previousSelectionText && key == previousSelectionKey) {
                $newItem.addClass("ui-selected");
                foundPreviousSelection = true;
                updateVerbButtons($newItem, verbsArray, idPrefix);
            }
      $(listElement).append($newItem);
            count++;
        }
    });

    var selectSize = count;
    if (selectSize < 3) selectSize = 3;
    if (selectSize > 12) selectSize = 12;
    $(listElement).attr("size", selectSize);
    
    if (!foundPreviousSelection) {
        for (var i = 1; i <= verbButtonCount; i++) {
            var target = $("#" + buttonPrefix + i);
            target.hide();
        }
    }
}

UpdateListContentsInLists:

  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("var ignoreScenery = [];")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+o.name+"');")
      }
    }
  ]]></function>

CallUpdateContentsInLists:

  <function name="CallUpdateContentsInLists" parameters="bs">
    UpdateContentsInLists
  </function>

Turn script:

  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>

Start script:

    <start type="script">
      UpdateContentsInLists
    </start>

User Interface Initialisation script:

    <inituserinterface type="script">
      if (not game.timeelapsed = 0) {
        UpdateContentsInLists
      }
    </inituserinterface>

Example Game

http://textadventures.co.uk/games/view/1cf53rawc02l6o8d7uu_wa/contents-in-lists-no-settimeout

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Contents in Lists">
    <gameid>8e915ad1-d720-4362-9e28-8d81592852b4</gameid>
    <version>4.0</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <start type="script">
      UpdateContentsInLists
    </start>
    <inituserinterface type="script">
      if (not game.timeelapsed = 0) {
        UpdateContentsInLists
      }
    </inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="table">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <feature_container />
      <listchildren />
      <listchildrenprefix>On it, you see</listchildrenprefix>
      <takemsg>It's too big to take.</takemsg>
      <look><![CDATA[An oak dining table.<br/>]]></look>
      <displayverbs type="stringlist">
        <value>Look at</value>
      </displayverbs>
      <object name="bowl">
        <inherit name="editor_object" />
        <inherit name="container_open" />
        <feature_container />
        <close type="boolean">false</close>
        <open type="boolean">false</open>
        <displayverbs type="stringlist">
          <value>Look at</value>
          <value>Take</value>
        </displayverbs>
        <take />
        <listchildren />
        <look><![CDATA[A plastic bowl.<br/>]]></look>
        <object name="apple">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <scenery />
          <look><![CDATA[A red apple.<br/>]]></look>
        </object>
        <object name="orange">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <look><![CDATA[A navel orange.<br/>]]></look>
          <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
            }
            destroy (this.name)
            apple.scenery = false
            if (ListContains(ScopeReachable(),bowl)) {
              msg ("By some strange (but much appreciated) coincidence, an apple has magically appeared in the bowl!")
            }
          </eat>
        </object>
      </object>
      <object name="lazy susan">
        <inherit name="editor_object" />
        <inherit name="surface" />
        <feature_container />
        <listchildren />
        <listchildrenprefix>On it, you see</listchildrenprefix>
        <look>A lazy susan.  You can spin it to move items around the table.  (Your grandparents used to have one.)</look>
        <spin>You spin it.</spin>
        <displayverbs type="stringlist">
          <value>Look at</value>
        </displayverbs>
        <takemsg>It {once:appears to be}{notfirst:is} attached to the table.</takemsg>
      </object>
      <object name="hat">
        <inherit name="editor_object" />
        <inherit name="wearable" />
        <take />
        <look><![CDATA[It's just like Indiana Jones's hat.<br/>]]></look>
        <feature_wearable />
      </object>
    </object>
  </object>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
  <verb>
    <property>spin</property>
    <pattern>spin</pattern>
    <defaultexpression>"You can't spin " + object.article + "."</defaultexpression>
  </verb>
  <function name="UpdateContentsInLists"><![CDATA[
    JS.eval ("var ignoreScenery = [];")
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
      if (GetBoolean (o, "scenery")) {
        JS.eval ("ignoreScenery.push('"+o.name+"');")
      }
    }
  ]]></function>
  <function name="CallUpdateContentsInLists" parameters="bs">
    UpdateContentsInLists
  </function>
  <javascript src="javascript.js" />
</asl>

The Best Way to Handle Things (or so says me)

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.)


Here is The Best Method, with no fiddling around with scenery.


I want to hide my apple until the orange has been eaten, so I set visible to false.


Here's the UpdateContentsInLists function (much thanks to mrangel):

  <function name="UpdateContentsInLists"><![CDATA[
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
    }
  ]]></function>

The turn script:

  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>

The start script:

UpdateContentsInLists
bowl.inventoryverbs = ListExclude(bowl.inventoryverbs, Split("Open;Close;Use", ";"))

The User Interface Initialisation script:

if (not game.timeelapsed = 0) {
  UpdateContentsInLists
}

Example Game:

http://textadventures.co.uk/games/view/59edxksdekis-korhnz5-q/contents-in-lists

<!--Saved by Quest 5.7.6606.27193-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Contents in Lists">
    <gameid>edd4c8c3-a135-448a-beb2-159be0998b30</gameid>
    <version>3.1</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <start type="script">
      UpdateContentsInLists
      bowl.inventoryverbs = ListExclude(bowl.inventoryverbs, Split("Open;Close;Use", ";"))
    </start>
    <inituserinterface type="script">
      if (not game.timeelapsed = 0) {
        UpdateContentsInLists
      }
    </inituserinterface>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="table">
      <inherit name="editor_object" />
      <inherit name="surface" />
      <feature_container />
      <listchildren />
      <listchildrenprefix>On it, you see</listchildrenprefix>
      <takemsg>It's too big to take.</takemsg>
      <look><![CDATA[An oak dining table.<br/>]]></look>
      <displayverbs type="stringlist">
        <value>Look at</value>
      </displayverbs>
      <object name="bowl">
        <inherit name="editor_object" />
        <inherit name="container_open" />
        <feature_container />
        <close type="boolean">false</close>
        <open type="boolean">false</open>
        <displayverbs type="stringlist">
          <value>Look at</value>
          <value>Take</value>
        </displayverbs>
        <take />
        <listchildren />
        <look><![CDATA[A plastic bowl.<br/>]]></look>
        <object name="apple">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <visible type="boolean">false</visible>
          <look><![CDATA[A red apple.<br/>]]></look>
        </object>
        <object name="orange">
          <inherit name="editor_object" />
          <inherit name="edible" />
          <take />
          <feature_edible />
          <look><![CDATA[A navel orange.<br/>]]></look>
          <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
            }
            destroy (this.name)
            apple.visible = true
            if (ListContains(ScopeReachable(),bowl)) {
              msg ("By some strange (but much appreciated) coincidence, an apple has magically appeared in the bowl!")
            }
          </eat>
        </object>
      </object>
      <object name="lazy susan">
        <inherit name="editor_object" />
        <inherit name="surface" />
        <feature_container />
        <listchildren />
        <listchildrenprefix>On it, you see</listchildrenprefix>
        <look>A lazy susan.  You can spin it to move items around the table.  (Your grandparents used to have one.)</look>
        <spin>You spin it.</spin>
        <displayverbs type="stringlist">
          <value>Look at</value>
        </displayverbs>
        <takemsg>It {once:appears to be}{notfirst:is} attached to the table.</takemsg>
      </object>
      <object name="hat">
        <inherit name="editor_object" />
        <inherit name="wearable" />
        <take />
        <look><![CDATA[It's just like Indiana Jones's hat.<br/>]]></look>
        <feature_wearable />
      </object>
    </object>
  </object>
  <turnscript name="update_contents_in_lists_turnscript">
    <enabled />
    <script>
      UpdateContentsInLists
    </script>
  </turnscript>
  <verb>
    <property>spin</property>
    <pattern>spin</pattern>
    <defaultexpression>"You can't spin " + object.article + "."</defaultexpression>
  </verb>
  <function name="UpdateContentsInLists"><![CDATA[
    foreach (o, ListExclude(ScopeVisible(),game.pov)) {
      if (not HasAttribute(o, "listalias")) {
        o.listalias = GetDisplayAlias(o)
      }
      o.listalias = Replace(o.listalias,"&nbsp;","")
      containers = ListExclude(ListParents(o), game.pov)
      containers = ListExclude(containers, ListParents(game.pov))
      foreach (c, containers) {
        o.listalias = "&nbsp;&nbsp;" + o.listalias
      }
    }
  ]]></function>
</asl>

This doesn't change the listalias when changing the alias, just to warn everyone!


This doesn't change the listalias when changing the alias, just to warn everyone!

I was thinking about this a while back. there's a couple of bits of code that modify the listalias of an object (such as adding "(open)" or "(worn)" at the end.

I was thinking about overriding some of those systems so that they can work with a variable listalias:

  obj.updateListalias => {
    if (HasString (this, "basealias")) {
      alias = this.basealias
    }
    else {
      alias = GetDisplayAlias(this)
    }
    if (HasAttribute (this, "descriptors")) {
      // assuming `descriptors` is a stringlist containing "worn", "open", or whatever
      alias = alias + " ("+Join(this.descriptors, ", ")+")"
    }
    foreach (p, ListExclude(ListParents(this), game.pov)) {
      if (not Contains (p, game.pov)) {
        alias = "→ "+alias
      }
    }
    this.listalias = alias
    foreach (o, GetDirectChildren (this)) {
      if (HasScript (o, "updateListAlias")) {
        do (o, "updateListAlias")
      )
    }
  }

  obj.changedparent => {
    // the rest of the usual stuff
    do (this, "updateListAlias")
  }
  obj.changedalias => {
    do (this, "updateListAlias")
  }

This way, you're only updating an object's alias when it moves, rather than every turn (which is putting quite a load on the server)

Or maybe even override GetListDisplayAlias - that way, you could have a changedlistalias script that recognises an actual alias change or the addition or removal of a prefix/suffix (from other scripts or libraries), and stores these updates in separate attributes that can then be applied to the current display alias whenever the list is displayed. For example, the wearable system appends " (worn)" to an object's listalias, and changedlistalias puts "worn" into the descriptors list. Means that the listalias stays current when alias is changed, and also merges the prefixes/suffixes added by different libraries in a consistent way.


After absorbing all of the information in this thread, I ended up only modifying this one function to handle the indentation and the scenery and not visible objects, and this keeps up with any changes to alias or listalias with no worries (for Desktop users only):

  <function name="GetListDisplayAlias" parameters="obj" type="string"><![CDATA[
    if (HasString(obj, "listalias")) {
      result = ProcessText(obj.listalias)
    }
    else {
      result = GetDisplayAlias(obj)
    }
    containers = ListExclude(ListParents(obj), game.pov)
    containers = ListExclude(containers, ListParents(game.pov))
    foreach (c, containers) {
      result = "&nbsp;" + result
    }
    if (obj.scenery or not obj.visible) return (null)
    return (result)
  ]]></function>

That's pretty much what I was thinking of :)

I'd still like to make it work more neatly in edge cases, though. Like meaning that if you change the alias of an object after some library has added " (worn)" or similar to the end of the listalias, it would still work.
You could have a couple of attributes, listaliasprefix and listaliassuffix. Setup a changedlistalias script on the default object to catch changes, and modify the prefix or suffix instead. Then GetListDisplayAlias can add the prefix/suffix to whatever the object's current alias is.


@KV, just to say that I'm trying your GetListDisplayAlias function out in a large game with many items (Woo Goes Further), as part of modifying it to optionally include game panes. So far, it has improved clarity significantly and only turned up one oddity (separation of container and contents in location display), which I can work around but am currently trying to understand. It may be an issue with transparent containers. I'll come back when I know more.


...okay, here is a small game that illustrates the problem:

<!--Saved by Quest 5.8.6747.20202-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Order Issue">
    <gameid>5f7c9cf0-4c5e-4a3b-ac0b-c4fd5a0fd025</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="box">
      <inherit name="editor_object" />
      <inherit name="container_open" />
      <feature_container />
      <take />
      <isopen type="boolean">false</isopen>
      <alias>box</alias>
      <transparent />
      <object name="stuff">
        <inherit name="editor_object" />
        <attr name="feature_container" type="boolean">false</attr>
        <take />
        <alias>stuff</alias>
        <transparent type="boolean">false</transparent>
        <isopen />
        <usedefaultprefix type="boolean">false</usedefaultprefix>
      </object>
    </object>
    <object name="more stuff">
      <inherit name="editor_object" />
      <usedefaultprefix type="boolean">false</usedefaultprefix>
    </object>
  </object>
  <function name="GetListDisplayAlias" parameters="obj" type="string"><![CDATA[
    if (HasString(obj, "listalias")) {
      result = ProcessText(obj.listalias)
    }
    else {
      result = GetDisplayAlias(obj)
    }
    containers = ListExclude(ListParents(obj), game.pov)
    containers = ListExclude(containers, ListParents(game.pov))
    foreach (c, containers) {
      result = "&nbsp;&nbsp;&nbsp;" + result
    }
    if (obj.scenery or not obj.visible) {
      return (null)
    }
    return (result)
  ]]></function>
</asl>

Hello.

EDITED

ScopeVisibleNotHeldForRoom is probably just going through all direct children of the room and adding to the list in the order in which those objects appear in the game's code (unless Pixie switched to alphabetical order... it seems like I read something about Pixie switching it to alphabetical order, but I think he decided against it because we'd have no control over the lists' order anymore).

If it only does that with objects inside of transparent containers, you'll probably have to modify that function, and you'll need to check for objects inside of objects (and I can't get into Quest to test anything).

Look at the scripts this search brings up:

https://github.com/textadventures/quest/search?q=scopevisiblenotheldforroom&unscoped_q=scopevisiblenotheldforroom


Thanks KV. I'm really just flagging up that this is a small issue that would probably need to be cleaned up if your code was to become standard. I'm happy to use it as it is since (a) I only have one transparent item; and (b) I can work around the order problem simply by re-ordering the items in its original location (a shop), since the item cannot be dropped once picked up. As I said, that is a minor cost in relation to the benefit in the game.


Gotcha!

Signing back off now.

Happy gaming, everyone!


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

Support

Forums