Dynamic Hyperlinks (success!)

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.


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)


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


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>

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


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!


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


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>

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.


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

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


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>

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


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


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

πŸ‘ πŸ‘ πŸ‘


"Hey! πŸ‘πŸ‘"
- A. Fonzarelli


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


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>

Thanks again, KV, we are there!
πŸ‘πŸ‘πŸ‘πŸ‘πŸ‘


Thank you, DavyB!

You, sir, were the inspiration!


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


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!


You want something to chew on K.V?

Chew on this! :)


(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>

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


@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>

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.


Log in to post a reply.

Support

Forums