Q: event depending on sound duration and grabbing an object

Started by AnasAbdin, Sun 31/07/2011 20:54:25

Previous topic - Next topic

AnasAbdin

hello all,

allow me to explain the situation:

the player did something that lead to a sound to be played. he should interact with an object until the sound file completes. Mouse must be clicked all the time until the sound ends.
this is the logic:

if (player triggers_an_event )
{
  play sound_file;
  disable all mouse functions except the interact;
  while ( sound_file.isPlaying() )
  {
        if ( mouse.interact(pressed_down_at_object_for_5_seconds)
        {
            player wins;
            restore all mouse functions;
        }
        else
        {
            player loses;
        }
  }
  if ( sound_file.ends_without_keeping_interacting_with_object )
  {
   player loses;
  }

}

I am mostly stuck in the -wait an event until a sound ends- part
and -keep holding mouse interact (hand) on an object to prevent a lose-event-

lol I hope I managed to explain the problem clearly...

another thing (greedy me):
is there a manual somewhere that contains all AGS commands? so I can go on with my coding without bothering you :) most of the commands are different and new to me, I  mean I do use JCreator but still.. AGS knows how to challenge me!!

Thanx for your time :)

Khris

In the AGS editor, hit F1, or double click agshelp.chm in the main directory. You'll get to the manual which has a list of all commands, among other things.
How did you code anything without that...?

To address your question:
In the function that contains the Sound.Play command, set a variable to true and start a Timer:

Code: ags
  aSound.Play();
  SetTimer(1, 10*GetGameSpeed());  // 10 seconds
  timed_sequence = true;


Declare the variable at the top of the room script:
Code: ags
bool timed_sequence;


To disable all mouse-clicks, create a on_mouse_click for the room (just paste this in the room script):
Code: ags
void on_mouse_click(MouseButton button) {
  if (timed_sequence) ClaimEvent();
}


This will prevent the global on_mouse_click from being executed if timed_sequence is true.

Now create the repeatedly_execute event for the room and make the function look like this:

Code: ags
int held_down_frames;

function room_RepExec() {

  bool mouse_over_object = (Object.GetAtScreenXY(mouse.x, mouse.y) == oObject);
  bool button_down = mouse.IsButtonDown(eMouseLeft);

  if (!timed_sequence) return;  // do nothing

  if (mouse_over_object && button_down) held_down_frames++;  // player is holding button down over object
      
  if (held_down_frames) {  // at some point, player started correct procedure
    if (!button_down || !mouse_over_object) {  // player let go of button or moved mouse off of object
      Fail();
    }
  }
  
  if (IsTimerExpired(1)) {
    if (held_down_frames >= 5*GetGameSpeed()) Win();  // at least 5 seconds until end of ten second timer
    else Fail();
  }
}


Both Fail and Win (which aren't actual commands) should set timed_sequence back to false and deactivate the timer: SetTimer(1, 0);

Edit: corrected Object.GetAtScreenXY

AnasAbdin

Quote from: Khris on Sun 31/07/2011 22:50:03
How did you code anything without that...?

Lol are you kiddin me!! I keep trying until it works :) I try to find similarities between java in general and AGS, they are not that different you know...

Quote from: Khris on Sun 31/07/2011 22:50:03
Declare the variable at the top of the room script:
Code: ags
bool timed_sequence;


Thanx man :) but I'm not THAT novice!!

the code is great, it's so much similar to what I had in mind, except that I lacked the knowledge of some methods by AGS but again thanx to you for pointing at agshelp.chm...

right now I have three conditions going on:

if player does not click the object at all and sound ends --> fails
if player clicks object but releases before sound ends --> fails
if player clicks object and releases after sound ends --> succeeds

however, when he succeeds he does his success routine and then heads back to the click and release does-not-click-the-object-at-all fail routine, I used the "return;" command inside the success routine but it did not work though.. I guess I'll  have to manage my way around the problem.

again thanx alot, I'll do my best to produce a great game in reward to your effort.

monkey0506

Instead of creating a separate "timed_sequence" variable and using a timer, why not just check the AudioChannel.IsPlaying? Also, Anas' original description seemed to indicate that the function should probably be blocking, which would save the call to ClaimEvent anyway...

I'm thinking something like this would be sufficient:

