Making RestoreGameSlot and game menus available at all times

Started by Fitz, Sun 19/04/2015 12:16:35

Previous topic - Next topic

Fitz

Loading savegames and displaying menus works just fine when the game's not busy with some blocking action. The problem is much of my game consists of dialog trees leading to cutscenes leading to new dialog trees -- during which loading and menus are unavailable. And even though I can have menus pop up by putting them in rep_exec_always, and that part works ok-ish (other than character speech displaying over the menus), loading savegames can't be put in rep_exec_always at all. All of that makes the checkpoint system I devised, with quickloading buttons, etc, not just clunky (because it can only be accessed when the character's walking freely), but impractival and inconvenient, along with such basic functions as being able to leave the game at any time (other than Alt-X, which is not quitting game, it's aborting it). Am I missing something really simple here or are those hard-coded behaviors that just are how AGS works and there's nothing you can do about it?

Monsieur OUXX

I know that performing advanced stuff (such as pausing and loading a game) while the game is performing some blocking built-in actions (especially dialogs, which display the text on top of everything) can be pretty complex.

I did some research into that, and I ended up finding some interesting workarounds using trial-and-error. The bad news is that I can't remember any of this from the top of my head. But I suggest you have a look at how our "real pause" system works and even hides the current line of dialog. Just start a new game and click on the blue "D" button on the right while a dialog is running. (I'll send you the link in PM)
 

MyEakinBack

I have a suggestion, although I'm not sure how good it is. You could use the custom dialogs available in AGS and add some images for saving/restoring etc. into the dialog itself. You could then use the dialog_options_mouse_click function to catch the mouse clicks and send them to the code for saving & restoring (while also killing the dialog). Here is an example:

Code: ags

function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
{
  // Create dialog options area
  info.X = 138;
  info.Y = 128;
  info.Width = 180;
  info.Height = 70;
}

function dialog_options_render(DialogOptionsRenderingInfo *info)
{
  // Clear the area
  info.Surface.Clear(Game.GetColorFromRGB(192, 192, 248));
  // Draw the image buttons
  info.Surface.DrawImage(4, 1, 443);
  info.Surface.DrawImage(16, 1, 444);
  // Set the dialog options to begin below the images
  int i = 1,  ypos = 12;
  // Render all the options that are enabled
  while (i <= info.DialogToRender.OptionCount)
  {
    if (info.DialogToRender.GetOptionState(i) == eOptionOn)
    {
      if (info.ActiveOptionID == i) info.Surface.DrawingColor = 14;
      else info.Surface.DrawingColor = 4;
      info.Surface.DrawStringWrapped(5, ypos, info.Width - 10, eFontSmall, eAlignLeft, info.DialogToRender.GetOptionText(i));
      ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontSmall, info.Width - 10);
    }
    i++;
  }
}

function dialog_options_get_active(DialogOptionsRenderingInfo *info)
{
  int i = 1,  ypos = 0;
  // Find the option that corresponds to where the player clicked
  while (i <= info.DialogToRender.OptionCount)
  {
    if (info.DialogToRender.GetOptionState(i) == eOptionOn)
    {
      ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontSmall, info.Width - 10);
      if ((mouse.y - info.Y) < ypos)
      {
        info.ActiveOptionID = i;
        return;
      }
    }
    i++;
  }
}

function dialog_options_mouse_click(DialogOptionsRenderingInfo *info, MouseButton button)
{
  // this would call your save & restore code
  if (mouse.x > 2 && mouse.x < 12 && mouse.y > 1 && mouse.y < 11) {
    StopDialog();
    save_game();
  }
  if (mouse.x > 14 && mouse.x < 24 && mouse.y > 1 && mouse.y < 11) {
    StopDialog();
    restore_game();
  }
}


Here is the example dialog rendered showing two arrow images (sprites 443 & 444) and some dialog options:

completed: Beyond Eternity

Monsieur OUXX

#3
Oh sorry sorry I think I didn't understand the issue.

Are you annoyed by the fact that you can't do custom things while dialog options are displayed? (I thought you were talking about the dialogs themselves).

I confess MyEakingBack's solution is too complex for me (I don't master at all the dialog bits of AGS scripting, more the visual bits).
HOWEVER if your issue is just the autosave thing, why don't you just set a boolean flag in your repeatedly_execute_always whenever the game need sto be saved, and then in repeatedly_execute juste check the value of that boolean and call SaveGameSlot? The saved game will only one frame/game loop late. 

