SOLVED: How to set restart-points and changing GUI display

Started by Caracal, Sun 25/03/2012 20:29:19

Previous topic - Next topic

Caracal

Hey guys, this time i got stuck in the middle of my work.
I:
I am at the subject “creating death-scenes- advanced”.  Everything is working well.
I created a new room, 302 where the player goes after the cut scene occurred. There I inserted a plain background and created a new GUI with a button, when the player presses that button, he/she enters the previous room. That’s at least what I had in my mind. But the problem is that the player starts at the region where he/she just died (death triggered by region). So I found something like a RestartPoint. And I came up with this:

function Button5_OnClick(GUIControl *control, MouseButton button)
{
player.ChangeRoom(player.PreviousRoom);
if (player.PreviousRoom == 4){
SetRestartPoint(168, 565);
}
gDeath.Visible = false;
}
But the above function does not work. AGS wants me to put other values in the SetRestartPoint(???). But which value? Not the x and y where the player starts out again?
Is it not that easy? So what exactly can I do that the player starts out at a certain spot in the previous room, which command might be better?

II:
Also I have several different scenes where the player can die. And I want something different to be displayed on the gDeatz- GUI, which pops up in Room 302. (something like,”oh this time the bus got you” or “the cat scratched you to death, strange”). And maybe add a funny picture next to the writing â€"which is also always different, according to the previous event. Now do I have to create a different GUI for each happening (which I don’t hope, if I have dozens of scenes) or is it best to add an “Object.Visible if previous room was…. “.

THANKS for any help in advance!

Khris

SetRestartPoint() doesn't require any parameters. While the manual entry is a bit confusing, it certainly doesn't mention coordinates anywhere.
All it does is save the game using slot 999, which is the slot that's loaded when the function RestartGame() is called.
It's basically an extra save slot.

Now, both SetRestartPoint() and player.ChangeRoom(...) are not immediately called. They get queued instead, and are called as soon as the current function has finished and game control is about to be returned to the player.
I don't know which of the two is called first if both are in the queue; I guess .ChangeRoom is called first (if AGS does check which other commands are in the queue before calling one of them and changes the order accordingly).

In other words, the proper way to undo a death scene is this:
1. find a way to store the game state immediately before the fatal action is ordered by the player
2. show the death scene, then GUI
3. restore the game to its previous state

Obviously, (1) is the hardest of the three. Using SetRestartPoint() is bad, because either the game is pretty much constantly saving its current state, or every fatal action has to be caught in advance, which is annoying for something like stepping on a region.

What I would do is this:
in on_mouse_click / eMouseLeft, before ProcessClick is called, store the current room, coordinates and .Loop of the player in global variables.
To undo the death scene, make sure the player's view is unlocked, set player.Loop to the stored value, then call player.ChangeRoom(stored_room, stored_x, stored_y). This will move the player back to where they were before the fatal walk command, in case of a deadly region, and should work for any other kind of interaction, too.

As for one GUI showing multiple different death messages, just put a label and a (not-clickable) button on the GUI, then change the label's .Text/button's NormalGraphic before displaying the GUI.
An elegant way of doing this would be to use two arrays for the death messages and pictures, then use an enum and a custom function:

Code: ags
// header
enum CauseOfDeath {
  eDeathFalling,
  eDeathLava,
  eDeathGun,
  ...
};

// top of global script
String death_message[100];
int death_picture[100];

// in game_start
  death_message[eDeathFalling] = "Watch your step! Ah, never mind.";
  death_picture[eDeathFalling] = 123;
  death_message[eDeathLava] = "Your skin doesn't take too kindly to the molten rocks. Neighter does your flesh.";
  death_picture[eDeathFalling] = 124;

// death GUI function
function ShowDeathGui(CauseOfDeath cod) {
  lblDeathMessage.Text = death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeathMessage.Visible = true;
}

Call it like this:
  ShowDeathGui(eDeathLava);

Caracal

Hey Khris, thanks for your fast and detailed reply!
But i fear that this is all a bit too much for me at the fist look. I have tryed to follow your instructions step by step and came up with the following:

