Quest Bug: Mouse Click on Menu Choice, then Sound Plays, but No Update (TA offline editor)

The title is self-explanatory. If you are presented with an inline menu (haven't tried popup menu) and then mouse-click on one of the choices, and then a sound effect is played, it works but nothing gets displayed or updated -- side panes, main text panel, command prompt, etc. But after the NEXT turn, everything gets properly updated (so there is a 1-turn delay). This glitch doesn't happen if you make a menu choice via keyboard, or if no sound is played afterwards.

I think this is a Quest bug. Anyone understand it or know of a workaround? Thanks.


It works for me.

I just made an example game.

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="showmenu">
    <gameid>2a4ccea3-9e1d-4b90-9dfc-36d343d3a137</gameid>
    <version>1.0</version>
    <firstpublished>2017</firstpublished>
    <turns type="int">0</turns>
    <statusattributes type="stringdictionary">
      <item>
        <key>turns</key>
        <value></value>
      </item>
    </statusattributes>
    <start type="script">
    </start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <description><![CDATA[<center>{command:ShowMenu}</center>]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="thing">
      <inherit name="editor_object" />
    </object>
  </object>
  <turnscript name="turns">
    <enabled />
    <script>
      game.turns = game.turns + 1
    </script>
  </turnscript>
  <command>
    <script>
      menuShow
    </script>
    <pattern>menu;show menu;showmenu</pattern>
  </command>
  <function name="menuShow">
    ShowMenu ("It's time for you to decide whether or not you move the thing.", Split("move the thing;don't move the thing", ";"), false) {
      switch (result) {
        case ("move the thing") {
          play sound ("moved.mp3", false, false)
          if (thing.parent = game.pov) {
            thing.parent = game.pov.parent
          }
          else {
            thing.parent = game.pov
          }
        }
        case ("don't move the thing") {
          msg ("Good choice.")
        }
      }
    }
  </function>
</asl>

http://textadventures.co.uk/games/view/kwigefjxfewauesn5gv3nq/showmenu


This just in:

If you set "Wait for sound to finish playing..." to yes, then it happens.

...or doesn't happen...

...until you take the next turn. Then the attributes/stats/panes are updated.


If you call to wait for the sound to finish playing, isn't that a 'wait' within a 'wait'?

NOTE: You can't stop the game until you've taken the next turn either.


Yeah... It freezes the game. You can't even STOP.


It doesn't print any of the text in this next example until the next turn either:

ShowMenu ("It's time for you to decide whether or not you move the thing.", Split("move the thing;don't move the thing", ";"), false) {
  switch (result) {
    case ("move the thing") {
      play sound ("moved.mp3", true, false)
      msg ("You move it.")
      on ready {
        msg ("Thanks (msg)")
        JS.addText ("THANKS (JS)")
      }
      if (thing.parent = game.pov) {
        thing.parent = game.pov.parent
      }
      else {
        thing.parent = game.pov
      }
    }
    case ("don't move the thing") {
      msg ("Good choice.")
    }
  }
}

If I create a command that does the same thing (plays the sound with the Wait) outside of ShowMenu, it works correctly.

Game code ``` ```

NOTE: It freezes the online player when you select the option that plays the sound, as soon as you click it.


Thanks for the confirmation KV!

So I guess "wait within a wait" never works.


Monster sorry, but see no bug...

Monster see block in block!

http://docs.textadventures.co.uk/quest/blocks_and_scripts.html

Monster see bug now! (Monster distracted...)


Great. Now that my game is so big, it's impractical to find every spot with a potential for conflict (menu/play sound/timer clash) and put "on ready" in there somewhere. Ho hum.


Actually, this is a bug because this works when you type in a menu choice, but not when you mouse-click it.


on ready won't fix it. ...or it didn't in my case. I was just testing with it. Nothing after the sound happened until the next turn using ShowMenu when the sound waited to finish playing.

The msg doesn't even print until the next turn:

play sound ("moved.mp3", true, false)
msg ("You move it.")

You can use show menu rather than ShowMenu.

It's a popup/alert, but it works.

View the game code
<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="showmenu">
    <gameid>2a4ccea3-9e1d-4b90-9dfc-36d343d3a137</gameid>
    <version>1.1</version>
    <firstpublished>2017</firstpublished>
    <turns type="int">0</turns>
    <statusattributes type="stringdictionary">
      <item>
        <key>turns</key>
        <value></value>
      </item>
    </statusattributes>
    <start type="script">
    </start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <description><![CDATA[<center>{command:FakeShowMenu}</center>]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="thing">
      <inherit name="editor_object" />
    </object>
  </object>
  <command>
    <pattern>menu;show menu;showmenu;FakeShowMenu</pattern>
    <script>
      menuShow
      game.notarealturn = true
    </script>
  </command>
  <command>
    <pattern>move the thing</pattern>
    <script>
      msg ("Playing mp3 audio...")
      play sound ("moved.mp3", true, false)
      msg ("You move it.")
      on ready {
        msg ("Thanks (msg)")
        JS.addText ("THANKS (JS)")
      }
      if (thing.parent = game.pov) {
        thing.parent = game.pov.parent
      }
      else {
        thing.parent = game.pov
      }
      request (Show, "Command")
    </script>
  </command>
  <command>
    <pattern>don't move the thing</pattern>
    <script>
      msg ("Good call!")
    </script>
  </command>
  <turnscript>
    <enabled />
    <script>
      game.turns = game.turns + 1
    </script>
  </turnscript>
  <function name="menuShow">
    show menu ("It's time for you to decide whether or not you move the thing.", Split("move the thing;don't move the thing", ";"), true) {
      HandleSingleCommand ("move the thing")
    }
  </function>
</asl>

I had to add the request (Show, "Command") because the text input field disappeared after making the selection for some reason when playing online.


Check it out:

http://textadventures.co.uk/games/view/kwigefjxfewauesn5gv3nq/showmenu


Thanks KV


No problem. Any chance I get to make Quest bend to someone's will, I'm at the ready. ("Error running script" my foot! We're roundin' up the posse!)


The alert seemed weird to me at first, but, if it's for the player to select whether or not audio is on during play, I think it's quite fitting.


Actually, this is a bug because this works when you type in a menu choice, but not when you mouse-click it.

Yep.

...and you can't even stop the game until you make another move.

...just like an infinite loop.


This debunks my 'wait within a wait' theory.

It only does it on the mouse click when set up the regular ShowMenu way. I got carried away and forgot what the experiment truly was. I was after results instead of ...help me out here... methodology?


Maybe it's a combination of the 'wait within wait' issue and the mouse-clicking. Somehow, the mouse-clicking adds another wrinkle, or another 'wait'.


I don't know...

This is all I could dig up....

ShowMenu

Parameters:

caption
options
allowCancel
callback

outputsection = StartNewOutputSection()
msg (caption)
count = 0
game.menuoptionskeys = NewStringList()
foreach (option, options) {
  count = count + 1
  if (TypeOf(options) = "stringdictionary") {
    optionText = StringDictionaryItem(options, option)
    optiontag = option
    style = GetCurrentLinkTextFormat()
    list add (game.menuoptionskeys, option)
  }
  else if (TypeOf(option) = "string") {
    optionText = option
    optiontag = option
    style = GetCurrentLinkTextFormat()
    list add (game.menuoptionskeys, option)
  }
  else if (TypeOf(option) = "object") {
    optionText = GetDisplayAlias(option)
    optiontag = option.name
    colour = ""
    if (HasString(option, "linkcolour") and GetUIOption("UseGameColours") = "true") {
      colour = option.linkcolour
    }
    else {
      colour = GetLinkTextColour()
    }
    style = GetCurrentTextFormat(colour)
    list add (game.menuoptionskeys, option.name)
  }
  else {
    error ("ShowMenu cannot handle a " + TypeOf(option))
  }
  msg (count + ": <a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\">" + optionText + "</a>")
}
EndOutputSection (outputsection)
game.menuoptions = options
game.menuallowcancel = allowCancel
game.menucallback = callback
game.menuoutputsection = outputsection

msg (count + ": <a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\">" + optionText + "</a>")


onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\"


ShowMenuResponse (when you click)

Parameter: option

if (game.menucallback = null) {
  error ("Unexpected menu response")
}
else {
  parameters = NewStringDictionary()
  dictionary add (parameters, "result", UnescapeQuotes(option))
  script = game.menucallback
  ClearMenu
  invoke (script, parameters)
}

HandleMenuTextResponse (when you enter a command)

Return type: Boolean
Parameter: input

handled = false
if (IsInt(input)) {
  number = ToInt(input)
  if (number > 0 and number <= ListCount(game.menuoptionskeys)) {
    handled = true
    ShowMenuResponse (StringListItem(game.menuoptionskeys, number - 1))
  }
}
return (handled)

grep -r HandleMenuTextResponse *

Core/CoreFunctions.aslx: <function name="HandleMenuTextResponse" parameters="input" type="boolean">
Core/CoreParser.aslx: if (HandleMenuTextResponse(command)) {


HandleCommand

  <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, "*")) {
        msg ("")
        msg (SafeXML (command))
      }
      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){		
          commands = Split(command, ".")
          if (ListCount(commands) = 1) {
            game.pov.commandqueue = null
            HandleSingleCommand (Trim(command))
          }
          else {
            game.pov.commandqueue = commands
            HandleNextCommandQueueItem
          }
		}
        else {
          game.pov.commandqueue = null
          HandleSingleCommand (Trim(command))	
        }		
      }
    }
    ]]>
  </function>


if (HandleMenuTextResponse(command)) {
  handled = true
}

Maybe ShowMenuResponse needs to return the Boolean, and HandleCommand needs to check ShowMenuResponse along with HandleMenuTextResponse?

I have no idea how that would work though.


Calling the Pixie -- do you know a workaround for this?


Short answer: No.

If you run this script (say in game.start):

JS.alert ("Hello")
play sound ("moved.mp3", true, false)

You get an alert, and the sound plays, as you expect. Put it in a ShowMenu, and the message displays after the sound finishes, despite being earlier in the code.

There is a bug report about a conflict when using timers and playing a sound:
https://github.com/textadventures/quest/issues/891

My feeling is that the issue is with play soundand it might be worth looking at doing that in JavaScript.


On clicking a link in a list of options, JavaScript will call ASLEvent, with the Quest function name "ShowMenuResponse", and the text of the option.

On the other hand, if you type a number, then HandleCommand will process it, noting that game.menucallback is not null, and so will pass the number to HandleMenuTextResponse. If the text is a number in range, then this will call ShowMenuResponse (otherwise, HandleCommand carries on with it; this is if HandleMenuTextResponse returns false).

Either way, ShowMenuResponsewill set up the result variable, clear the options from the menu, and call the ShowMenu script.

You can add JS.alert ("I am here") to see where Quest is, and if you add that to the start of ShowMenuResponse, the alert pops up AFTER the sound plays, even though Quest does the play sound later... If you click the link. If you type the number, it works as expected.

It looks like it is an issue with ASLEvent, and you can set up your own event like this:

msg ("Click <a onclick=\"ASLEvent('HandleClick', 'Test')\">here</a>.")

So if you click on "here", the HandleClick function is called. Set that up with a single parameter, and this code:

JS.alert ("In HandleClick")
play sound ("moved.mp3", true, false)

You get the same effect (so stop blaming ShowMenu!). The sound plays first, and the stop button is disabled.

If you set the wait to false, it seems to be okay, so perhaps an option would be to add a timer afterwards:

JS.alert ("In HandleClick")
play sound ("moved.mp3", false, false)
SetTimeout (4) {
  msg ("Done")
}

Whoops, accidental post here...


So...

How is everyone today?


confused...


I tried Pixie's advice but couldn't get it to work:

play sound ("moved.mp3", false, false)
SetTimeout (4) {
  msg ("Done")
}

The sound won't play. Making it 'play sound ("moved.mp3", true, false)' only recreates the original problem. I tried placing the 'SetTimeout' to before 'play sound' (with both 'false, false' and 'true, false') without success. I changed the # of seconds for the timeout to equal the length of the played sound, and longer than the played sound, also to no avail.

I was going to add this to GitHub's list of bugs if there is no objection.


I wonder if we could make HandleCommand check for a Boolean set by ASLEvent just like it does for HandleMenuTextResponse.

We'd need to alter something else so ASLEvent actually set the Boolean, of course...



msg ("Playing mp3 audio...")
msg ("You move it.")
SetTimeout (1) {
  play sound ("moved.mp3", true, false)
  msg ("Done")
  if (thing.parent = game.pov) {
    thing.parent = game.pov.parent
  }
  else {
    thing.parent = game.pov
  }
}

This script doesn't stop you from entering a command while the sound plays.

This doesn't help at all, but it may teach us something that does help...


...and this one doesn't print "Done" or progress the turn count, but you CAN stop the game:

msg ("Playing mp3 audio...")
msg ("You move it.")
SetTimeout (1) {
  play sound ("moved.mp3", true, false)
  SetTimeout (1) {
    msg ("Done")
    if (thing.parent = game.pov) {
      thing.parent = game.pov.parent
    }
    else {
      thing.parent = game.pov
    }
  }
}

Dcoder, I did try it before posting and it worked for me (on the desktop version; uploading to the web might be a different matter...).


Well... This doesn't really help here at all (I don't think), but...

I made HandleMenuTextResponse understand it when you enter text that matches the selection:

handled = false
if (IsInt(input)) {
  number = ToInt(input)
  if (number > 0 and number <= ListCount(game.menuoptionskeys)) {
    handled = true
    ShowMenuResponse (StringListItem(game.menuoptionskeys, number - 1))
  }
}
foreach (o, game.menuoptions) {
  if (o = input) {
    handled = true
    ShowMenuResponse (input)
  }
}
return (handled)

That doesn't help Dcoder here (yet?), but I thought I could use HandleMenuTextResponse in the ASLEvent called by selecting the menu item. That didn't work.


I found ASLEvent and UIEvent, but I can't find how play sound actually works.

...and the two I did find only confuse me further.

function ASLEvent(event, parameter) {
    UIEvent("ASLEvent", event + ";" + parameter);
}
function UIEvent(cmd, parameter) {
    questCefInterop.uiEvent(cmd, parameter);
}

Pixie's suggestion behaves almost the same as it does without the SetTimeout when I try it. The sound plays either way. If 'Wait for sound...' is set to no, everything works properly on click BUT commands can still be entered, and "Done" prints after four seconds.

> menu
Playing mp3 audio...
Done

> menu
Playing mp3 audio...

> l
You are in a room.
You can see a thingy.
menu

Done


Everything still freezes when I change 'Wait...' to yes.

The sound plays no matter what on my end, though.


There is already an issue on GitHub:

https://github.com/textadventures/quest/issues/891


This proves that it's either ASLEvent or play sound causing the problem:

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="not showmenu">
    <gameid>2a4ccea3-9e1d-4b90-9dfc-36d343d3a137</gameid>
    <version>1.1</version>
    <firstpublished>2017</firstpublished>
    <turns type="int">0</turns>
    <statusattributes type="stringdictionary">
      <item>
        <key>turns</key>
        <value></value>
      </item>
    </statusattributes>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <description type="script"><![CDATA[
      msg ("<br><a color=\"blue\" style=\"cursor:pointer\" onclick=\"ASLEvent('HandleSingleCommand', 'move the thing')\">{u:{colour:blue:Click here to move the thing}}</a>")
    ]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="thing">
      <inherit name="editor_object" />
    </object>
  </object>
  <command>
    <pattern>move the thing</pattern>
    <script>
      msg ("Playing mp3 audio...")
      play sound ("moved.mp3", true, false)
      msg ("Done")
      if (thing.parent = game.pov) {
        thing.parent = game.pov.parent
      }
      else {
        thing.parent = game.pov
      }
    </script>
  </command>
  <command>
    <pattern>don't move the thing</pattern>
    <script>
      msg ("Good call!")
    </script>
  </command>
  <turnscript>
    <enabled />
    <script>
      game.turns = game.turns + 1
    </script>
  </turnscript>
</asl>

I have Quest open in Visual Studio.

Here are the two scripts I find when I search for 'sound'.

They are .vb files. Anyone speak Visual Basic? (I have no idea if this is even where the problem is, but I'll try any suggestions in a build. I'm crazy.)

   public void PlaySound(string filename, bool sync, bool looped)
        {
            m_playerUI.PlaySound(filename, sync, looped);
            if (sync)
            {
                ChangeThreadState(ThreadState.Waiting);

                lock (m_waitForResponseLock)
                {
                    Monitor.Wait(m_waitForResponseLock);
                }
            }
        }
    public class PlaySoundScript : ScriptBase
    {
        private ScriptContext m_scriptContext;
        private WorldModel m_worldModel;
        private IFunction<string> m_filename;
        private IFunction<bool> m_synchronous;
        private IFunction<bool> m_loop;

        public PlaySoundScript(ScriptContext scriptContext, IFunction<string> function, IFunction<bool> synchronous, IFunction<bool> loop)
        {
            m_scriptContext = scriptContext;
            m_worldModel = scriptContext.WorldModel;
            m_filename = function;
            m_synchronous = synchronous;
            m_loop = loop;
        }

        protected override ScriptBase CloneScript()
        {
            return new PlaySoundScript(m_scriptContext, m_filename.Clone(), m_synchronous.Clone(), m_loop.Clone());
        }

        public override void Execute(Context c)
        {
            string filename = m_filename.Execute(c);
            m_worldModel.PlaySound(filename, m_synchronous.Execute(c), m_loop.Execute(c));
        }

        public override string Save()
        {
            return SaveScript("play sound", m_filename.Save(), m_synchronous.Save(), m_loop.Save());
        }

        public override object GetParameter(int index)
        {
            switch (index)
            {
                case 0:
                    return m_filename.Save();
                case 1:
                    return m_synchronous.Save();
                case 2:
                    return m_loop.Save();
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public override void SetParameterInternal(int index, object value)
        {
            switch (index)
            {
                case 0:
                    m_filename = new Expression<string>((string)value, m_scriptContext);
                    break;
                case 1:
                    m_synchronous = new Expression<bool>((string)value, m_scriptContext);
                    break;
                case 2:
                    m_loop = new Expression<bool>((string)value, m_scriptContext);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public override string Keyword
        {
            get
            {
                return "play sound";
            }
        }
    }


Thanks guys. I'm going to wait and see what happens in the next update before I tackle this...


Log in to post a reply.

Support

Forums