Also KEEP IN MIND that there are very good custom modules that handle custom dialog options very well. You should download one and see if the way it's written makes it easier for you to manage blocking things.
 

Fitz

No, the problem is deeper than that -- I can't do anything during cutscenes, either, and they are PLENTY (and include lots and lots of talking, which pops up over menus)! I need a method for quickloads, exiting game and using game-loading buttons at any time -- a method that overrides EVERY other action (cutscenes, speech, dialog options). If the speech's ZOrder is untouchable, I might simply reduce the checkpoint menu's position and size so that it doesn't overlap with the speech (which usually displays in the upper half of the screen). So, I'll have a look into your code first. BUT MyEakinBack does make some good points there that I might have a use for. So, thank you both -- and I'll let you know how it worked out (or just come back with more questions :P).

Monsieur OUXX

Quote from: Fitz on Mon 20/04/2015 06:26:31
I might simply reduce the checkpoint menu's position and size so that it doesn't overlap with the speech (which usually displays in the upper half of the screen).

You can try making the current speech line disapear instantly by switch speech to "timed mode" and then setting the speed to maximum speed. When your menu appears the speech line will briefly stay on screen and almost immediately disappear. The only draw back is that that line of speech will be consumed, so if you "unpause" the player cannot re-read it.
 

Fitz

Yeah, thought of that, too -- and while it's not perfect, it might be enough. The whole checkpoint/quickload system is intended for players who've already been through a given part or the whole game and want a quick access to some previous chapters. When you play it for the first time and you just want to get to the end, neither will really be necessary: the gameplay's pretty linear, you go from left to right, and if you fail, the game brings you back to the last checkpoint automatically -- and the cutscenes (including animations of failure) can always be skipped by Esc. So yeah, for a person who's already played the game, skipping a given line of dialogue shouldn't be an issue. I'll work this solution into the code once I figure out the pause system.

Monsieur OUXX

Quote from: Fitz on Mon 20/04/2015 08:55:37
once I figure out the pause system.
If you've looked at what I've sent you, my discovereies were actually not as cool as I remembered. It's mostly fiddling with the SkipStyle to make sure dialogs can be skipped when needed or not skipped by mistake when you have a custom gui displayed and don't want the keypresses to skip speech.
Also in the RealPause module you can have a look at "suspendableWait" which does exactly like Wait but also works when the game is paused. That's for when you want to do a Wait in repeatedly_execute_always which normally fobids them -- typically, when you want to do some timed things in your custom Guis while the game is actually paused using AGS' built-in PauseGame();
You can see that function in action if you search macros WAIT_10, WAIT_100, WAIT_200 etc. in the scripts.

 

Fitz

Looking through it right now -- and obviously feeling like a monkey looking at a space rocket engine ;) SuspendableWait does sound like a step in the right direction -- because the player needs to be able to click the checkpoint buttons (regular GUI buttons), and I can't put the code for buttons into rep_exec_always. But the one thing I'm wondering is whether SuspendableWait can just put game's other processes on hold but only to return to them later (via unpause), or can it go all the way, including terminating other processes to carry out RestoreGameSlot?

Monsieur OUXX

#9
Quote from: Fitz on Mon 20/04/2015 10:01:05
But the one thing I'm wondering is whether SuspendableWait can just put game's other processes on hold but only to return to them later (via unpause), or can it go all the way, including terminating other processes to carry out RestoreGameSlot?

That's a very good question. I have no idea. ;) The thing with AGS loops is that I always need some time to remember in what order things are done, and what blocks what (during the current game cycle or over several game cycles).

Seriously though, it's dangerous to talk about "processes" in AGS, it gives the false feeling that AGS is multithreaded, which is not true (well, there are just two big threads: the engine's inner working thread, and the custom script thread. And each is doing its work right after one another at each game cycle, so it's not really multithreading.

The simple idea behind suspendable wait is that it's a Wait (so you can call it only in repeatedly_execute, not repeatedly_execute_always) that actually waits indefinitely  if you call PauseGame somewhere else (typically: in repeatedly_execute_always). It can finish waiting normally if you call UnPauseGame afterwards.

