Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small feature request (UpdateObjectLinks) #1256

Closed
theolen opened this issue Nov 30, 2024 · 16 comments
Closed

Small feature request (UpdateObjectLinks) #1256

theolen opened this issue Nov 30, 2024 · 16 comments

Comments

@theolen
Copy link

theolen commented Nov 30, 2024

Hello I’m working on a game and I added portraits into the custom sidebar pane. I want them to be clickable so I copied the code for verbs menu into it. It shows the verb menu but doesn’t work if you click it (I can’t see that) and it’s the only thing I haven’t managed to implement in my own.
I think UpdateObjectLinks only scans the main body and not the custom pane. The html is identical yet acts differently, the text link works and the portrait link doesn’t. It’s the exact same html?

@KVonGit
Copy link
Collaborator

KVonGit commented Dec 1, 2024

Hello,

If you share your code, I can probably help out.

You can zip the game file and drop it into a post as an attachment, or:

```
Surround your code with three backticks like this.
```

@textadventures textadventures locked and limited conversation to collaborators Dec 1, 2024
@KVonGit KVonGit converted this issue into a discussion Dec 1, 2024
@textadventures textadventures unlocked this conversation Dec 2, 2024
@KVonGit KVonGit reopened this Dec 2, 2024
@theolen
Copy link
Author

theolen commented Dec 2, 2024

Have made some progress.

#take lamp
Error running script: The object with the name '<img alt="lamp" width="100" src="https://th' cannot be found.

setCustomStatus ("<a data-elementid=\"lamp\" class=\"cmdlink elementmenu\" data-verbs=\"" + Join(GetDisplayVerbs(lamp), "/") + "\"><img width=\"100px\" src=\"https://th.bing.com/th/id/OIP.RjuTZl8G4f9_ud7_kzJJRwAAAA?rs=1&pid=ImgDetMain\"></a><br><a data-elementid=\"lamp\" class=\"cmdlink elementmenu\" data-verbs=\"" + Join(GetDisplayVerbs(lamp), "/") + "\">lamp</a>")

The image link doesn't work but the text link does.

@Pertex
Copy link
Collaborator

Pertex commented Dec 2, 2024

I just copied your code into my game and it works without problems. Could you post a game that shows your problem?

@theolen
Copy link
Author

theolen commented Dec 2, 2024 via email

@gwgrue
Copy link

gwgrue commented Dec 3, 2024

game here yest"day,
cant find post? saw more code,
copied game tho -helps?

Hello,
Please run this ASLX demo.

<!--Saved by Quest 5.8.6836.13983-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="theolen-github">
    <gameid>f05507d0-09ea-4c86-be93-f4395c4b25e7</gameid>
    <version>1.0</version>
    <firstpublished>2024</firstpublished>
    <customstatuspane />
    <start type="script"><![CDATA[
      JS.setCustomStatus ("<a data-elementid=\"lamp\" class=\"cmdlink elementmenu\" data-verbs=\"" + Join(GetDisplayVerbs(lamp), "/") + "\"><img width=100 src=\"https://th.bing.com/th/id/OIP.RjuTZl8G4f9_ud7_kzJJRwAAAA?rs=1&pid=ImgDetMain\"><br/>lamp</a>")
    ]]></start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="lamp">
      <inherit name="editor_object" />
      <inherit name="switchable" />
      <feature_switchable />
    </object>
  </object>
</asl>

@theolen
Copy link
Author

theolen commented Dec 3, 2024

game here yest"day, cant find post? saw more code, copied game tho -helps?

Hello, Please run this ASLX demo.

<!--Saved by Quest 5.8.6836.13983-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="theolen-github">
    <gameid>f05507d0-09ea-4c86-be93-f4395c4b25e7</gameid>
    <version>1.0</version>
    <firstpublished>2024</firstpublished>
    <customstatuspane />
    <start type="script"><![CDATA[
      JS.setCustomStatus ("<a data-elementid=\"lamp\" class=\"cmdlink elementmenu\" data-verbs=\"" + Join(GetDisplayVerbs(lamp), "/") + "\"><img width=100 src=\"https://th.bing.com/th/id/OIP.RjuTZl8G4f9_ud7_kzJJRwAAAA?rs=1&pid=ImgDetMain\"><br/>lamp</a>")
    ]]></start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="lamp">
      <inherit name="editor_object" />
      <inherit name="switchable" />
      <feature_switchable />
    </object>
  </object>