Code: ags
  // player triggers event
  AudioChannel *channel = aSound.Play();
  int counter = 0;
  while (channel.IsPlaying)
  {
    // inside this loop, mouse functions will be blocked anyway...
    if ((mouse.IsButtonDown(eMouseLeft)) && (Object.GetAtScreenXY(mouse.x, mouse.y) == oObject)) counter++; // mouse over object, and held down
    else if (counter)
    {
      // player already clicked on the object, but released it too soon
      // player loses
      return; // abort the rest of the loop and calling function, without cancelling the sound playing
    }
    if (counter >= (GetGameSpeed() * 5)) // mouse held down over object at least 5 seconds, regardless of game speed
    {
      // player held down mouse for 5 seconds
      // player wins
      return;
    }
    Wait(1); // lets the screen refresh without aborting the loop or function, while blocking other event handling functions
  }
  // player never clicked the object during the sound playing, or held the mouse down over the object less than 5 seconds
  // player loses

Khris

Quote from: AnasAbdin on Mon 01/08/2011 06:44:35
Quote from: Khris on Sun 31/07/2011 22:50:03
How did you code anything without that...?
Lol are you kiddin me!! I keep trying until it works :) I try to find similarities between java in general and AGS, they are not that different you know...

Uh, alright, I REALLY SHOULD HAVE KNOWN..!!

Quote
Quote from: Khris on Sun 31/07/2011 22:50:03
Declare the variable at the top of the room script:
Code: ags
bool timed_sequence;

Thanx man :) but I'm not THAT novice!!

Right, your Game in Production thread didn't follow forum rules, the screenshots are 319 wide and JPEGS, and you couldn't find the help file that comes with AGS or the online version of it, but OF COURSE you know where to put the declaration of a room variable, PLEASE EXCUSE MY IGNORANCE.

Seriously buddy, chill := How in the name of FSCK am I supposed to know what you know and don't. :)