As you know, Wait is blocking, but only for you and your custom repeatedly_execute scripts, but it is not blocking for the internal engine processing (updating of all internal variables, such as character positions, etc. -- allt he things that happen automatically at each game loop). It's also not blocking for your repeatedly_execute_always scripts, since they get executed at each game loop no matter what. I don't know if I can explain it more clearly, it's quite tricky.
Typically you would use that if you have two types of GUIs:
- the ones that should be affected by the game being paused (using GamePause). Typically, that would be your custom portrait GUI or dialog choices GUI. They're part of the game. In those, use SuspendableWait.
- the ones that should NOT be affected by the game being paused with GamePause. Typically, that's your Debug GUI or save/restore saved games GUI. In those, use regular Wait.

That's getting off-topic. The more I write,t he less I'm sure of what you're trying to achieve and if any of my suggestions matches your needs.
 

Fitz

Do you remember the code enough to check? Like, put a RestoreGameSlot in the button's code. I didn't have the chance to check the build yet, it needs some dll that I have to get and I'm busy with something else right now -- but yeah, if it lets you interrupt everything and load a game, then we have a winner. Entering and exiting a pop-up menu is not a problem, I tried that and it works -- it's trying to kill the game's processes and go do something else that's the tough part.

I have another idea, too. Each cutscene can be skipped with Esc. What if I changed it to eSkipAnyKey -- and if either F5 (which I use for checkpoint menu) or any quickload hotkeys were pressed, the game, instead of going to Room10, containing a VCR-style rewind sequence symbolizing going back to the last checkpoint, would go to a separate room with the checkpoint menu! The room would have no cutscenes, no blocking events, so you could run any script. If, however, you exited the room without choosing a checkpoint to load, the game would go back to Room 10, play the rewind sequence and load the latest checkpoint. The only things to determine:
1. Where do I put the code that'd link to the eSkipAnyKey to determine which of the "any key" was pressed?
2. How do I limit it to the cutscenes only -- because in normal walking mode it'd be unnecessary? Do I set a boolean and put 1 at the beginning of each cutscene and 0 at the end or did I miss some simple IsCutscenePlaying property? I could've sworn I've read about such a thing -- but memory is a fickle little thing ;)
3. Is there a way to kill a dialogue option menu and return to it later? There is no dDialog.Stop -- which would've been very handy. That's just optional, though -- if the solution above worked, it'd solve a million problems and that one is just a minor inconvenience.

But realistically, the more I think about my idea, the crazier, more convoluted and unfeasible it all looks. I might be able to get some things working (e.g. fail/death cutscenes), but when I think of what it'd do elsewhere (regular cutscenes that have more action following, for which I'd have to code individual code in response), everything comes crumbling down.

Fitz

I think I got it!

1. I set the cutscene skipping to eSkipAnyKey

2. I set new int global variable for cutscenes:
- by default it's set to 0
- as soon as the cutscene starts, I give it a value:
    a) if it's an animation of failure, I switch it to 1
    b) if it's a success/insigificant, I switch it to 2

3. I set condition in rep_exec_always for F6 - the checkpoint menu trigger

Code: ags
if (IsKeyPressed(eKeyF6)) {
  if (Cuts==1) //meaning failure
    {
    Cuts=3;
    }
  if (Cuts==2) //positive or neutral outcome
    {
    Cuts=4;
    }
  }


4. If there's a dialog starting at the end a positive/neutral outcome, I placed it inside a following condition:

Code: ags
if (Cuts!=4) {


5. I set a condition in rep_exec_always:

Code: ags
if (Cuts == 3) //failure
  {
  cMag.ChangeRoom(9); //go to a dedicated checkpoint menu room
  Cuts = 0;
  }

if (Cuts == 4) //positive or neutral outcome
  {
  gLevel.Visible = true;
  mouse.EnableMode(eModePointer);
  mouse.Mode = eModePointer;
  Cuts = 0;
  }


That way:
1. F6 ends the cutscene, just like any other key, but it also modifies the Cuts value
2. As soon as the cutscene is over and nothing blocks it, the game decides what to do:
- if F6 has not been pressed, it proceeds as usual
- if it has been pressed, the game automatically does one of the following:
  a) if the outcome was negative, it changes the room to checkpoint menu room
  b) if the outcome was positive/neutral, the checkpoint menu loads in the current room

I'm still testing it -- and there'll be a LOT of coding involved in activating the game dialog if the player leaves the same-room checkpoint menu -- BUT it's... interesting, to say the least!

SMF spam blocked by CleanTalk