Quote from: Khris on Sun 25/03/2012 21:04:16
in on_mouse_click / eMouseLeft, before ProcessClick is called, store the current room, coordinates and .Loop of the player in global variables.
To undo the death scene, make sure the player's view is unlocked, set player.Loop to the stored value, then call player.ChangeRoom(stored_room, stored_x, stored_y). This will move the player back to where they were before the fatal walk command, in case of a deadly region, and should work for any other kind of interaction, too.

Where exatly can i find this "eMouseLeft" before the progressclick? I do understand the whoole idea and it sounds good but whats the script command for the "on mouse click" and do i put it in the "walk on region" function? And how do i store these coordinates then?

[/quote]

Code: ags
// header
enum CauseOfDeath {
  eDeathFalling,
  eDeathLava,
  eDeathGun,
  ...
};

// top of global script
String death_message[100];
int death_picture[100];

// in game_start
  death_message[eDeathFalling] = "Watch your step! Ah, never mind.";
  death_picture[eDeathFalling] = 123;
  death_message[eDeathLava] = "Your skin doesn't take too kindly to the molten rocks. Neighter does your flesh.";
  death_picture[eDeathFalling] = 124;

// death GUI function
function ShowDeathGui(CauseOfDeath cod) {
  lblDeathMessage.Text = death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeathMessage.Visible = true;
}

Call it like this:
  ShowDeathGui(eDeathLava);

[/quote]

Also here i get the idea but still got questions. For example when i create the new label, and button on the gui, i can not like... click on it and say "run script (at least not with the label). And then where in the script do i put the diffrent "//" categorys from your code?
I spend almost half an hour, reading the code over and over again, maybe i will understand it a bit better tomorrow. But maybe its just a bit too advanced for me right now. Thank you very much though!! :-)

Khris

1. Go to the on_mouse_click function in the global script. (Jump to it via the dropdown menu above the script)
In there, you'll find an if block with the condition (button == eMouseLeft).
Everything inside that block is run after you click anywhere in the game (and not on a GUI) with the left mouse button. Here's the block I'm talking about, taken right out of a Default game:
Code: ags
  else if (button == eMouseLeft) {
    ProcessClick(mouse.x, mouse.y, mouse.Mode );
  }

All you need to do is create a few global int variables, then set them before the ProcessClick line:
Code: ags
  else if (button == eMouseLeft) {
    player_loop = player.Loop;
    player_x = player.x; player_y = player.y;             // TYPO CORRECTED
    player_room = player.Room;
    ProcessClick(mouse.x, mouse.y, mouse.Mode );
  }

So on clicking the try again button on the death GUI, you'd call:
Code: ags
  gDeathMessage.Visible = false;
  player.Loop = player_loop;
  player.ChangeRoom(player_room, player_x, player_y);

This will put the player back to where they were before the last click was processed.

2. A label is used to put text on a GUI. In addition, you can change that text at any time. Why would you want to click it as the player, or find out if it was clicked as the designer? All we do is change the label's text to the specific death message we want to show to the player.

I kind of told you exactly where to put each code segment in the comment line.

// header: GlobalScript.ash

// top of global script: GlobalScript.asc, at the top, outside and before any of the functions

// in game_start: find the function called game_start, then put the lines in between { and }

// death GUI function: put this anywhere in the global script, again outside any function
To be able to call it from a room, you also need to add this line to the header, below(!) the enum definition:
Code: ags
import function ShowDeathGui(CauseOfDeath cod);