Did you do this:
QuoteBoth Fail and Win (which aren't actual commands) should set timed_sequence back to false and deactivate the timer: SetTimer(1, 0);
?

monkey:
Yeah, I was thinking of doing it like that, too, but the mouse turns into the clock and GUIs might grey out and all that, so I decided to go the non-blocking route. You're right about IsPlaying, of course.

monkey0506

#5
That's easy enough to work around (completely generically and without permanently changing any user settings):

Code: ags
  // before while loop
  int waitGraphic = mouse.GetModeGraphic(eModeWait);
  int guiDisabledMode = GetGameOption(OPT_WHENGUIDISABLED);
  mouse.ChangeModeGraphic(eModeWait, mouse.GetModeGraphic(mouse.Mode));
  SetGameOption(OPT_WHENGUIDISABLED, 2);
  // while loop
  // ...
  // after while loop
  mouse.ChangeModeGraphic(eModeWait, waitGraphic);
  SetGameOption(OPT_WHENGUIDISABLED, guiDisabledMode);


So, either way the end result is pretty much the same. :P Oh, and LOL at the rest of your post. :=

Edit: Oh, and since I was using return inside the while loop, I'd probably have changed up some of that logic too if I needed to do this..otherwise it's only two function calls, so it's not like I'd be duplicating very much code if I just added it before each return.

AnasAbdin

I tried either ways, the while channel playing and the first way... something is always wrong going on and I'm just not able to get my hands on it...

this is what happened with the while channel is playing method:
first lets agree on this:
player wins = holds mouse over object until sound ends.
player slips = holds mouse over object but releases before sound ends.
player loses = doesn't click on the object at all until sound ends.


while the sound is playing, the only cursor available is the Wait cursor, however, it can be used normally!!! so what happened is I tried pressing mouse down all the time on the object while the sound is playing, the player wins then loses!
also, if I clicked the object while the sound is playing then release, player slips then loses..

either case leads to this: Wait cursor is on all the time, I can't get any other cursor and the mouse is dead while the player keeps repeating (lose) routine over and over...

Quote from: Khris on Mon 01/08/2011 10:52:49
Did you do this:
QuoteBoth Fail and Win (which aren't actual commands) should set timed_sequence back to false and deactivate the timer: SetTimer(1, 0);

I don't get it?? omg lol I think I've coded so much the last coupla weeks my mind got dumb on easy stuff!! did I mention I'm drawing/composing/writing all together!!


also:
how is this an integer?
Quote// before while loop
  int waitGraphic = mouse.GetModeGraphic(eModeWait);
  int guiDisabledMode = GetGameOption(OPT_WHENGUIDISABLED);

Khris

Not sure why you'd get a while cursor using my method, it isn't blocking. Did you rewrite it to include a while loop?

Anyway, I wanted to test my code, noticed I forgot that "XY" part in the Object.GetAtScreenXY command and wrote a Fail and Win function:

Code: ags
void Fail(String fail) {
  timed_sequence = false;
  SetTimer(1, 0);
  Display("Fail: %s", fail);
}

void Win() {
  timed_sequence = false;
  SetTimer(1, 0);
  Display("Win!");
}


I added a fail parameter to test the two different ways of failing (slipping/time).

Everything worked perfectly.
The only thing that might be missing is resetting the frame counter if the player is supposed to do this several times.

So again, not sure what the problem was. If I were overly suspicious I'd think you didn't test my code because you didn't mention the error and said it produced a wait cursor, which it clearly doesn't.

Edit:
Quote from: AnasAbdin on Mon 01/08/2011 13:58:59also:
how is this an integer?

Pretty much everything in AGS is an integer, enums, Character IDs, View numbers, Cursor modes, settings.
If you look at the cursor list in the editor, they are all numbered. And the valid values for game options are in most cases 0 or 1, but e.g. with OPT_PORTRAITPOSITION, it's 0 - 3, again, an integer.
Edit2:
mouse.GetModeGraphic() returns the sprite slot number, which is obviously an integer.
But still, "int old_mode = mouse.Mode;" is a valid way of storing the current mouse cursor mode.

AnasAbdin

dude of course I tested all code by you all!! you took from your own time and effort to help me the least I can do is use it!

to remove any suspicion I used your code EXACTLY asyou wrote it, of course I figured out the XY addition to the method name... still! the player wins/slips/loses normally, but then enters an infinite loop of lose...

I know that AGS stores alot of values as integers, but when I tried the while loop code AGS gave me an error about it... btw I am logging on the forum from a different platform, my AGS is running on another PC... so please forgive me for not cutting/pasting error messages and stuff...

I think the code leads to being stuck in an infinite loop here:

if (!timed_sequence) return;  // do nothing......

Khris

Well, when I test my code I don't get the wait cursor.
Also, if there was an infinite loop, I'd get one Display() after another and had to Alt-X out of the game, however I don't.

Also, the line you posted makes AGS ignore the rest of the room_RepExec function if the game isn't running the special timed sequence; as you can see in my Fail/Win functions, timed_sequence is supposed to be set back to false.
So what the line does is in fact prevent an infinite loop of failing/winning.

Could you post your script?

AnasAbdin

Code: ags

//top of room script...
bool timed_sequence = true;
bool mouse_over_object;
bool button_down;
int held_down_frames;
int counter = 0;  
  
void on_mouse_click(MouseButton button) {
  if (timed_sequence) ClaimEvent();
}

void Fail(String fail) {
  timed_sequence = false;
  SetTimer(1, 0);
  player.Say("Fail");
}

void Win(){
  timed_sequence = false;
  SetTimer(1, 0);
  player.Say("Win!");
}

function room_RepExec()
{
   mouse_over_object = (Object.GetAtScreenXY(mouse.x, mouse.y) == object[0]);
   button_down = mouse.IsButtonDown(eMouseLeft);

  if (!timed_sequence) {player.Say("Time out!!"); }  // <----- this is where the loop never ends...

  if (mouse_over_object && button_down) {held_down_frames++;}  // player is holding button down over object
      
  if (held_down_frames) {  // at some point, player started correct procedure
    if (!button_down || !mouse_over_object) {  // player let go of button or moved mouse off of object
      Fail("asd");
      return;
    }
  }
  
  if (IsTimerExpired(1)) {
    if (held_down_frames >= 5*GetGameSpeed()) 
      Win();
      else Fail("asd");
      
  }
}



man I'm sure it's a very simple solution... hehe

Khris

....

Like I described in my previous post, you have changed the one line that prevents the infinite loop.
Did you think removing "return;" wouldn't do anything...?

AnasAbdin

no no I did use return; and still it did not exit the loop!! I tried a coupla stuff from desperation...


Khris

The other thing I noticed is that you're initializing timed_sequence to true, not false like I did.

And you didn't post the object's interaction code.

But I'm really at a loss at what else to tell you, the code works fine for me.
Also, looking at the code you posted, as soon as the first fail is called, timed_sequence is set to false, so if you changed it back to say "if (!timed_sequence) return;", it should exit the loop right there.

monkey0506

#14
See, a funny thing about AGS is that, unless you're using a drastically different version that changes the script functions, or unless your copy of AGS just got royally screwed up somehow and is no longer producing valid executable files (in which case your game wouldn't even run), if the code works for one person, it works for everybody, unless you've changed something, in which case it's not the same code.

I realize you said you couldn't copy and paste the code because it's on separate systems, presumably your development system doesn't have internet access. Still, if you want the code to work, then you have to a) use the code we've provided, tested, and verified that it works, or b) tell us what changes you made to that code, what your current code looks like, what the expected results are, what the actual results are, and any error messages you might get along the way.

