Dynamic Hyperlinks (success!)

DavyB
16 May 2018, 08:45

I have made progress beyond my wildest dreams towards giving players dynamic control of Quest features. Bringing the map in and out was very easy. Bringing the games panes in and out raised a few issues but these can be worked around. The final element is the ability to switch hyperlinks on and off dynamically. Can it be done? If so, then it is possible for a player to have a vanilla, fully classic game, with just text and the player guessing what verbs are needed...perhaps bringing in links if stuck, or the map if lost, or the panes to reduce typing.


mrangel
16 May 2018, 10:51

Switching hyperlinks on and off is easy enough, it's a property of the game object. enablehyperlinks, I think (off the top of my head).
But… if you switch it on or off during the game, it will only affect text output after that point. It should be fairly simple to remove existing hyperlinks, as there's JS functions to do that (such as removing links to objects in a room when you leave the room), so it shouldn't be too hard to use the same function to disable all hyperlinks.

The disabling of links that have gone out of scope is done by the function UpdateObjectLinks in CoreScopes.aslx. In the case where hyperlinks have been disabled, this function will do nothing. I think it would make more sense to have an else clause, where it sends an empty data bundle. This would make all links currently on the screen disappear.

I'm thinking you probably want to change the functions GetDisplayName and/or GetDisplayNameLink as well. So that when links are disabled, instead of sending object names in plain text, it still generates the {object:name} links. Then change the function in the text processor (which will be a lot easier in 5.8) so that if hyperlinks are disabled, it still sends out the <a> element, but add the class "disabled" to it.
This means that as soon as you enable hyperlinks, when UpdateObjectLinks is run at the end of the turn, the links should appear for objects that are still in scope.

(Note: I should be working now, and am currently in a holiday cottage in Derbyshire with very unstable internet access. So this is all off the top of my head, not properly tested)


DavyB
16 May 2018, 11:24

@mrangel, that's wonderful! I don't claim to follow all that technical detail but "easy enough" is just what I'd hoped to hear!! There is no rush on this, so happy to talk more when you return from your holiday...enjoy Derbyshire...


K.V.
16 May 2018, 11:25

Try adding this command and turn script:

This is OLD CODE. (Click here for the new code.)

  <command name="toggle_links_cmd">
    <pattern>links;hyperlinks;toggle links;toggle hyperlinks</pattern>
    <script>
      // I am not using game.enablehyperlinks because the links are not \
      // created while that is set to false.
      if (not GetBoolean(game, "suppresshyperlinks")) {
        game.suppresshyperlinks = true
        EnableTurnScript (nolinks)
        msg ("Hyperlinks disabled.")
      }
      else {
        game.suppresshyperlinks = false
        DisableTurnScript (nolinks)
        msg ("Hyperlinks enabled.")
      }
    </script>
  </command>
  <turnscript name="nolinks">
    <script>
      if (GetBoolean(game, "suppresshyperlinks")) {
        JS.eval ("setTimeout(function(){$('a').each(function(){$(this).addClass('disabled');});},1);/*May need to increase the time for online play, but it would probably ruin the effect!*/")
      }
    </script>
  </turnscript>

DavyB
16 May 2018, 12:25

Excellent KV, that seems perfect! Does your comment in the turnscript imply there is a high processing cost when the links are hidden?


K.V.
16 May 2018, 12:34

No, I just expected it to need different settings online. I was thinking it may need more than a millisecond, but it actually needs less.

The links flash for a split second online when toggled off.

Everything mrangel said is true. I was just trying to find a way to pull this off without having to change all those scripts. I have one more idea to test out. Be right back!


DavyB
16 May 2018, 12:50

...in terms of effect, it would be okay simply to show links in the current location after switching them on, which could be achieved by triggering 'look' to repeat the room description with links shown?


K.V.
16 May 2018, 13:01