</asl>

Howdy friend
Yes some of the posts were lost after moving from discussion tab to issues tab.
My friend, the code works in a new game. But in my game it doesn’t work.
It tries to use the contents of the tags as the object name instead of using the data-element attribute.

@rheadkid
Copy link

rheadkid commented Dec 3, 2024

Hi, all. What is the issue with Quest's source code? (Not to say there isn't one.)


There's a Quest function named UpdateObjectLinks, and there's also a JS function named updateObjectLinks.


I think UpdateObjectLinks only scans the main body and not the custom pane.

Scanning the HTML document is handled by JS. Let's look at the JS function first.

Line 712 is checking every single element with the class elementmenu in the entire document, not just the main body.

updateObjectLinks

function updateObjectLinks(data) {
$(".elementmenu").each(function (index, e) {
var $e = $(e);
var verbs = data[$e.data("elementid")];
if (verbs) {
$e.removeClass("disabled");
$e.data("verbs", verbs);
// also set attribute so verbs are persisted to savegame
$e.attr("data-verbs", verbs);
} else {
$e.addClass("disabled");
}
});
}


Moving on to the Quest function.

It shows the verb menu but doesn’t work if you click it (I can’t see that) and it’s the only thing I haven’t managed to implement in my own.

UpdateObjectLinks

<function name="UpdateObjectLinks">
if (game.enablehyperlinks) {
data = NewStringDictionary()
foreach (object, ScopeVisible()) {
dictionary add (data, object.name, Join(GetDisplayVerbs(object), "/"))
}
JS.updateObjectLinks(data)
exits = NewStringList()
foreach (exit, ScopeExits()) {
list add (exits, exit.name)
}
JS.updateExitLinks(exits)
commands = NewStringList()
foreach (cmd, ScopeCommands()) {
list add (commands, cmd.name)
}
JS.updateCommandLinks(commands)
}
</function>

Line 299 is building its list/dictionary using things which are visible and in scope (using ScopeVisible()). It then calls the JS updateObjectLinks function using that dictionary as the argument.

If something is not visible (or in scope) in Quest, the game will print: I can’t see that.

My friend, the code works in a new game. But in my game it doesn’t work.

Sounds like your object in your game is not in scope (or it's not visible), but the object in the sample game is visible and in scope.

@theolen
Copy link
Author

theolen commented Dec 3, 2024

Hi, all. What is the issue with Quest's source code? (Not to say there isn't one.)

There's a Quest function named UpdateObjectLinks, and there's also a JS function named updateObjectLinks.

I think UpdateObjectLinks only scans the main body and not the custom pane.

Scanning the HTML document is handled by JS. Let's look at the JS function first.

Line 712 is checking every single element with the class elementmenu in the entire document, not just the main body.

updateObjectLinks

function updateObjectLinks(data) {
$(".elementmenu").each(function (index, e) {
var $e = $(e);
var verbs = data[$e.data("elementid")];
if (verbs) {
$e.removeClass("disabled");
$e.data("verbs", verbs);
// also set attribute so verbs are persisted to savegame
$e.attr("data-verbs", verbs);
} else {
$e.addClass("disabled");
}
});
}

Moving on to the Quest function.

It shows the verb menu but doesn’t work if you click it (I can’t see that) and it’s the only thing I haven’t managed to implement in my own.

UpdateObjectLinks

<function name="UpdateObjectLinks">
if (game.enablehyperlinks) {
data = NewStringDictionary()
foreach (object, ScopeVisible()) {
dictionary add (data, object.name, Join(GetDisplayVerbs(object), "/"))
}
JS.updateObjectLinks(data)
exits = NewStringList()
foreach (exit, ScopeExits()) {
list add (exits, exit.name)
}
JS.updateExitLinks(exits)
commands = NewStringList()
foreach (cmd, ScopeCommands()) {
list add (commands, cmd.name)
}
JS.updateCommandLinks(commands)
}
</function>

Line 299 is building its list/dictionary using things which are visible and in scope (using ScopeVisible()). It then calls the JS updateObjectLinks function using that dictionary as the argument.

If something is not visible (or in scope) in Quest, the game will print: I can’t see that.

My friend, the code works in a new game. But in my game it doesn’t work.