If you can't provide that information, it's going to be very difficult (i.e., impossible) for us to properly help you. I'm not trying to be rude, I'm just trying to explain what you need to do for us to be able to help you, because what you're doing right now isn't very helpful for us trying to help you.

Regarding the code, in your first post you indicated that if the user held down the mouse for 5 seconds that the "win" condition was satisfied automatically, but you later changed (or just reworded) that, so that the player has told hold down the mouse for at least 5 seconds, and not release it until the music has stopped playing (if I understand you properly).

Based on my current understanding of what you want, I've verified that this code works fine:

Code: ags
  // player triggers event
  AudioChannel *channel = aSound.Play();
  int waitGraphic = mouse.GetModeGraphic(eModeWait);
  int guiDisabledMode = GetGameOption(OPT_WHENGUIDISABLED);
  mouse.ChangeModeGraphic(eModeWait, mouse.GetModeGraphic(mouse.Mode));
  SetGameOption(OPT_WHENGUIDISABLED, 2);
  int counter = 0;
  while (channel.IsPlaying)
  {
    // inside this loop, mouse functions will be blocked anyway...
    if ((mouse.IsButtonDown(eMouseLeft)) && (Object.GetAtScreenXY(mouse.x, mouse.y) == object[0])) counter++; // mouse over object, and held down
    else if (counter)
    {
      // player already clicked on the object, but released it too soon
      // player loses
      mouse.ChangeModeGraphic(eModeWait, waitGraphic);
      SetGameOption(OPT_WHENGUIDISABLED, guiDisabledMode);
      Display("Sorry, you slipped!");
      return; // abort the rest of the loop and calling function, without cancelling the sound playing
    }
    Wait(1); // lets the screen refresh without aborting the loop or function, while blocking other event handling functions
  }
  mouse.ChangeModeGraphic(eModeWait, waitGraphic);
  SetGameOption(OPT_WHENGUIDISABLED, guiDisabledMode);
  if (counter >= (GetGameSpeed() * 5)) // mouse held down over object at least 5 seconds, regardless of game speed
  {
    // player wins
    Display("You win!");
    return;
  }
  // player never clicked the object during the sound playing, or held the mouse down over the object less than 5 seconds
  // player loses
  Display("Sorry, you lose!");


The only minor thing I found was that in lieu of Mouse.GetModeHotspotX/GetModeHotspotY, you have to do the work yourself to make sure that the wait cursor (which is never used for interaction processing) has the same cursor hotspot (X, Y) location as the cursor you're switching from or the cursor will shift when AGS changes it to the wait cursor (albeit displaying the same image, since I've provided for that). So if you're using the eModePointer cursor to interact with the object, then you should make sure that eModeWait has the same cursor hotspot.

Other than that, I've verified my code, and Khris has verified his. The code works, you just have to make sure you're implementing the same code. ;)

AnasAbdin

First of all, I'd like to thank you guys so much (Khris & +monkE3y_05_06+ ) for all the efforts and time you spent helping me, I can't express how much I appreciate it :)

I tried +monkE3y_05_06+'s last code and worked as a charm, I think my problem was I am not used to get code from other people, so I had to feel an urge to edit it alittle bit... also I misplaced the following:

Code: ags
  AudioChannel *channel = aSound.Play();
  int waitGraphic = mouse.GetModeGraphic(eModeWait);
  int guiDisabledMode = GetGameOption(OPT_WHENGUIDISABLED);


outside the function....

so yeah, I'm the one to blame (as if I wasn't from the first place hehe)...

Quote
The only minor thing I found was that in lieu of Mouse.GetModeHotspotX/GetModeHotspotY, you have to do the work yourself to make sure that the wait cursor (which is never used for interaction processing) has the same cursor hotspot (X, Y) location as the cursor you're switching from or the cursor will shift when AGS changes it to the wait cursor (albeit displaying the same image, since I've provided for that). So if you're using the eModePointer cursor to interact with the object, then you should make sure that eModeWait has the same cursor hotspot.

fixed, no problem.

again, you two are awesome. and yet again, sorry for the bother! and Khris, I'm sure I did something wrong using your 100% correct code, I'll see to that :)

SMF spam blocked by CleanTalk