Custom dialog options extension to Script API

Started by Crimson Wizard, Mon 24/11/2014 22:04:20

Previous topic - Next topic

Crimson Wizard

There was a request by abstauber to add functions which set active dialog option and run some option; also the one that forces custom options to redraw.
http://www.adventuregamestudio.co.uk/forums/index.php?issue=542.0
http://www.adventuregamestudio.co.uk/forums/index.php?issue=532.0

He needed that because he was making a dialog options controlled by keyboard.

If I understand the situation right, the actual problem is that there are no events for key press and timed update (tick) related to custom dialog options, that would have RenderingInfo as parameter (DialogOptionsRenderingInfo is not accessible from normal function). There is one for mouse click, though (dialog_options_mouse_click), and the one called "dialog_options_get_active", which is practically "on mouse move" event.

What I propose is to add two more optional event handlers:

1. A timed updated event -- dialog_options_update / dialog_options_tick / dialog_options_repexec, or something like that.
2. A key press event -- dialog_options_key_press.

Both will get DialogOptionsRenderingInfo as parameter.
Current active option may be then set with DialogOptionsRenderingInfo.ActiveOptionID from dialog_options_key_press.
Timed update event may be used for any changes to GUI over time.

Last but not least, introducing a DialogOptionsRenderingInfo.Update() function will help to redraw GUI when required, thus supporting special timed effects. This may be called from either *_key_press or *_repexec event handlers, depending on situation.


Crimson Wizard

#1
No feedback?

Anyway, I made this demo build:
http://www.mediafire.com/download/6by5yp1a51vasg2/AGS-3.4.0.1--dlgopt-demo.zip
Code branch: https://github.com/ivan-mogilko/ags-refactoring/tree/feature-scapi-dialogopts

Changes:
1. Now supported: function dialog_options_repexec(DialogOptionsRenderingInfo *info). Compareable to "repeatedly_execute", it is called every tick when custom dialog options are on screen.
The function may be used if you need to update the UI state dynamically without human interaction.
Note that I won't recommend you to do drawing in this function. Instead, call info.Update() if you need UI to be repainted (see below).

2. Now supported: function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode). It is called when a key is pressed while custom dialog options are on screen.
Note that unlike dialog_options_mouse_click and dialog_options_get_active callbacks this one does not make options redraw itself automatically.

3. DialogOptionsRenderingInfo.Update(): forces custom dialog options to redraw itself at any random time.

4. The previously existing property DialogOptionsRenderingInfo.ActiveOptionID now makes options to redraw themselves when being set a new value.


Random example:
Code: ags

function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode) 
{		
    if (keycode == eKeyUpArrow && info.ActiveOptionID > 1)
        info.ActiveOptionID = info.ActiveOptionID - 1;
    if (keycode == eKeyDownArrow && info.ActiveOptionID < info.DialogToRender.OptionCount)
	info.ActiveOptionID = info.ActiveOptionID + 1;
}


EDIT: I realized I forgot about running dialog option from script. I'll add this next.

abstauber

Quote from: Crimson Wizard on Thu 27/11/2014 11:53:56
No feedback?
Hehe, I thought it would be mockery if I'd answer this :)
My feedback is of course: YES YES YES, what an improvement! :D Change #4 seems to be the most convenient way for me, I'll try it out soon.

If I remember correctly, the DialogOptionsRenderingInfo surface also didn't support 32bit alpha transparency - at least semi transparent backgrounds are rendered with semi-solid magic pink. Would it be hard to fix this as well?

Crimson Wizard

Quote from: abstauber on Thu 27/11/2014 12:04:31
If I remember correctly, the DialogOptionsRenderingInfo surface also didn't support 32bit alpha transparency - at least semi transparent backgrounds are rendered with semi-solid magic pink. Would it be hard to fix this as well?

Noooow.... this was fixed in 3.3.0 :)

Quote
* Added "DialogOptionsRenderingInfo.HasAlphaChannel" property that lets you enable alpha channel for custom dialog option surface.
You set this property in dialog_options_get_dimensions.

Gurok

I thought these were very cool, CW! I haven't tried them yet because I'm waiting for them to hit the next 3.4.0 build. In particular, this function:
function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode)

Will be very cool. I can see a lot of games using it to bind the last dialogue option (e.g. goodbye) to the escape key.
[img]http://7d4iqnx.gif;rWRLUuw.gi

abstauber