(I usually don't mention every single little step when answering non-trivial questions. My philosophy is that unless a person is able to fill in the blanks, they shouldn't be using the code anyway because I'd have to hold their hand through every single little change they want to make or typo they encounter.)

Edit: typo corrected

Caracal

This is difficult for me and maybe far above my current AGS-horizons but it is exactly what I need and I can see that in this code I find the solution for my perfect Sierra-like death-scene game. Therefore I am keen to understand !!

So I looked over it and came up with the following questions:
The following symbols are not defined:
player_loop = player.Loop;
player_x = player.x; player_y = player_y;
player_room = player.Room;
ProcessClick(mouse.x, mouse.y, mouse.Mode );
And I fail in understanding their meaning. Their suppose is selfexplaining (player x and y position ect). Do I have to set global variables before that?
Ok. For the part with the GUI I think that I set up the scripting just as you suggested. And the good thing: I don’t get an error message when test-running the game!!!
The only thing that remains here as a question: how do I say AGS when a certain scene occurred, meaning how does it know that right now “eDeathFalling” occurred? Where do I put this in? Because I think that this is the reason why the text of the Gui Label and the image of the Button don’t change.
At the beginning I tried the simple way â€"which even I understood- I enhanced the “Try again”Button with the “change to previous room”, “GUIDeath.Visible = false” function. This was simple and did work except for the fact that the character would start on the region he died on (mostly even outside the walkable area) isn’t there another simple way to reset his position? (except coming up with a new GUI for each scene)
(I perfectly understand what you mean and I think I am a good example of what you mention, but once I got this clear I will be able to understand practice and enhance this. So I am really trying to understand this)

Khris

You have to create the global variables yourself.
Open the Global variables pane, then add ints with the names I gave you (those with _ in them), initial value 0.

Their meaning is this: every time the player clicks their mouse to give the player character an order, before the order is executed, the player character's room, position and direction they're facing is saved in these global variables for later use (instead of setting a RestartPoint, if you will).

If the order results in the character dying, what will happen is some walking, maybe an animation, maybe even a room change, then the Death GUI is shown. The point is that the character's coordinates have changed, the direction they were facing has changed, maybe even the room. But since we stored all that in the global variables, we can move the game "back" to the situation before the fatal action was "clicked".

The only downside, and I'm only realizing this now: obviously, if in addition room objects changed their position or sprite, that's not reset automatically. So in that cases you have to do it by hand. But we'll get to that later.

Like I told you in my previous post, when the layer clicks "Try again", the player's position is restored using the values that were saved in the global variables prior to the fatal action (3rd code block).

How does AGS know when eDeathFalling occured? Like I told you at the very bottom of my first reply, after animating the death of the player character, show the GUI using ShowDeathGui(eDeathFalling);.

Caracal

Sometimes miracles do happen.
I set up the global variables with the initial int of 0 (I think that is where my mistake was yesterday) and it worked. Following the rest of your instructions now the game restarts exactly at the point of fatal action. Amazing. Thank you so much for this.

The only thing that I have noticed is:
The one death scene is triggered by the interaction with another character in the room. A short cut scene occurs and even though I wrote cMuder.UnlockView(); or I also tried SetView (the proper view), after the cutscene, upon reentering the room (by pressing the try again button), the cMurder only displayed as the default poster. When I step onto the region of interaction the cut scene occurs as scripted, but the normal view is just the default graphic.
Now after this monumental success, I tried to move on to the Gui, which was suppose to change pictures and text according to the death scene. But even though I followed all instructions, set up everything as requested AGS keeps telling me this:
“GlobalScript.asc(12): Error (line 12): Undefined token 'LabelDeathMessage'”
I even called the Label in the Gui DeathMessage.
The same thing is with the button, its also undefined bDeathPic, even though I called it DeathPic.

The full function which is causing the problem is this:

function ShowDeathGui(CauseOfDeath cod) {
  lblDeathMessage.Text = death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeathMessage.Visible = true;
}
Also if I try to add more enumerations in this:
// header
enum CauseOfDeath {
eDeathFalling,
eDeathLava,
eDeathGun,
...
};
Then AGS wont like… accept them. Its not giving me a comant-suggestion when I start writing them out, and it calls them “undefined” I wrote this:
enum CauseOfDeath {                                // All the deathscenes
  eDeathPit,
  eDeathPoison,
};
Also it says that the “ShowDeathGui” is undefined. Even though I imported the “ ShowDeathGui(CauseOfDeath cod);” Function
I could have sworn it worked yesterday.

Snarky

I'm not sure this is actually the source of your problems, but a couple of quick points: First, there's a difference between "LabelDeathMessage", "lblDeathMessage" and a label called "DeathMessage". Just check what the scripting name of the label is (under its properties), and make sure that that's the exact name (names are case-sensitive, for example) you use in line 12 of your GlobalScript.

Second, AGS's autocomplete doesn't always update correctly when you add new things, like more enum values, and sometimes it'll even say that there's something wrong in code referencing these new values. The simplest thing is to save your game, shut down AGS and restart the application; that should update it.

Khris

Yes, like Snarky explained, if the code says "lblDeathMessage", the label's name is also supposed to be "lblDeathMessage".
(When AGS went OO a few years back, it would take a character's name like "ROGER" and auto-generate the script name "cRoger", but that was confusing and didn't do much good, so it was scrapped. Since AGS now allows designers to choose any name they want, that's exactly the name you're supposed to use in scripts.)

Most people, like myself, kept the convention though, so I still put "lbl" at the start of all my label names, just to make my code more readable.

With enums, make sure there's a comma after each value, except the last one:
Code: ags
enum CauseOfDeath {                                // All the deathscenes
  eDeathPit,
  eDeathPoison
};


Regarding ShowDeathGui, could you post the function in which you're calling it?

Caracal

//At the top of the script I have the following:
enum CauseOfDeath {                                // All the deathscenes
  eDeathPit,
  eDeathPoison
};

import function ShowgDeath(CauseOfDeath cod);
String death_message[100];                        //Death message text
int death_picture[100];                           //Death message image


function ShowgDeath(CauseOfDeath cod) {
lblDeathMessage.Text = death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeath.Visible = true;
}
//Further down in the script i have this:
function ShowgDeath (CauseOfDeath cod){
  lblDeathMessage.Text= death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeath.Visible = true;
}
This is all i have which relates to the Gui. At least all I know of. I pretty much tried to stick to your instructions and changed the names.
Now with your advices and with the hints to the enums these struggle got obliterated. But still if I put into the room-script ShowgDeath(eDeathPit) then nothing happens, AGS even tells me this:
Failed to save room room4.crm; details below
room4.asc(36): Error (line 36): Undefined token 'ShowgDeath'

The entire cut scene looks like this:
function region1_WalksOnto()
{
Wait(20);
aBubbels.Play();
cHenk.Walk(88, 701);
Wait(10);
cHenk.LockViewFrame(3, 0, 0);
cHenk.Move(81, 724, eBlock, eAnywhere);
cHenk.LockView(3);
cHenk.Animate(0, 10, eOnce, eBlock, eForwards);
Wait(10);
cHenk.UnlockView();
aBubbels.Stop();
aAmbient.Stop();
cHenk.ChangeRoom(302);
ShowgDeath (eDeathPit)         //this is whats not defined even though I have everything set up in the global script?
}


Khris

Alright:

in GlobalScript.ash, the global script's header:

Code: ags
enum CauseOfDeath { 
  eDeathPit,
  eDeathPoison
};

import function ShowgDeath(CauseOfDeath cod);


At the top of GlobalScript.asc:
Code: ags
function ShowgDeath (CauseOfDeath cod){
  lblDeathMessage.Text= death_message[cod];
  bDeathPic.NormalGraphic = death_picture[cod];
  gDeath.Visible = true;
}

Caracal

I added these but still it wont do it. AGS gives no error message but also nothing changes on the Gui within the game.
If I put the “ShowgDeath (eDeathPit)” underneath the cutscene code in Room 4 then AGS says
“Failed to save room room4.crm; details below: room4.asc(35): Error (line 35): Undefined token 'ShowgDeath'”

Which is odd because I think that I have everything as it is suppose to so far.

Also I forgot to show my InGame function, yesterday. This is what I have there:

Code: ags
 function game_start() {
  // Put the code all in a function and then just call the function. 
  // It saves cluttering up places like game_start.
  initialize_control_panel(); 
  // Use the KeyboardMovement module to, per default, replicate the standard
  // keyboard movement of most Sierra games. See KeyboardMovement.txt for more info
  KeyboardMovement.SetMode(eKeyboardMovement_Tapping); 
//Death scenes recommended for the text messages
    death_message[eDeathPit] = "random default text.";
    death_picture[eDeathPit] = 43;
    death_message[eDeathoison] = "banal default text";
    death_picture[eDeathpoison] = 45;
    // in game_start 
}



Khris

Well, AGS won't change anything on the GUI as long as you don't actually call the ShowgDeath function.
So let's solve the undefined token error first, which suggests you didn't import the function.

To be clear, open the script called "GlobalScript.ash". That's were the enum definiton and import line goes. This script should be pretty much empty besides that.

Regarding the end of the cutscene code:
Code: ags
cHenk.ChangeRoom(302);
ShowgDeath (eDeathPit)

There's a semicolon missing at the end of the second line. Also, the room change is going to happen after the GUI is shown, since room changes get queued until the function has finished.

Caracal

Allright, this does it! Problem solved the GUI works.
By stressing that I have to import it into the asH script you told me just the right thing! … It works. Thank you so mutch !
But there still is something, that, I think, you mentioned earlier.
After the following scene occurred:
Code: ags

function region2_WalksOnto()
{
  cMurder.LockViewFrame(5, 1, 0);
  cHenk.Walk(593, 611, eBlock, eWalkableAreas);
cMurder.LockView(PLANTACTION);
cHenk.LockViewFrame(ROONFALL, 1, 0);
cMurder.Animate(0, 15, eOnce, eBlock, eForwards);
cMurder.UnlockView();
cMurder.LockViewFrame(5, 1, 0);
Wait(10);
aAmbiente.Stop();
cMurder.LockView(4);
cHenk.UnlockView();
ShowgDeath (eDeathPlant);
cHenk.ChangeRoom(302);
}

Everything is fine now with the GUI and the restart. BUT, once the Player enters the previous room again the View of the “cMurder” is kind of… well it’s the default-purple-poster. When I step onto the region again, the animation occurs as normal but the normal view of cMurder has permanently changed into the default graphic. Strange, what could be the reason?


Khris

The last cMurder command locks the view to 4.
Also, the poster is sprite 3, not the blue cup (0), which is used when the sprite is missing.

It just looks like you confused the loop with the view parameter somewhere or something like that.
How and where are you unlocking cMurder's view after the region event?

Caracal

I changed the Lock view 4 into an Unlock View.
And it was the blue cup. (it just looked like the poster at the first sight.
I was not sure but then I thought . Then I saw that in the “normal” character view for cMurder I had only one image (the first frame of loop 0) Since the cMurder is a static figure he needed only this one frame (think of it as a stature) therefore I thought that I could leave the loop 1 and 2 empty. But I noticed that AGS used the blue cup as the default images and automatically generated these. I just replaced them with the image from loop 0. And now everything works fine. Even though the cMurder does not move from the spot.
I think that’s it.
Thank you so much Khris.
I believe it was not easy with me but now I have everything so far. I am glad now and cant tell you how happy I am. Thanks to you I can finally do something Sierra-KingsQuest 7 like. You’re a hero!

how can I mark this thread as solved?

steptoe

Edit your first posting.

I usually add 'SOLVED' to the start of the subject line and change message icon to thumbs up.
It's not over until the fat lady sings..

Khris

You're welcome :)

The reason why the loop changed is the LockViewFrame command you're using. That changes the frame to view 5, loop 1, frame 0, and after UnlockView, the character will continue to face left (loop 1). That's intentional because the animation view might have a separate animation for every direction and AGS simply assumes that the other view's loop 1 contains the facing-left animation.

You can solve this without putting frames in cMurder's view by calling cMurder.Loop = 0; after unlocking the view.

Caracal

Makes perfectly sense! Naturally the character faces the direction of interaction, i observed that on other interactions before i just did not remember at the moment. How do you know all these things, did you program AGS yourself?! (rethorical question i suppose)

SMF spam blocked by CleanTalk