[SOLVED] Hotspot & Object AnyClick bug/unintended feature/undocumented?

Started by homelightgames, Fri 19/09/2014 19:03:19

Previous topic - Next topic

homelightgames

I don't know if Anyclick is supposed to do this, or if this is an 'unintended' feature, but either way it's not how things usually work in AGS.

The setup is (as an example) is a room with a an object (oCup) and a hotspot (hBlue).  The object (oCup) starts off visible.
Code: ags

function hBlue_AnyClick()
{
  if (oCup.Visible == false) {
    Display("Anyclick has been used.");
    oCup.Visible = true;
  }
}
function hBlue_Interact()
{
  if (oCup.Visible == true) {
    Display("Interact has been used.");
    oCup.Visible = false;
  }
}


Now, unless I'm misunderstanding how Anyclick works and can't find this documented (which is a possibility), the Anyclick function should not be called initially, because the object is not initially invisible.  It seems like it should require a separate click, once oCup is visible, in order to be called.
And yet when I Interact with the object, on the same game engine step, it also calls Anyclick AFTER it calls the Interact function (which makes oCup invisible), so that Anyclick runs, even though it's function is before the Interact function in the code.

Usually AGS does not do this. If a test is false initially, and then made true after the initial test, it usually must wait until the game engine step in order to be tested again.

Now, I might be missing something, I might not know something, but either way I found this quite unintuitive.  It kept me for quite a while last night, trying to figure out what I was missing.  Is this how AGS is supposed to handle Anyclick?

BTW it behaves this way with Objects too.

monkey0506

Could you post your on_mouse_click function? Also if you're calling these functions yourself from anywhere, that code would be useful.

homelightgames

In this case, it is the Default AGS template, which means the Room script (and everywhere else) is completely clean.  That was one of the things that kept me up, though, was trying to find if I was recalling the Anyclick function from somewhere else, but I wasn't.  In other words, I'm pretty sure this occurs, regardless of the project (although I could be wrong, since I can't try every project ever made :)).

Code: ags

function on_mouse_click(MouseButton button) {
  // called when a mouse button is clicked. button is either LEFT or RIGHT
  if (IsGamePaused() == 1) {
    // Game is paused, so do nothing (ie. don't allow mouse click)
  }
  else if (button == eMouseLeft) {
    ProcessClick(mouse.x, mouse.y, mouse.Mode );
  }
  else if (button == eMouseRight || button == eMouseWheelSouth){
    // right-click our mouse-wheel down, so cycle cursor
    mouse.SelectNextMode();
  }
}

monkey0506

This is confirmed behavior of the current versions of AGS (per testing and a review of the engine source). I wasn't sure if this was intended behavior, but it's existed since at least the original source code import from AGS 3.2.1.

Current source:
Engine/ac/global_hotspot.cpp:117-122
Code: cpp
    if (thisroom.hotspotScripts != NULL) // there are AGS 3.0+ style interaction scripts for the room
    {
        if (passon>=0) // cursor is a standard mode
            run_interaction_script(thisroom.hotspotScripts[hotspothere], passon, 5, (passon == 3)); // invoke hotspot interaction event (e.g., hBlue_Interact)
        run_interaction_script(thisroom.hotspotScripts[hotspothere], 5);  // any click on hotspot -- (e.g., hBlue_AnyClick) this is ALWAYS invoked
    }


Legacy source:
Engine/ac.cpp:16620-16625
Code: cpp
  if (thisroom.hotspotScripts != NULL) 
  {
    if (passon>=0)
      run_interaction_script(thisroom.hotspotScripts[hotspothere], passon, 5, (passon == 3));
    run_interaction_script(thisroom.hotspotScripts[hotspothere], 5);  // any click on hotspot
  }


As you can see, the code is exactly identical. Whether or not this qualifies as a bug could be up in the air, but as it's been so long standing it's probably safer to assume that it was intentional behavior.

To be absolutely certain that your AnyClick event is unique, you would have to do something like this:

Code: ags
function hBlue_AnyClick()
{
  if ((game.used_mode == eModeWalk) || (game.used_mode == eModeLook) || (game.used_mode == eModeInteract) ||
      (game.used_mode == eModeUseinv) || (game.used_mode == eModeTalk) || (game.used_mode == eModePickup) ||
      (game.used_mode == eModeUserMode8) || (game.used_mode == eModeUserMode9))
  {
    // cursor mode was not unique, abort
    return;
  }
  if (oCup.Visible == false)
  {
    Display("Anyclick has been used.");
    oCup.Visible = true;
  }
}


I would suggest using IsInteractionAvailable except that if there's anything in front of the hotspot you might get unreliable results (though it does return false if only the AnyClick event is defined). This also brings to my attention that the engine is defining a Hotspot.IsInteractionAvailable method, but it's not imported and since there's a global function by the same name it can't be pulled in by user scripts either... :~( (Sorry, that's something being included in 3.3.1 and I didn't have a build of the most recent source)

homelightgames

I'm curious to know understand the conditions of why this was instigated this way, since it does seem somewhat counter-intuitive to the rest of AGS behavior.

The situation that I had this for was that when a certain object was visible, the mouse cursor would change to a custom cursor (which requires Anyclick) over the specific hotspot.  Interacting with the hotspot would make the object visible, while the Anyclick would change the room, which wouldn't work if it was all on the same click.
The hack I implemented to stop this from happening was to put a timer in the repeatedly execute, and then a test on the Anyclick function to ensure that it would not be called if it was on the same timer Step as the Interact function.  It worked, but only after a long while of trying to figure out if I was doing something wrong.

Thank you monkey_05_06 for taking the time to show and explain this.  It is very much appreciated.

visionmind

monkey0506

As for why it happens, I imagine it probably had something to do with the way interactions were handled in legacy versions of the editor. It's been a long time since I've used even AGS 2.72 (though I know some people still swear by it, perhaps they could chime in), but I believe the Interaction Editor was actually set up in a similar fashion such that any click, handled or not, was still a part of the "Any click" event. I think this was done in part to help simplify the graphical "scripting" (mediated through the Interaction Editor) and make it more user-friendly for beginners.

Edit: I've rethought this again and I think that the introduction of IsInteractionAvailable methods on a per-entity basis (e.g., Character.IsInteractionAvailable, Hotspot.IsInteractionAvailable...) really covers everything you would need. The 3.3.1 betas introduce these methods, so you could simply check at the beginning of your AnyClick events:

Code: ags
function hBlue_AnyClick()
{
  if (hBlue.IsInteractionAvailable(game.used_mode)) return;
  // ...
}


I think this is the best approach for now, without invalidating or breaking existing scripts.

homelightgames


Khris

Instead of using _Interact, you could simply put this in _AnyClick:

Code: ags
  if (mouse.Mode == eModeInteract) {
    // interact with hotspot
    ...
  }
  else {
    // other cursor
    ...
  }


That way the fact that _AnyClick is called after _Interact doesn't matter. If this messes with your using .IsInteractionAvailable(eModeInteract) elsewhere, just add an empty function as _Interact.

homelightgames

Thanks Khris, that works perfectly.  That's the sort of solution that makes me realize how much I overcomplicated the whole issue.

visionmind

SMF spam blocked by CleanTalk