Sounds like your object in your game is not in scope (or it's not visible), but the object in the sample game is visible and in scope.

It is in scope and visible I have just checked.

It appears to be a bug in jjmenu -this section below:

if (typeof param === "undefined") {
            var verbs = el.data("verbs");
            **var text = el.html();**
            var elementId = el.data("elementid");
            param = buildMenuOptions(verbs, text, elementId);
        }

Note the var text which grabs the entire html of the section.

function buildMenuOptions(verbs, text, elementId) {
    var verbsList = verbs.split("/");
    var options = [];
    var metadata = new Object();
    metadata[text] = elementId;
    var metadataString = JSON.stringify(metadata);

    $.each(verbsList, function (key, value) {
        options = options.concat({
            title: value,
            action: {
                callback: function (selectedValue) {
                    **sendCommand(selectedValue.toLowerCase() + " " + text, metadataString);**
                }
            }
        });
    });

    return options;
}

Now here it seems to send the HTML as the command instead of grabbing the alias using the elementID.

@rheadkid
Copy link

rheadkid commented Dec 3, 2024

Now here it seems to send the HTML as the command instead of grabbing the alias using the elementID.

💯

Image:

XHRPOST
	
ctlScriptManager	"UpdatePanel1|cmdSubmit"
fldUIMsg	'command {"command":"look at <img width=\\"100px\\" src=\\""https://th.bing.com/th/id/OIP.RjuTZl8G4f9_ud7_kzJJRwAAAA?rs=1&pid=ImgDetMain\\">","metadata":"{\\"<img width=\\\\\\"100px\\\\\\" src=\\\\\\""https://th.bing.com/th/id/OIP.RjuTZl8G4f9_ud7_kzJJRwAAAA?rs=1&pid=ImgDetMain\\\\\\">\\":\\"lamp\\"}"}'

Text:

XHRPOST
	
ctlScriptManager	"UpdatePanel1|cmdSubmit"
fldUIMsg	'command {"command":"look at lamp","metadata":"{\\"lamp\\":\\"lamp\\"}"}'

This seems to work, but likely breaks something else:

function buildMenuOptions(verbs, text, elementId) {
    if(text.indexOf("<")>-1) text = elementId;
    var verbsList = verbs.split("/");
    var options = [];
    var metadata = new Object();
    metadata[text] = elementId;
    var metadataString = JSON.stringify(metadata);

    $.each(verbsList, function (key, value) {
        options = options.concat({
            title: value,
            action: {
                callback: function (selectedValue) {
                    sendCommand(selectedValue.toLowerCase() + " " + text, metadataString);
                }
            }
        });
    });

    return options;
}

@angelwedge
Copy link

Now here it seems to send the HTML as the command instead of grabbing the alias using the elementID.

It isn't supposed to grab the alias from anywhere.

It sends the command using the contents of the element as the alias, but attaches a data structure which allows the parser to link that alias back to the right object in case it isn't actually one of the object's aliases.

This is only a problem if game.multiplecommands is turned on. This allows the player to enter multiple commands on one line by ending each command with .. However, in this case, it splits on the dots in the image URI before matching that URI against the data dictionary.

Easiest solution would be to turn off multi-command mode. If you want that to be on, you could implement a workaround by overriding the core function HandleCommand.
You would need to find these lines:

        if (game.multiplecommands){
          commands = Split(command, ".")

and change to something like (off the top of my head)…

if (game.multiplecommands) {
  foreach (alias, metadata) {
    command = Replace (command, alias, Replace (alias, ".", "@@@DOT@@@"))
  }
  commands = NewStringList ()
  foreach (comm, Split (command, ".")) {
    list add (commands, Replace (comm, "@@@DOT@@@", "."))
  }

This is the simplest solution I can find to make this work without breaking any of the underlying metadata logic; the only side effect is that if the player types @@@DOT@@@ as part of a command it will turn into a . before being parsed.

@angelwedge
Copy link

This seems to work, but likely breaks something else:

Looks good. However, you are using if(text.indexOf("<")>-1). This is a bit odd, as it's the presence of a dot in the string, rather than a <, which causes the problem.

For this approach, I think that (in case echo commands is turned on), it would be better to add an extra data element, data-elementalias or similar, when you are generating those links; then modify jjmenu_popup, changing:

            var text = el.html();

to

            var text = el.data("elementalias") ? el.data("elementalias") : el.html();

@rheadkid
Copy link

rheadkid commented Dec 4, 2024

This is only a problem if game.multiplecommands is turned on.

💯

image

@rheadkid
Copy link

rheadkid commented Dec 4, 2024

https://textadventures.co.uk/games/view/w8zndngjduykmw66zghqhq/image-in-pane

Image in Pane.aslx.zip

Image in Pane.aslx

<!--Saved by Quest 5.8.6836.13983-->
<asl version="580">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Image in Pane">
    <gameid>5e690319-ecd0-4177-8b40-341106365053</gameid>
    <version>1.0.0</version>
    <firstpublished>2024</firstpublished>
    <menufont>Georgia, serif</menufont>
    <feature_advancedscripts />
    <customstatuspane />
    <subtitle>v{game.version}</subtitle>
    <multiplecommands />
    <description><![CDATA[Testing<br/><br/>See https://github.com/textadventures/quest/issues/1256]]></description>
    <author>Richard Headkid</author>
    <inituserinterface type="script"><![CDATA[
      JS.setCustomStatus (Chr(60) + "a id=\"text-adventures-custom-link\" data-index=\"0\" data-elementid=\"TextAdventures\" class=\"cmdlink elementmenu\" data-verb=\"['Use','Drop']\" data-verbs=\"Examine/Take\">" + Chr(60) + "img width=\"100px\" src=\"https://avatars.githubusercontent.com/u/5058951?s=48&v=4\">" + Chr(60) + "/a>")
      JS.eval ("function paneButtonClick(target, button) { var selectedListItem = $(target + ' .ui-selected');  var selectedObject = selectedListItem.data('elementname') || selectedListItem.text() || selectedListItem.text();  var selectedElementId = selectedListItem.data('elementid');  var selectedElementName = selectedListItem.data('object') || selectedListItem.text() || selectedListItem.data('elementname'); var verb = button.data('verb');  var metadata = new Object();  metadata[selectedElementName] = selectedElementId;  var metadataString = JSON.stringify(metadata); if (selectedObject.length > 0) { var cmd = verb.toLowerCase() + ' ' + selectedElementName; sendCommand(cmd, metadataString);}}")
      JS.eval ("function buildMenuOptions(verbs, text, elementId) { if(text.indexOf(\"<\")>-1){ text = elementId;} var verbsList = verbs.split(\"/\"); var options = []; var metadata = new Object(); metadata[text] = elementId; var metadataString = JSON.stringify(metadata);    $.each(verbsList, function (key, value) { options = options.concat({ title: value, action: { callback: function (selectedValue) { sendCommand(selectedValue.toLowerCase() + \" \" + text, metadataString); } } });}); return options;}")
    ]]></inituserinterface>
  </game>
  <object name="Testing Grounds">
    <inherit name="editor_room" />
    <isroom />
    <usedefaultprefix type="boolean">false</usedefaultprefix>
    <prefix>the</prefix>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="TextAdventures">
      <inherit name="editor_object" />
      <look><![CDATA[<br/><a target="_blank" href="https://textadventures.co.uk">https://textadventures.co.uk</a>]]></look>
      <usedefaultprefix type="boolean">false</usedefaultprefix>
      <takemsg>Alex wouldn't like that.</takemsg>
      <alias><![CDATA[<img src="https://avatars.githubusercontent.com/u/5058951?s=48&v=4"/>]]></alias>
      <listalias><![CDATA[<img width=100 src="https://avatars.githubusercontent.com/u/5058951?s=48&v=4" data-object="TextAdventures" data-elementid="TextAdventures" data-elementname="TextAdventures" data-index="0" data-verbs="['Look at','Take']"/><span style="display:none">TextAdventures</span>]]></listalias>
    </object>
  </object>
  <function name="HandleCommand" parameters="command, metadata"><![CDATA[
    handled = false
    if (game.menucallback <> null) {
      if (HandleMenuTextResponse(command)) {
        handled = true
      }
      else {
        if (game.menuallowcancel) {
          ClearMenu
        }
        else {
          handled = true
        }
      }
    }
    if (not handled) {
      StartTurnOutputSection
      if (StartsWith (command, "*")) {
        // Modified by KV to bypass turn scripts and turn counts, and to print "Noted."
        game.suppressturnscripts = true
        msg ("")
        msg (SafeXML (command))
        msg ("Noted.")
        // Added for Quest 5.8    - KV
        FinishTurn
      }
      else {
        shownlink = false
        if (game.echocommand) {
          if (metadata <> null and game.enablehyperlinks and game.echohyperlinks) {
            foreach (key, metadata) {
              if (EndsWith(command, key)) {
                objectname = StringDictionaryItem(metadata, key)
                object = GetObject(objectname)
                if (object <> null) {
                  msg ("")
                  msg ("&gt; " + Left(command, LengthOf(command) - LengthOf(key)) + "{object:" + object.name + "}")
                  shownlink = true
                }
              }
            }
          }
          if (not shownlink) {
            msg ("")
            OutputTextRaw ("&gt; " + SafeXML(command))
          }
        }
        if (game.command_newline) {
          msg ("")
        }
        game.pov.commandmetadata = metadata
        if (game.multiplecommands) {
          foreach (alias, metadata) {
            command = Replace (command, alias, Replace (alias, ".", "@@@DOT@@@"))
          }
          commands = Split(command, ".")
          if (ListCount(commands) = 1) {
            game.pov.commandqueue = null
            HandleSingleCommand (Trim(Replace(command, "@@@DOT@@@", ".")))
          }
          else {
            game.pov.commandqueue = commands
            HandleNextCommandQueueItem
          }
        }
        else {
          game.pov.commandqueue = null
          HandleSingleCommand (Trim(command))
        }
      }
    }
  ]]></function>
  <function name="GetDisplayAlias" parameters="obj" type="string">
    if (HasString(obj, "alias")) {
      result = ProcessText(obj.alias)
    }
    else {
      result = obj.name
    }
    return (result)
  </function>
  <function name="ProcessTextCommand_Object" parameters="section, data" type="string"><![CDATA[
    objectname = Mid(section, 8)
    text = ""
    colon = Instr(objectname, ":")
    if (colon > 0) {
      text = Mid(objectname, colon + 1)
      objectname = Left(objectname, colon - 1)
    }
    object = ObjectForTextProcessor(objectname)
    if (object = null) {
      return ("@@@open@@@" + ProcessTextSection(section, data) + "@@@close@@@")
    }
    else {
      if (LengthOf(text) = 0) {
        text = SafeXML(GetDisplayAlias(object))
      }
      if (game.enablehyperlinks) {
        linkid = ProcessTextCommand_GetNextLinkId()
        colour = ""
        if (HasString(object, "linkcolour") and GetUIOption("UseGameColours") = "true") {
          colour = object.linkcolour
        }
        else {
          colour = GetLinkTextColour()
        }
        style = GetCurrentTextFormat(colour)
        text = Replace(Replace(text, "&lt;", "<"), "&gt;", ">")
        return ("<a id=\"" + linkid + "\" style=\"" + style + "\" class=\"cmdlink elementmenu\" data-elementid=\"" + object.name + "\">" + text + "</a>")
      }
      else {
        return (text)
      }
    }
  ]]></function>
</asl>

@rheadkid
Copy link

rheadkid commented Dec 4, 2024

@angelwedge

Does this part look OK? (It seems to work, but I changed your code just a little.)

        if (game.multiplecommands) {
          foreach (alias, metadata) {
            command = Replace (command, alias, Replace (alias, ".", "@@@DOT@@@"))
          }
          commands = Split(command, ".")
          if (ListCount(commands) = 1) {
            game.pov.commandqueue = null
            HandleSingleCommand (Trim(Replace(command, "@@@DOT@@@", ".")))
          }
          else {
            game.pov.commandqueue = commands
            HandleNextCommandQueueItem
          }
        }

@angelwedge
Copy link

Does this part look OK? (It seems to work, but I changed your code just a little.)

I think I made it a little more complex than it needs to be because I thought the alias from the object like would be used when echoing commands; but it looks like that always uses the default alias if there's a value in metadata; which seems weird to me.

By default, I suspect it will work as intended because the alias passed back from a verb button is never displayed, and the default UI doesn't allow players to send multiple commands using an object link or the inventory panes. So it should work fine.

On the other hand… I can see that this structure (the way command metadata is handled differently in 2 places) might be placing unexpected gotchas in the way of a hypothetical future user who wants to tweak the UI (for example, if you wanted to tweak the verbs menu to work with grammars other than "[verb] [object]"). That's probably something to deal with when it becomes relevant; but it's bouncing around in my head now.

@theolen
Copy link
Author

theolen commented Dec 5, 2024

Please continue discussion on #1270

@theolen theolen closed this as completed Dec 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants