[SOLVED] Max Wait...() length - Or blocking with on_key_press()

Started by Snarky, Tue 21/11/2017 22:48:31

Previous topic - Next topic

Snarky

I want to block indefinitely until a mouse or key is pressed. AGS only offers timed blocks in WaitKey() and WaitMouseKey(), but if the timing is long enough that's probably fine. So I want to put in as big a number as I can when calling these functions.

The int maxvalue is 2,147,483,647 (according to the manual). But trying to Wait() or WaitKey() for that length of time (or 1 less, in case it has to be able to go above it) doesn't work: it just gets ignored. What's the biggest number you can provide as an argument to the Wait...() functions?

(Though since there's no WaitMouse(), I'll probably have to build my own solution anyway, with Wait(1); in a loop, and flag that gets set on the keyboard/mouse events.)

Wyz

Well, it's either a 32 bit or 16 bit signed integer; I had a quick peek at the source code of AGS, and it may well be a 16 bit value. In that case it would only go up to 32,767, which would last you about 14 minutes. That should probably be long enough... hmmm. unless someone is out to make some tea or something. :-D
Btw, a good way to find out if that really is the maximum value, is to compare 32,767 to 32,769. The latter should finish instantaneously in case of a 16 bit value.

Well, you are probably still better of using your loop solution; I guess that is a bit more reliable all in all.

Hope that helps you in any case. :)
Life is like an adventure without the pixel hunts.

Snarky

Thanks. 14 minutes isn't enough â€" I often leave my game running for breaks longer than that â€" so I guess I'm building my own.

Crimson Wizard

You could also try using "PauseGame" and checking "IsGamePaused" in where you do want particular script to hold.

Khris

Using a while loop, Wait(1) and IsButtonDown / IsKeyPressed should work fine, as far as I can see.
The key param is an int, so a for loop should work for catching all key presses.

Snarky

Are  IsButtonDown() / IsKeyPressed() guaranteed to catch every key/button press? I'm worried that depending on the timing of when the game loop runs, it might miss a click that lasts less than 1/40 sec. (Or alternatively, if the click is longer than 1/40 sec, it might end up skipping two or more consecutive Waits.) It seems safer to go with on_key_press() and on_mouse_click().

Khris

What I did was store the button / key and wait for it to be released, then exit the function.
It's true that this could miss a click lasting less than 1/40 sec but that's a tiny edge case which in practice shouldn't be an issue.

Using on_ is safer I guess, but also a bit more complicated? Not sure.

LimpingFish

Layman question: Is there some technical reason/limitation as to why a wait/waitkey/waitmousekey command has to have a timer attached?

Steam: LimpingFish
PSN: LFishRoller
XB: TheActualLimpingFish
Spotify: LimpingFish

Crimson Wizard

Quote from: LimpingFish on Wed 22/11/2017 23:55:25
Layman question: Is there some technical reason/limitation as to why a wait/waitkey/waitmousekey command has to have a timer attached?

It is just made so.
Cannot think of a reason why it had to be that other than intentional design.

Snarky

Here's what I imagine happened:

On the first day, CJ created Wait(), and he saw that it needed a timeout because otherwise it would never stop blocking.
On the second day, CJ created WaitKey() and WaitMouseKey(), and he figured that they should have the same parameters as Wait().

Snarky

Quote from: Snarky on Tue 21/11/2017 22:48:31
(Though since there's no WaitMouse(), I'll probably have to build my own solution anyway, with Wait(1); in a loop, and flag that gets set on the keyboard/mouse events.)
Quote from: Snarky on Wed 22/11/2017 10:18:58
It seems safer to go with on_key_press() and on_mouse_click().

I just tried to implement this, but it doesn't seem like on_key_press() or on_mouse_click() get called during a Wait()?

Snarky

So yeah, it turns out that on_key_press() and on_mouse_click() aren't called during a Wait() (which I guess sort of makes sense given that you check for whether the game is blocked with IsInterfaceEnabled()). I therefore had to implement it with Mouse.IsButtonDown() and IsKeyPressed(). I referred to monkey0506's KeyPressAlways module for some of the keycode logic. It seems to work pretty well after I make sure to check whether a button/key was down last loop as well as right now.

Can't be bothered to do all the cleanup and package it up as a module, but here's the raw code:

Code: ags
//Header

/// Like eKeyNone, a value used to accept any mouse button
#define eMouseNone 0

/// The way in which a block came to an end
enum BlockEndType
{
  eBlockTimeOut, 
  eBlockKeyInterrupt, 
  eBlockMouseInterrupt, 
  eBlockUnknownInterrupt
};

/// Blocks for loops number of game loops. Just like Wait(), but you can have loops > 32767
import BlockEndType Block(int loops);
/// Blocks for loops number of game loops, or until a key is pressed. If loops<0, will wait indefinitely. If keyCode is set, will ignore other keys
import BlockEndType BlockKey(int loops=-1, eKeyCode keyCode=eKeyNone);
/// Blocks for loops number of game loops, or until a mouse button is pressed. If loops<0, will wait indefinitely. If button is set (must be eMouseLeft, eMouseRight or eMouseMiddle), will ignore other mouse buttons 
import BlockEndType BlockMouse(int loops=-1, MouseButton button=eMouseNone);
/// Blocks for loops number of game loops, or until a key or mouse button is pressed. If loops<0, will wait indefinitely. If keyCode is set, will ignore other keys. If button is set (must be eMouseLeft, eMouseRight or eMouseMiddle), will ignore other mouse buttons
import BlockEndType BlockMouseKey(int loops=-1, eKeyCode keyCode=eKeyNone, MouseButton button=eMouseNone);
/// Blocks for loops number of game loops, or until interrupted, using the current Speech.SkipStyle and .SkipKey settings. If the current SkipStyle doesn't use a time-out, it will only time out if forceTimeOut is set to true  
import BlockEndType BlockSpeech(int loops, bool forceTimeOut=false);


Code: ags
// Script

#define MAX_WAIT 32767

#define CTRL_OFFSET 64
#define ALT_OFFSET (-236)
#define CTRL_RANGE_START 1
#define CTRL_RANGE_END 26
#define ALT_RANGE_START 301
#define ALT_RANGE_END 326
#define MAX_KEYCODE 435

#define eKeyLeftShift 403
#define eKeyRightShift 404
#define eKeyLeftCtrl 405
#define eKeyRightCtrl 406
#define eKeyLeftAlt 407
#define eKeyRightAlt 420

int _waitLoops;
eKeyCode _interruptKey;
MouseButton _interruptButton;

bool _wasButtonDown;
bool _wasKeyDown;

int _maxInt(int a, int b)
{
  if(a>b) return a;
  return b;
}

int _minInt(int a, int b)
{
  if(a<b) return a;
  return b;
}

bool IsButtonDownAny(static Mouse)
{
  return Mouse.IsButtonDown(eMouseLeft) || Mouse.IsButtonDown(eMouseRight) || Mouse.IsButtonDown(eMouseMiddle);
}

bool IsKeyPressedCombi(eKeyCode keyCode)
{
  if(keyCode >= CTRL_RANGE_START && keyCode <= CTRL_RANGE_END)
  {
    return (IsKeyPressed(eKeyLeftCtrl) || IsKeyPressed(eKeyRightCtrl)) && IsKeyPressed(keyCode+CTRL_OFFSET);
  }
  else if(keyCode >= ALT_RANGE_START && keyCode <= ALT_RANGE_END)
  {
    return (IsKeyPressed(eKeyLeftAlt) || IsKeyPressed(eKeyRightAlt)) && IsKeyPressed(keyCode+ALT_OFFSET);
  }
  else
    return IsKeyPressed(keyCode);
}

bool IsModifier(eKeyCode keyCode)
{
  return (keyCode == eKeyLeftShift || keyCode == eKeyRightShift ||
          keyCode == eKeyLeftCtrl  || keyCode == eKeyRightCtrl  ||
          keyCode == eKeyLeftAlt   || keyCode == eKeyRightAlt);
}

eKeyCode GetKeyPressed()
{
  for(int i=1; i<MAX_KEYCODE; i++)
  {
    if(!IsModifier(i) && IsKeyPressed(i))
      return i;
  }
  return 0;
}

bool IsKeyPressedAny()
{
  return (GetKeyPressed() != 0);
}

bool CheckKey(eKeyCode keyCode)
{
  if(keyCode == eKeyNone)
    return IsKeyPressedAny();
  else
    return IsKeyPressedCombi(keyCode);
}

bool CheckMouse(MouseButton button)
{
  if(button == eMouseNone)
    return Mouse.IsButtonDownAny();
  else
    return Mouse.IsButtonDown(button);
}

BlockEndType EndBlock(BlockEndType endType)
{
  _waitLoops = 0;
  _wasButtonDown = false;
  _wasKeyDown = false;
  return endType;
}

BlockEndType Block(int loops)
{
  while(loops>0)
  {
    _waitLoops = _minInt(MAX_WAIT, loops);
    loops -= _waitLoops;
    Wait(_waitLoops);
  }
  return eBlockTimeOut;
}

BlockEndType BlockKey(int loops, eKeyCode keyCode)
{
  _interruptKey = keyCode;
  if(loops<0)
  {
    // Keep waiting indefinitely until the WaitKey() was interrupted
    while(true)
    {
      _waitLoops = MAX_WAIT;
      WaitKey(MAX_WAIT);
      // If the wrong key, ignore the interruption
      if((keyCode == eKeyNone && _waitLoops != 0) || IsKeyPressedCombi(keyCode) || _wasKeyDown)
        return EndBlock(eBlockKeyInterrupt);
    }
  }
  
  while(loops>0)
  {
    _waitLoops = _minInt(MAX_WAIT, loops);
    loops -= _waitLoops;
    WaitKey(_waitLoops);
    if((keyCode == eKeyNone && _waitLoops != 0) || IsKeyPressedCombi(keyCode) || _wasKeyDown)
      return EndBlock(eBlockKeyInterrupt);
    loops += _waitLoops;
  }
  return EndBlock(eBlockTimeOut);
}

BlockEndType BlockMouse(int loops, MouseButton button)
{
  _interruptButton = button;
  if(loops<0)
  {
    while(true)
    {
      _waitLoops = MAX_WAIT;
      WaitMouseKey(MAX_WAIT);
      if(CheckMouse(button) || _wasButtonDown)
        return EndBlock(eBlockMouseInterrupt);
    }
  }
  
  while(loops>0)
  {
    _waitLoops = _minInt(MAX_WAIT, loops);
    loops -= _waitLoops;
    WaitMouseKey(_waitLoops);
    if(CheckMouse(button) || _wasButtonDown)
      return EndBlock(eBlockMouseInterrupt);
    loops += _waitLoops;
  }
  return EndBlock(eBlockTimeOut);
}

BlockEndType BlockMouseKey(int loops, eKeyCode keyCode, MouseButton button)
{
  _interruptKey = keyCode;
  _interruptButton = button;
  if(loops<0)
  {
    // Keep waiting indefinitely until the WaitKey() was interrupted
    while(true)
    {
      _waitLoops = MAX_WAIT;
      WaitMouseKey(MAX_WAIT);
      if(CheckMouse(button) || _wasButtonDown)
        return EndBlock(eBlockMouseInterrupt);
      if(CheckKey(keyCode) || _wasKeyDown)
        return EndBlock(eBlockKeyInterrupt);
      // Fallback if we can't identify what was pressed, but it doesn't matter
      if(_waitLoops != 0 && button == eMouseNone && keyCode == eKeyNone)
        return EndBlock(eBlockUnknownInterrupt);
    }
  }
  
  while(loops>0)
  {
    _waitLoops = _minInt(MAX_WAIT, loops);
    loops -= _waitLoops;
    WaitMouseKey(_waitLoops);
    if(CheckMouse(button) || _wasButtonDown)
      return EndBlock(eBlockMouseInterrupt);
    if(CheckKey(keyCode) || _wasKeyDown)
      return EndBlock(eBlockKeyInterrupt);
    // Fallback if we can't identify what was pressed, but it doesn't matter
    if(_waitLoops != 0 && button == eMouseNone && keyCode == eKeyNone)
      return EndBlock(eBlockUnknownInterrupt);
    loops += _waitLoops;
  }
  return EndBlock(eBlockTimeOut);
}

BlockEndType BlockSpeech(int loops, bool forceTimeOut)
{
  switch(Speech.SkipStyle)
  {
    case eSkipKey:
      if(forceTimeOut)
        return BlockKey(loops, Speech.SkipKey);
      else
        return BlockKey(-1, Speech.SkipKey);
    case eSkipKeyMouse:
      if(forceTimeOut)
        return BlockMouseKey(loops, Speech.SkipKey);
      else
        return BlockMouseKey(-1, Speech.SkipKey);
    case eSkipKeyMouseTime:
      return BlockMouseKey(loops, Speech.SkipKey);
    case eSkipKeyTime:
      return BlockKey(loops, Speech.SkipKey);
    case eSkipMouse:
      if(forceTimeOut)
        return BlockMouse(loops);
      else
        return BlockMouse(-1);
    case eSkipMouseTime:
      return BlockMouse(loops);
    case -1: // In AGS 3.4.0, Speech.SkipStyle reports -1 instead of eSkipTime(==2) when set to that value
    case eSkipTime:
      return Block(loops);
  }
}

function repeatedly_execute_always()
{
  if(_waitLoops != 0)
  {
    _wasButtonDown = CheckMouse(_interruptButton);
    _wasKeyDown = CheckKey(_interruptKey);
  }
  if(_waitLoops > 0)
    _waitLoops--;
}

SMF spam blocked by CleanTalk