If you use the last code I posted, you can see the links flash for a split second after entering a command when the links are toggled off. (It doesn't do that in the desktop player.)


Try this instead (no more need for a turn script, and it works the same online as it does in the desktop player):

Version 2 - Fixed to work in saved games!

  <command name="toggle_links_cmd">
    <pattern>links;hyperlinks;toggle links;toggle hyperlinks</pattern>
    <script><![CDATA[
      JS.eval ("if (typeof(linksEnabled) == 'undefined'){var linksEnabled = true; function updateCommandLinks(data) {     $(\".commandlink\").each(function (index, e) {         var $e = $(e);         if (!$(e).data(\"deactivated\")) {             var elementid = $e.data(\"elementid\");             var available = $.inArray(elementid, data) > -1 || elementid.length == 0;             if (available) {                 if (linksEnabled) {$e.removeClass(\"disabled\");}             } else {                 $e.addClass(\"disabled\");             }         }     });$(\".cmdlink\").each(function(){	if (!linksEnabled) {		$(this).addClass(\"disabled\");	}});};}")
      if (not GetBoolean(game, "suppresshyperlinks")) {
        game.suppresshyperlinks = true
        JS.eval ("linksEnabled = false;")
        JS.eval ("$('.cmdlink,.commandlink').each(function(){$(this).addClass('disabled');});")
        msg ("Hyperlinks disabled.")
      }
      else {
        game.suppresshyperlinks = false
        JS.eval ("linksEnabled = true;")
        msg ("Hyperlinks enabled.")
      }
    ]]></script>
  </command>

K.V.
16 May 2018, 13:05

it would be okay simply to show links in the current location after switching them on, which could be achieved by triggering 'look' to repeat the room description with links shown

I was thinking this might be a good way to handle it, but that last code I posted renders this unnecessary.


K.V.
16 May 2018, 13:11

Here's a test game with that last bit of code I posted:

http://textadventures.co.uk/games/view/bfgvvuakbuscgifh-q90tq/toggling-hyperlinks


K.V.
16 May 2018, 13:56

BONUS CODE:

  <command name="font_inc">
    <pattern>increase font size;increase font;inc font size;inc font</pattern>
    <script>
      JS.eval ("var currentSize = $('body').css(\"font-size\");$('#divOutput *').each(function(){$(this).css(\"font-size\", parseInt($(this).css(\"font-size\").replace(/px/,'')) + 1+\"px\");})")
      IncreaseObjectCounter (game, "defaultfontsize")
      msg ("Font-size increased.")
    </script>
  </command>
    <command name="font_dec">
    <pattern>decrease font size;decrease font;dec font size;dec font</pattern>
    <script>
      JS.eval ("var currentSize = $('body').css(\"font-size\");$('#divOutput *').each(function(){$(this).css(\"font-size\", parseInt($(this).css(\"font-size\").replace(/px/,'')) - 1+ \"px\");})")
      DecreaseObjectCounter (game, "defaultfontsize")
      msg ("Font-size decreased.")
    </script>
  </command>

DavyB
16 May 2018, 14:01

Yes, that looks even better. Happier without a turn script.


K.V.
16 May 2018, 14:19

Yes, that looks even better. Happier without a turn script.

:thumbsup:


EDIT

:thumbsup: must be a GitHub markdown thing...

How about &#128077;?

👍

Oh, yeah... There we are!


Yes, that looks even better. Happier without a turn script.

👍 KV likes this


DavyB
16 May 2018, 14:32

Thanks once again. I'll update my games to include all these options...and make use of 👍 every time the opportunity arises!

👍 👍 👍


K.V.
16 May 2018, 15:13

"Hey! 👍👍"
- A. Fonzarelli


DavyB
16 May 2018, 15:59

...one more point! By default, links are on initially. I can set them off with a silent internal toggle but is there an easier way? ...an initialise function that takes on/off as a parameter?


K.V.
16 May 2018, 16:29

By default, links are on initially. I can set them off with a silent internal toggle but is there an easier way? ...an initialise function that takes on/off as a parameter?

Umm...

Starting from scratch:

Step 1

Add this function:

  <function name="SetHyperlinkStatus" parameters="setting">
    if (setting = "on") {
      bool = "true"
    }
    else if (setting = "off") {
      bool = "false"
    }
    else {
      // Incorrect input.  Just turn the links on.
      bool = "true"
    }
    JS.eval ("var linksEnabled = "+bool+";")
    if (bool = "false") {
      game.suppresshyperlinks = true
      JS.eval ("$('.cmdlink,.commandlink').each(function(){$(this).addClass('disabled');});")
    }
    else {
      game.suppresshyperlinks = false
    }
  </function>

Step 2

Add this to game.inituserinterface:

// You can set this to "on" or "off", make sure it is a STRING!
SetHyperlinkStatus ("off")
// The next bit could be pasted into an included JS file, but works just as well in JS.eval()
JS.eval ("if (typeof(linksEnabled)=='undefined'){var linksEnabled = true;} function updateCommandLinks(data) {     $('.commandlink').each(function (index, e) {         var $e = $(e);         if (!$(e).data('deactivated')) {             var elementid = $e.data('elementid');             var available = $.inArray(elementid, data) > -1 || elementid.length == 0;             if (available) {                 if (linksEnabled) {$e.removeClass('disabled');}             } else {                 $e.addClass('disabled');             }         }     });$('.cmdlink').each(function(){	if (!linksEnabled) {		$(this).addClass('disabled');	}});};")

Step 3

Add this command:

  <command name="toggle_links_cmd">
    <pattern>links;hyperlinks;toggle links;toggle hyperlinks</pattern>
    <script>
      game.notarealturn = true
      game.suppressturnscripts = true
      if (not GetBoolean(game, "suppresshyperlinks")) {
        SetHyperlinkStatus ("off")
        msg ("Hyperlinks disabled.")
      }
      else {
        SetHyperlinkStatus ("on")
        msg ("Hyperlinks enabled.")
      }
    </script>
  </command>

Example game:

http://textadventures.co.uk/games/view/kdkpb_dnxkeqps-ib0mqcq/toggling-links-version-2

The code:

<!--Saved by Quest 5.8.6708.15638-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Toggling Links (Version 2)">
    <gameid>bbd81b4c-8136-4df3-989f-0e2802d32f88</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <feature_advancedscripts />
    <inituserinterface type="script"><![CDATA[
      SetHyperlinkStatus ("off")
      JS.eval ("if (typeof(linksEnabled)=='undefined'){var linksEnabled = true;} function updateCommandLinks(data) {     $('.commandlink').each(function (index, e) {         var $e = $(e);         if (!$(e).data('deactivated')) {             var elementid = $e.data('elementid');             var available = $.inArray(elementid, data) > -1 || elementid.length == 0;             if (available) {                 if (linksEnabled) {$e.removeClass('disabled');}             } else {                 $e.addClass('disabled');             }         }     });$('.cmdlink').each(function(){	if (!linksEnabled) {		$(this).addClass('disabled');	}});};")
    ]]></inituserinterface>
  </game>
  <command name="toggle_links_cmd">
    <pattern>links;hyperlinks;toggle links;toggle hyperlinks</pattern>
    <script>
      if (not GetBoolean(game, "suppresshyperlinks")) {
        SetHyperlinkStatus ("off")
        msg ("Hyperlinks disabled.")
      }
      else {
        SetHyperlinkStatus ("on")
        msg ("Hyperlinks enabled.")
      }
    </script>
  </command>
  <object name="room">
    <inherit name="editor_room" />
    <isroom />
    <description><![CDATA[If you require assistance, enter {command:HELP}.<br/>]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <exit alias="north" to="second room">
      <inherit name="northdirection" />
    </exit>
    <object name="stick">
      <inherit name="editor_object" />
    </object>
  </object>
  <object name="second room">
    <inherit name="editor_room" />
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
  </object>
  <function name="SetHyperlinkStatus" parameters="setting">
    if (setting = "on") {
      bool = "true"
    }
    else if (setting = "off") {
      bool = "false"
    }
    else {
      // Incorrect input.  Just turn the links on.
      bool = "true"
    }
    JS.eval ("var linksEnabled = "+bool+";")
    if (bool = "false") {
      game.suppresshyperlinks = true
      JS.eval ("$('.cmdlink,.commandlink').each(function(){$(this).addClass('disabled');});")
    }
    else {
      game.suppresshyperlinks = false
    }
  </function>
</asl>

DavyB
16 May 2018, 17:03

Thanks again, KV, we are there!
👍👍👍👍👍


K.V.
16 May 2018, 17:06

Thank you, DavyB!

You, sir, were the inspiration!


What else you got (besides dragging the map; I'm still researching that)?


DavyB
16 May 2018, 17:53

With your responses today, I need to update the games I've been working on and test them. Who knows what will come out of that.

I thought I had a couple more problems with game panes but experiments showed that these were just consequences of the way the games were set up rather than being a general issue. I WILL be back!


CheeseMyBaby
16 May 2018, 18:31

You want something to chew on K.V?

Chew on this! :)


mrangel
16 May 2018, 20:33

(Yeah, I know it's already solved, but wanted to see if my method was viable. I think it should be, if you don't mind overriding a function. And I still have no idea from looking at the XML how Quest distinguishes between a regex and a simple pattern)

<command name="toggle_links_cmd" pattern="^(toggle |turn |switch |(?<text>enable|disable|show|hide))?(hyper)?links? ?(?<text>on|off|)$">
  <script>
    game.notarealturn = true
    game.suppressturnscripts = true
    switch (LCase(text)) {
      case ("on","enable","show") {
        game.suppresshyperlinks = false
        msg ("Hyperlinks enabled.")
      }
      case ("off","disable","hide") {
        game.suppresshyperlinks = true
        msg ("Hyperlinks suppressed.")
      }
      default {
        game.suppresshyperlinks = not GetBoolean(game, "suppresshyperlinks")
        msg ("Hyperlinks toggled {either game.suppresshyperlinks:off|on}.")
      }
    }
  </script>
</command>

<function name="UpdateObjectLinks">
  if (GetBoolean(game, "suppresshyperlinks")) {
    JS.updateObjectLinks(NewStringDictionary())
    JS.updateExitLinks(NewStringList())
    JS.updateCommandLinks(NewStringList())
  }
  else 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>

DavyB
19 May 2018, 17:02

Thanks everyone, this looks closed. Its use can be seen in the latest version of Giantkiller Too, just uploaded.


Richard Headkid
19 May 2018, 19:27

@mrangel

That code works splendidly. I only needed to change the way the pattern is declared:

  <command name="toggle_links_cmd">
    <pattern type="string"><![CDATA[^(toggle |turn |switch |(?<text>enable|disable|show|hide))?(hyper)?links? ?(?<text>on|off|)$]]></pattern>
    <script>
      game.notarealturn = true
      game.suppressturnscripts = true
      switch (LCase(text)) {
        case ("on","enable","show") {
          game.suppresshyperlinks = false
          msg ("Hyperlinks enabled.")
        }
        case ("off","disable","hide") {
          game.suppresshyperlinks = true
          msg ("Hyperlinks suppressed.")
        }
        default {
          game.suppresshyperlinks = not GetBoolean(game, "suppresshyperlinks")
          msg ("Hyperlinks toggled {either game.suppresshyperlinks:off|on}.")
        }
      }
    </script>
  </command>

mrangel
19 May 2018, 19:40

My regex is a little wonky. I think ^(?<text>toggle|turn|switch|enable|disable|show|hide|) ?(hyper)?links? ?(?<text>on|off)?$ might be better. As far as I can tell from the docs, when you specify multiple named subpatterns with the same name, it will take the last one that matches. So (?<text>show|hide|toggle|) will set text to "show", "hide", "toggle", or an empty string. Whereas (?<text>on|off)? will set it to "on" or "off", or leave the value from the first version. So in "show hyperlinks off", text is off; but in "hide links" text is "hide".

It's still a little ugly, because "hide hyperlinks on" will turn the links on (the first word being ignored if you specify "on" or "off"). But getting it to behave sensibly however you phrase the command would have doubled the length of the code.