Alphabetized 'You can see' Object List

I like challenging myself and seeing how various codes and functions work, and I'd like to get this to work, but part of me feels that if it were possible it would've been done already.
I'd like the objects that you can see to be in an alphabetical order, rather than in the order the adventure writer added them to the room. Same with the exits, but I figure the same coding might be suitable for both, so I'll stick to the objects for now.
I've got part of the script:

objectsinroom = NewObjectList()
objectsinroom = GetDirectChildren(game.pov.parent)
objectsinroom = ObjectListSort(objectsinroom, "name")

The problem arises when I then try to modify the relevant section in the 'ShowRoomDescription' function. I wont print the whole function, just the bit I'm trying to modify:

objects = FormatObjectList(game.pov.parent.objectslistprefix, GetNonTransparentParent(game.pov.parent), Template("And"), ".")
desc = AddDescriptionLine(desc, objects)
if (game.autodescription_youcansee_newline) {
  msg (desc + "<br/>"cord)
  desc = ""
}

When I try changing the first line to:

objects = FormatObjectList(game.pov.parent.objectslistprefix, GetNonTransparentParent(objectsinroom), Template("And"), ".")

Or even:

objectsinroom = GetNonTransparentParent(game.pov.parent)
objects = FormatObjectList(game.pov.parent.objectslistprefix, objectsinroom, Template("And"), ".")

I get an error message. Is there a way to get this to work. I'm probably using the wrong list function.


FormatObjectList works with the room (the parent) rather than the list of objects in it, so the above will not work. However, you should be able to change FormatObjectList. I have not tried it, but inserting this after the third line should work:

list= ObjectListSort(list, "name")

Sorry Pixie, Isn't that what I've done. I'm a bit confused. Do you mean in the 'ShowRoomDescription' function.
Like this:

 if (i = game.autodescription_youcansee) {
      objects = FormatObjectList(game.pov.parent.objectslistprefix, GetNonTransparentParent(game.pov.parent), Template("And"), ".")
      objects = ObjectListSort(objects, "name")
      desc = AddDescriptionLine(desc, objects)
      if (game.autodescription_youcansee_newline) {
        msg (desc + "<br/>"cord)
        desc = ""
      }
    }

Just tried this, still get an error message.


No. The FormatObjectList function expects 4 parameters:

  1. A prefix ("You can see " or similar) (game.pov.parent.objectslistprefix)
  2. The room whose contents you want to show (GetNonTransparentParent(game.pov.parent))
  3. The word "and" or "or" to display between the two items (Template("And"))
  4. The text to display at the end (".")

It returns a string, which looks like "You can see a fish, a book, a pie and a banana."

First you tried giving it a sorted list as its second parameter; which didn't work because it was expecting a room, not a list.

Then you tried sorting the string that comes out; which gives an error because ObjectListSort() expects a list, not a string.

You can only sort the list when it is a list. That means after FormatObjectList turns the room into a list of contents, but before FormatObjectList joins that list into a string. You need to modify the function FormatObjectList, not ShowRoomDescription.

You need to insert the line list = ObjectListSort(list, "name")
somewhere after list = RemoveSceneryObjects(GetDirectChildren(parent))
but before foreach (item, list) {.

Hope that helps.


K.V.

I added a few objects to an example game:

image


Then, I copied the FormatObjectList function and added the line Pixie suggested:

result = ""
count = 0
list = RemoveSceneryObjects(GetDirectChildren(parent))
// KV added the next line, as per Pixie, to sort in alpha order.
list = ObjectListSort(list, "name")
if (CheckDarkness()) {
  list = RemoveDarkObjects(list)
}
listLength = ListCount(list)
foreach (item, list) {
  if (LengthOf(result) = 0) {
    result = preList + " "
  }
  result = result + GetDisplayNameLink(item, "object")
  if (CanSeeThrough(item)) {
    result = result + FormatObjectList(" (" + item.contentsprefix, item, preFinal, ")")
  }
  count = count + 1
  if (count = listLength - 1) {
    result = result + " " + preFinal + " "
  }
  else if (count < listLength) {
    result = result + ", "
  }
  else {
    result = result + postList
  }
}
return (result)

Result:

image


Inquiry:

How do we change the order of the things in the panes?

(I believe it is the middle of the night in Pix's part of the world, so I'll attempt this.)


EDIT:

I was typing that up and missed mrangel's post, which basically says the same thing as mine, only it's more informative and worded better. (I definitely learned a thing or two while perusing mrangel's post.) 😅


mrangel,

Any idea how Quest works up the lists for the panes?


@KV

How do we change the order of the things in the panes?

I'm pretty sure that's in playercore.js.


K.V.

I'm pretty sure that's in playercore.js.

Thanks!

I'm on it!


(specifically the updateList function; which receives a list of objects and displays them in the side pane. The text "JS.updateList" doesn't seem to occur anywhere in the Core libraries, so I suspect the Quest-side of this is hard coded. Therefore any changes would involve making 'updateList' sort the list before displaying it)


K.V.

UpdateList (from playercore.js)

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);
        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();
        }
    }
}

K.V.
Click here for more default scripts
kv@LAPTOP-GJBI9A4P:/mnt/c/Program Files (x86)/Quest 5$ grep -r updateList *
desktopplayer.js:function updateListEval(listName, listData) {
desktopplayer.js:    updateList(listName, eval("(" + listData + ")"));
playercore.js:function updateList(listName, listData) {

From desktopplayer.js:

function updateListEval(listName, listData) {
    updateList(listName, eval("(" + listData + ")"));
}

NOTE: These are the default scripts. I'm only posting them here because it's easier for me to look at them this way, plus I don't think mrangel has access to these files without downloading them.


I'm searching for listData next. (UPDATE: This didn't help anything.)


I think updateList is adding each object to the HTML during the for, as opposed to creating a list then applying it to the HTML. (I hope I phrased that correctly.)

 $(listElement).empty();
    var count = 0;
    $.each(listData, function (key, value) {
        var data = JSON.parse(value);
        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++;
        }

image


If one were to create a new JS function that takes the content of #lstPlaceObjects, sorts it, then replaces that section of the HTML, then call that function from a turn script, I wonder if that would work.

I might know enough about JS to create such a function...


K.V.

I got it.

Thanks to mrangel, Doctor Agon, and The Pixie!

image

image


CLICK HERE TO VIEW THE JS FUNCTIONS AND THE EXAMPLE GAME'S CODE

I added a new JS function (which I found on StackExchange):

function sortUL(selector) {
var $ul = $(selector);
$ul.find('li').sort(function (a, b) {
    var upA = $(a).text().toUpperCase();
    var upB = $(b).text().toUpperCase();
    return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;
}).appendTo(selector);
};

And the following was posted by mrangel a little while after this was posted. It is much more efficient than what I originally had here.

updateList = (function() {
  var original_updateList = updateList;
  return function (listName, listData) {
    original_updateList (listName, listData);
    sortUL ((listName == "placesobjects")?"#lstPlacesObjects":((listName == "inventory")?"#lstInventory":""))
  }
})();

Combined, we have my newly created JS file:

updateList = (function() {
  var original_updateList = updateList;
  return function (listName, listData) {
    original_updateList (listName, listData);
    sortUL ((listName == "inventory")?"#lstInventory":"#lstPlacesObjects")
  };
})();

function sortUL(selector) {
var $ul = $(selector);
$ul.find('li').sort(function (a, b) {
    var upA = $(a).text().toUpperCase();
    var upB = $(b).text().toUpperCase();
    return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;
}).appendTo(selector);
};

Here's the game's code:

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="label color">
    <gameid>8a482a90-1a03-45aa-bef8-83bd0f5fd2ed</gameid>
    <version>1.0</version>
    <firstpublished>2017</firstpublished>
    <start type="script">
      JS.setCss ("#inventoryLabel", "color:red")
      JS.setCss ("#placesObjectsLabel", "color:purple")
      JS.setCss ("#compassLabel", "color:green")
    </start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="aThing">
      <inherit name="editor_object" />
      <take />
    </object>
    <object name="bThing">
      <inherit name="editor_object" />
      <take />
    </object>
    <object name="rthing">
      <inherit name="editor_object" />
      <take />
    </object>
    <object name="eThing">
      <inherit name="editor_object" />
      <take />
    </object>
  </object>
  <function name="FormatObjectList" parameters="preList, parent, preFinal, postList" type="string"><![CDATA[
    result = ""
    count = 0
    list = RemoveSceneryObjects(GetDirectChildren(parent))
    // KV added the next line to sort in alpha order.
    list = ObjectListSort(list, "name")
    if (CheckDarkness()) {
      list = RemoveDarkObjects(list)
    }
    listLength = ListCount(list)
    foreach (item, list) {
      if (LengthOf(result) = 0) {
        result = preList + " "
      }
      result = result + GetDisplayNameLink(item, "object")
      if (CanSeeThrough(item)) {
        result = result + FormatObjectList(" (" + item.contentsprefix, item, preFinal, ")")
      }
      count = count + 1
      if (count = listLength - 1) {
        result = result + " " + preFinal + " "
      }
      else if (count < listLength) {
        result = result + ", "
      }
      else {
        result = result + postList
      }
    }
    return (result)
  ]]></function>
  <javascript src="updateListKV.js" />
</asl>

Here's a link to the example game:

http://textadventures.co.uk/games/view/cfh57is1-eq-pksfoafxbq/label-color


Thanks, I didn't even look at the 'FormatObjectList' function.
As to the re-ordering of the objects in the panes, that was going to be another question.
It seems odd that the objects in the room and the objects in the panes are two separate lists, but I guess that's down to the coding of the core functions.
It's going to take me awhile to look at that.


Seems to be an awful lot of code there.

Every time an item is added to one of the lists, you add it and then sort both lists. Why not just sort the one that's had an element added to it? So your updateListKV.js would look something like:

updateList = (function() {
  var original_updateList = updateList;
  return function (listName, listData) {
    original_updateList (listName, listData);
    sortUL ((listName == "inventory")?"#lstInventory":"#lstPlacesObjects")
  };
})();

(whenever the server changes the contents of either list, it calls original_updateList() and then sorts the list that was just changed)


K.V.

I only added the two lines at the end. I just copied the default updateList and altered it to override it.

I didn't know you could do what you've got there, mrangel.

That's pretty cool.

I just deleted the kvSort function and replaced the other with your script, and it works like a charm!

Whoo-hoo!

Thanks, again!


EDIT

I altered the code in my post, replacing it with yours, mrangel. (I pointed out who wrote it, too, which is only proper.)


I think there's still a better way to do it, but my JS is very rusty.

Now my brain's just being weird at me, and I'm wondering about a sort function that looks like…
(This won't work. This is the consequence of writing code off the top of my head in the early hours of the morning having not slept)

(function() {
  var sortAppend = function (adding) {
    var ucA = adding.text().toUpperCase();
    var items = this.find("li");
    if (items.length == 0) {
      this.prepend(adding)
    } elsif (ucA > items.last().text().toUpperCase()) {
      items.last().after(adding);
    } else {
      items.each(function () {
        this.before(adding);
        return (ucA > this.text().toUpperCase());
      });
    }
  };
  $("#lstInventory").append = sortAppend;
  $("#lstPlacesObjects").append = sortAppend;
})();

(No idea if that would work, I'm pretty much typing off the top of my head)


How do I go about adding that 'updateList' function to my game.
Can it be done through the 'GUI' or does it have to be done in the code view.


K.V.

You can do it either way.

I use the GUI.

Add a JS function here.
image


Click NEW to create a new file here.
image


NOTE: The filename doesn't matter. The function names are all Quest will care about.
image


Paste in the text.
image


Done.
image


You can also use a text editor and Code View.

  • Create a new file and copy the text into it. Name it anything you like, just give it a .js extension. (whatever.js)
  • Move that file to your game's main directory.
  • Open the game in Code View, and add this line at the bottom: <javascript src="whatever.js" />(it should go just before the closing </asl>.)

NOTE: I didn't alter FormatObjectList this time.
image


OK. Thanks. I'll get on to that.
Having trouble pasting in the text. Not sure what I'm doing wrong.
Just have to do it the long way, and type it in.


Or if you're on the web version and don't have the "Advanced" thing at the left, just stick it in the UI Initialisation script using JS.eval(). Removing the linebreaks and double-quotes is simple enough:

JS.eval("sortUL = function(selector) {$(selector).find('li').sort(function (a, b) {var upA = $(a).text().toUpperCase();var upB = $(b).text().toUpperCase();return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;}).appendTo(selector);};")
JS.eval("updateList = (function() {var ori = updateList;return function (listName, listData) {ori(listName, listData);sortUL((listName=='inventory')?'#lstInventory':'#lstPlacesObjects')};})();")

Success. Thanks all.
Was having to manually type in the code, couldn't get it to paste the code onto the JavaScript page. Then I saw the link to the game and copied that across.
Even better now with the initialisation scripting.
Was getting a bit confused too as I was typing. I thought at first it said '#1stInventory' and not '#lstInventory'.


Had a message from someone else saying I'm wrong, you can't do this in the web version because you can't declare or override JS functions. So just to clarify:

These methods will not work if called from within JS.eval():

  • function someFunctionName(arguments) { ... }
  • var someFunctionName = function(arguments) { ... }

Those two are equivalent and create a function that is local to the eval block it was created in. But you can create a global function by doing:

  • someFunctionName = function(arguments) { ... }

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

Support

Forums