Quote from: Gurok on Thu 27/11/2014 12:38:07
Will be very cool. I can see a lot of games using it to bind the last dialogue option (e.g. goodbye) to the escape key.
The final puzzle piece will be to trigger dialog options via keyboard for this ;)
Something like dSampleDialog.StartOption(4) (with 4 being the 4th dialog option inside the sample dialog)... or  can dialog_options_key_press trigger this as well?

Crimson Wizard

#6
Quote from: abstauber on Thu 27/11/2014 12:42:55
Quote from: Gurok on Thu 27/11/2014 12:38:07
Will be very cool. I can see a lot of games using it to bind the last dialogue option (e.g. goodbye) to the escape key.
The final puzzle piece will be to trigger dialog options via keyboard for this ;)
Something like dSampleDialog.StartOption(4) (with 4 being the 4th dialog option inside the sample dialog)

I am on it, although I am currently not sure what would be the better way to do this.
For starters, there's a conceptual difference between using DialogTopic or DialogOptionsRenderingInfo object to run an option.
If you use DialogTopic, the index must mean a global index of options, while if you use DialogOptionsRenderingInfo, then the index should mean a visible option, same as ActiveOptionID.

abstauber

In the meantime I gave this a go, but this doesn't seem to run my existing code:
The dialog gets rendered and crashes right after it.



Quote from: Crimson Wizard on Thu 27/11/2014 12:45:44
For starters, there's a conceptual difference between using DialogTopic or DialogOptionsRenderingInfo object to run an option.
If you use DialogTopic, the index must mean a global index of options, while if you use DialogOptionsRenderingInfo, then the index should mean a visible option, same as ActiveOptionID.

An easy and consistent way could also be somthing like DialogOptionsRenderingInfo.RunActiveOption. Right now something like this happens automatically while clicking on an option. There's just no way to do this for keyboard interactions.

The global DialogTopic solution would enable starting options from a non-running dialog which might be odd and unwanted.

Gurok

Quote from: Crimson Wizard on Thu 27/11/2014 12:45:44
I am on it, although I am currently not sure what would be the better way to do this.
For starters, there's a conceptual difference between using DialogTopic or DialogOptionsRenderingInfo object to run an option.
If you use DialogTopic, the index must mean a global index of options, while if you use DialogOptionsRenderingInfo, then the index should mean a visible option, same as ActiveOptionID.

If you're tossing up between the two, a method of the Dialog object (e.g. dSampleDialog.StartOpiton(4)) would allow authors to write their own method for DialogOptionsRenderingInfo if they wanted.

I'm sorry. I forgot there wasn't such a capability. I was just dreaming about potential use cases. Didn't mean to make more work for you!
[img]http://7d4iqnx.gif;rWRLUuw.gi

Crimson Wizard

I was playing with these new functions and came to conclusion that we'd need a way to completely disable default mouse interaction, otherwise moving and clicking mouse around breaks keyboard-controlled UI.
I am thinking either add another property (like boolean DefaultMouseControls), or disable mouse control if the currenly obligatory  "dialog_options_get_active" callback is not found in script.

abstauber

I'm in favor of a DefaultMouseControls property which can be changed during runtime. For example if one releases a touch-device version of the gui, it would be nice to switch back to mouse/touch controls. Also some people might want to mix keyboard and mouse controlled dialogs.

Have I already mentioned that I most super highly appreciate, that you started working on this? Thank you!  ;-D

Crimson Wizard

#11
Quote from: abstauber on Mon 01/12/2014 08:53:10
I'm in favor of a DefaultMouseControls property which can be changed during runtime. For example if one releases a touch-device version of the gui, it would be nice to switch back to mouse/touch controls. Also some people might want to mix keyboard and mouse controlled dialogs.

I have another alternative idea which I'd like to try out first: disable all default behavior and require user to implement the one he needs.
Script-wise, in comparison to standard method, this would require game dev to additionally write this:
Code: ags

function dialog_options_mouse_click(DialogOptionsRenderingInfo *info, MouseButton button)
{
    info.RunActiveOption(); // considering the active option is set in "dialog_options_get_active"
}


If the user does not want mouse controls, then he simply does not implement dialog_options_get_active (which, as I said, is in fact "on mouse move") and dialog_options_mouse_click. Key controls may be implemented using only dialog_options_key_pressed.


In my opinion this will make the dialog options system complete (allows any kind of controls) and more transparent (no implicit behavior).

abstauber


Crimson Wizard

#13
Here's another build:
http://www.mediafire.com/download/74c05tchsgq89k0/AGS-3.4.0.1--dlgopt-demo2.zip

Changes:
* Implemented bool DialogOptionsRenderingInfo.RunActiveOption(), which runs the option set as DialogOptionsRenderingInfo.ActiveOptionID. Returns true if the option may be run, otherwise - false.
* Game dev now must explicitly run active option, even if he wants mouse controls.
* Game dev now must explicitly reset active option if the mouse is not above options UI
(not sure about this, should see how this will work).

Other changes that allow more freedom in customizing your Dialog Options:
* "dialog_options_get_active" callback is run even if the mouse is beyond UI bounds;
EDIT: Hmm, this struck me that we don't need this callback at all if we have "dialog_options_repexec".
* "dialog_options_mouse_click" callback is run regardless of whether mouse was over particular option or not;
* options UI no longer unconditionally redrawn if the mouse button was pressed (you need to call Update if something has changed).

I remind that the UI redraw is run automatically if ActiveOptionID has changed, therefore you don't need to to extra call Update() if you set ActiveOptionID.


Examples of working custom options.

1. Classic mouse controls
Spoiler

int dlg_opt_color = 14;
int dlg_opt_acolor = 13;
int dlg_opt_ncolor = 4;

function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
{
    // Create a 200x200 dialog options area at (50,100)
    info.X = 50;
    info.Y = 100;
    info.Width = 200;
    info.Height = 200;
}

function dialog_options_render(DialogOptionsRenderingInfo *info)
{
    info.Surface.Clear(dlg_opt_color);
    int i = 1,  ypos = 0;
    // 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 = dlg_opt_acolor;
            else info.Surface.DrawingColor = dlg_opt_ncolor;
            info.Surface.DrawStringWrapped(5, ypos, info.Width - 10,
                    eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
            ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
        }
        i++;
    }
}

function dialog_options_get_active(DialogOptionsRenderingInfo *info)
{
    info.ActiveOptionID = 0;
    if (mouse.y < info.Y || mouse.y >= info.Y + info.Height ||
        mouse.x < info.X || mouse.x >= info.X + info.Width)
    {
        return; // return if the mouse is outside UI bounds
    }

    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), eFontFont0, info.Width - 10);
            if ((mouse.y - info.Y) < ypos)
            {
                info.ActiveOptionID = i;
                return;
            }
        }
        i++;
    }
}

function dialog_options_mouse_click(DialogOptionsRenderingInfo *info, MouseButton button)
{
    info.RunActiveOption();
}

[close]

2. Keyboard controls
Spoiler

int dlg_opt_color = 14;
int dlg_opt_acolor = 13;
int dlg_opt_ncolor = 4;

function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
{
    // Create a 200x200 dialog options area at (50,100)
    info.X = 50;
    info.Y = 100;
    info.Width = 200;
    info.Height = 200;
    info.ActiveOptionID = 1; // set to first option
}

function dialog_options_render(DialogOptionsRenderingInfo *info)
{
    info.Surface.Clear(dlg_opt_color);
    int i = 1,  ypos = 0;
    // 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 = dlg_opt_acolor;
            else info.Surface.DrawingColor = dlg_opt_ncolor;
            info.Surface.DrawStringWrapped(5, ypos, info.Width - 10,
                    eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
            ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
        }
        i++;
    }
}

function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode)
{
    if (keycode == eKeyUpArrow && info.ActiveOptionID > 1)
        info.ActiveOptionID = info.ActiveOptionID - 1;
    if (keycode == eKeyDownArrow && info.ActiveOptionID < info.DialogToRender.OptionCount)
        info.ActiveOptionID = info.ActiveOptionID + 1;
    if (keycode == eKeyReturn || keycode == eKeySpace)
        info.RunActiveOption();
}
[close]

abstauber

I'd really love to test this, but as soon as I apply the width and height to *info, AGS crashes without any warning :embarrassed:

Crimson Wizard

Quote from: abstauber on Mon 01/12/2014 12:35:08
I'd really love to test this, but as soon as I apply the width and height to *info, AGS crashes without any warning :embarrassed:

Hmmmmmm. What version of AGS do you copy this over?

Can you show your entire script related to dialog options?

abstauber

I copied this over AGS 3.4.2 Alpha
A pm with the code is on its way 8-)

Crimson Wizard

Quote from: abstauber on Mon 01/12/2014 13:05:22
I copied this over AGS 3.4.2 Alpha
Should be 3.4.0.1, although I am not sure if that matters, because the archive contains all AGS program files.

Crimson Wizard


abstauber

I've adapted my code (by removing tons of workarounds) and it works like a charm so far.

SMF spam blocked by CleanTalk