Greying out choices that don't meet requirements...

TessSadist

Well-Known Member
Donor
Game Developer
Aug 4, 2019
1,298
5,562
Hi all,

I'm planning out my 0.3 (almost done with 0.2) a little for my game right now, and I was wondering if anyone knows how to do the following:

If you have a basic menu with choices, and some of the choices are not met with prior requirements (any type of say for example prior choice flag or numerical value based on a trait/stat), that the choices are shown in grey that you can see in game or some other muted color but they are not able to be picked/selected versus normal choices you do meet, etc. I also wonder that in say three different playthroughs you might have three different situations where certain choices would be there versus muted out so how complicated does it get to account for all of those situations. My lazy solution was to just either not have those choices there if they don't meet requirements (maybe even different menu sets or something based on flags/variables), but would be interesting if players could see options for future styles, etc. This might be a fairly easy thing to do but I'm so new at coding that I have no clue. I'll take a look at the Renpy documentation right after I release 0.2, but just taking a chance that someone here might already know this solution.
 

moskyx

Forum Fanatic
Jun 17, 2019
4,218
13,959
First of all, it's easy to add choices to any menu that will only be shown if the requirements are met. You don't need to create different menus checking that variable beforehand:

Python:
menu:
    "Go Right":
        jump goright
    "Go Left":
        jump goleft
    "Use Spell" if MyImportantVariable == True: #This option will only be shown if the condition is met
        jump usespell
Now you want the players to know there is a 'hidden' option they can't choose because they are missing some points. The 'easy' solution could be something like this:

Python:
label MyChoice:
    menu:
        "Go Right":
            jump goright
        "Go Left":
            jump goleft
        "Use Spell" if MyImportantVariable == True: #This option will only be shown if the condition is met
            jump usespell
        "{color=#999999}Use Spell{/color}" if MyImportantVariable == False: #This option will only appear if the condition is not met, you can change the color tag
            jump MyChoice
This way clicking on Use Spell when the condition is not met will leave you at the same menu. You can also add a little text to let the player know why is that so they don't think there's a bug or something.
I'm sure there are better methods, though
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,191
Python:
        "Use Spell" if MyImportantVariable == True: #This option will only be shown if the condition is met
            jump usespell
        "{color=#999999}Use Spell{/color}" if MyImportantVariable == False: #This option will only appear if the condition is not met, you can change the color tag
            jump MyChoice
No, no and no. This is too much works for something that Ren'py can do natively.
There's a configuration variable, , designed especially for this.

Code:
define config.menu_include_disabled = True
define gui.choice_button_text_insensitive_color = "#555"

label start:
    menu:
        "Enabled":
            pass
        "Disabled" if True is False:
            pass
    "Done"
    return
 

TessSadist

Well-Known Member
Donor
Game Developer
Aug 4, 2019
1,298
5,562
Thank you for the input and help, I think the second looks a little easier so I have to try that first. :)
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,607
2,256
Honestly, this is a question that's been in my head too, but I didn't have a reason to check it out. So thanks for asking, because now I have this alternative...

Rather than show or hide ALL disabled menu choices... you can do it selectively by altering the menu choices screen.

Before:
Python:
# ---- screens.rpy ----

screen choice(items):
    style_prefix "choice"

    vbox:
        for i in items:
            textbutton i.caption action i.action

After:
Python:
# ---- screens.rpy ----

screen choice(items):
    style_prefix "choice"

    vbox:
        for i in items:
            if i.action:
                if i.kwargs and i.kwargs.get( "enabled" ) is False:
                    textbutton i.caption action NullAction() sensitive False
                else:
                    textbutton i.caption action i.action
            else:
                textbutton i.caption

With the screen.rpy file altered in the above way, we can now pass a parameter ("enabled") to the menu choice. If is omitted, the menu choice will be shown (as usual). But if the value is False (or the result of any evaluation done by it is False)... then the menu choice is made unavailable - without the need to write the same menu item twice.

Parameters can be passed by putting them in parenthesis before the colon at the end of the menu choice. (enabled=True):

Python:
# ---- script.rpy ----

label start:

    scene black with fade

    "Start:.... "

    $ num3 = True

label do_menu_again:

    menu:
        "Some menu text, shown at the bottom"
        "Option 1":
            "This is option 1"

        "Option 2" (enabled=False):
            pass

        "Option 3" (enabled=num3 == True):
            "This is option 3"
            $ num3 = False
            jump do_menu_again

        "Option 4":
            "This is option 4"

    "*** THE END ***"

    return

In this example, "Option 2" is always disabled. "Option 3" is disabled depending on the value of the num3 variable.

The (enabled=num3 == True) is a bit of overkill on my part. (enabled=num3) would have worked equally well without as much typing. I just wanted to highlight that more complex checks could be put in there if needed... like maybe...
"Option 5" (enabled=alice_love >= 6):

Plus a reminder that it is still possible to hide some menu options completely by using something like...
"Option 6" if num6 == True:

Edit: Please note, I completely rewrote this code based on the technical refinements anne O'nymous suggested below. The original is included here:

You don't have permission to view the spoiler content. Log in or register now.
 
Last edited:
  • Like
Reactions: glassware

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,191
So thanks for asking, because now I have this alternative...
I like you, but this time you really over complicated it. And I don't just say this in regard of the native way to do it that I shown above on the thread.

Passing parameters in the text of the menu imply that you have to write the choice twice. Therefore, you can just use the fact that , available through :

Python:
# Insensitive button will have grey text
define gui.choice_button_text_insensitive_color = "#555"

screen choice(items):
    style_prefix "choice"

    vbox:
        for i in items:
            # If there's argument and the first one is False, 
            # then it's a disabled choice ; no action and none sensitive
            if i.args and i.args[0] is False:
                textbutton i.caption action NullAction() sensitive False
            # Else it's a regular choice.
            else:
                textbutton i.caption action i.action

label whatever:
    menu:
        "choice 1":
            "choice 1"
        # Just put the condition as first arguments instead of
        # using it as effective condition
        "choice 2"( True is False ):
            "choice 2"
Or, if you expect to use parameters, you can deports it in a keyword argument :
Python:
define gui.choice_button_text_insensitive_color = "#555"

screen choice(items):
    style_prefix "choice"

    vbox:
        for i in items:
            if i.kwargs and i.kwargs.get( "enabled" ) is False:
                textbutton i.caption action NullAction() sensitive False
            else:
                textbutton i.caption action i.action

label whatever:
    menu:
        "choice 1":
            "choice 1"
        "choice 2"( enabled=True is False ):
            "choice 2"
But still, the use of is better.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,607
2,256
Passing parameters in the text of the menu imply that you have to write the choice twice.
Yup. Well, sort of.
Depending on how I'd imagine using "unavailable" menu items - I might not always choose to write it twice.
... though, honestly, I can't think of an example where I wouldn't.

Therefore, you can just use the fact that , available through :
You're assuming I could understand args and kwargs... it's only been a couple of years... I'm easing myself into it slowly. In fairness, I probably could - and now I look it is pretty straight forward... but up until this point, I've been keeping my head firmly in the sand about the stuff I didn't already know, until I was spoon fed a better answer.

But still, the use of is better.
Aye. And certainly a lot simpler to implement.

But I also regarded "simple copy/paste change to screens.rpy" and "add the phrases '(disabled)' to a menu choice" to be pretty simple too.

In my head, my imaginary game had a mix of menus where sometimes you'd show options that were unavailable (as some sort of subtle prompt to the player) and sometimes you'd actually hide them (because sometimes they don't need to know).
config.menu_include_disabled is an all or nothing solution, I was trying to make it a little more flexible than that.

Ultimately, I knew there was probably a more elegant way to implement a similar solution - but that one was simple enough for me to follow given my current understanding... Seriously... kwargs, styles and classes all still terrify me - but I figure if I've gotten this far without them... :whistle:

In my defense... I did know parameters had been added to menus and choices... I just couldn't figure out how to do something clever with them.
 
Last edited:
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,191
You're assuming I could understand args and kwargs...
They are just variables you know, and they don't bite... at least they never bitten me.


[...] until I was spoon fed a better answer.
Open your mouth, the plane is coming ;)


While their name is just a convention, args and kwargs are two variables used to handle an undefined number of parameters.

To explain them, I have to go back to the base:

By default you have, def myFunction( param1, param2 ):. The function have two, and only two, parameters, all mandatory.
If you write myFunction( 1 ), Python/Ren'py will complain that a parameter is missing.
And if you write myFunction( 1, 2, 3 ), Python/Ren'py will complain that there's too much parameters.

Then come the optional parameters, def myFunction( param1, param2, param3=0 ):. Now the function have between two and three parameters.
If you write myFunction( 1 ), Python/Ren'py will still complain that a parameter is missing.
If you write myFunction( 1, 2, 3 ), the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 3
But if you write myFunction( 1, 2 ), Python will understand that you chose to not give the third parameter, and will assign it the value by default ; the one you gave in the declaration of the function. Therefore, the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 0

This imply that Python/Ren'py have two kind of parameters, named "arguments" in Python. The regular one ("param1" and "param2"), and keyword arguments ("param3").
The later are named like this because they can be used in two way.
The first one, you show it above. "param3" is the third parameters in the declaration line, it will be assigned with the third values passed to the function.
For the second one, imagine that your function only have more than one optional parameters, def myFunction( param1, param2, param3=0, param4=None ):. How do you pass a value to param4, but not to param3 ?
Of course, you can write myFunction( 1, 2, 0, 4 ), but what is the interest to have an optional parameter, if you have to give it a value when you call the function ?
Therefore, instead of relying on the order of the parameter, you can rely on its name. Which mean that if you write myFunction( 1, 2, param4=4 ), the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 0
  • param4 = 4
Therefore, the second way to use keyword parameters is to explicitly use their name when you call the function.

Now, what about the args and kwargs variables ?
They are a way to works not only with optional parameters, but also with a totally unknown number of parameters.
Basically you write it like this, def myFunction( *args, **kwargs ):. Every value passed directly to the function will be stored in a tuple named args, and every value passed to the function with the help of a keyword will be stored in dict named kwargs.
So, if now you write myFunction( 1, 2, param3=3, param4=4 ), you'll have
  • args = ( 1, 2 )
  • kwargs = { "param3": 3, "param4": 4 }
And with the same declaration, you can pass as many parameters than you want, or as you need.

Reported to the context of menu choices, most of the games don't need parameters for them. Some will need an "enabled" keyword parameter, other a "hint" one. Some will rely only on basic unnamed parameters, others only on keyword ones. There's just no way to know what will be needed.
Therefore, this feature rely on args and kwargs. This way, whatever the dev need, the values will be stored and available.

On a side note, * and ** are magic commands of Python, and they works both way.
In a function declaration, they'll groups unnamed arguments (*) and keyword arguments (**) using the variable name that follow them. Therefore you can perfectly have def myFunction( *blabla, **annoying ), it will works.
But in a function call, they will do the opposite, splitting the content of args into individual values, and the content of kwargs into keyword parameters.

Code:
init python:
    def myFunction( param1, param2, param3=0, param4=False ):
        store.param1 = param1
        store.param2 = param2
        store.param3 = param3
        store.param4 = param4

    def myProxy( *args, **kwargs ):
        myFunction( *args, **kwargs )

default param1 = None
default param2 = None
default param3 = None
default param4 = None

label start:
    $ myProxy( 1, 2, param3=3, param4=4)
    "[param1] / [param2] / [param3] / [param4]"

In my head, my imaginary game had a mix of menus where sometimes you'd show options that were unavailable (as some sort of subtle prompt to the player) and sometimes you'd actually hide them (because sometimes they don't need to know).
And you achieve it in your refactored code.


Ultimately, I knew there was probably a more elegant way to implement a similar solution - but that one was simple enough for me to follow given my current understanding...
I'll tell you a secret, don't repeat it: At one time, before parameters were added to menu choice, I had things as ugly as
Code:
menu:
     "open.png|(12,14)|open door|True" if door.isClosed:
          [...]
     "open.png|(12,14)|open door|False" if not door.isClosed:
          [...]
and yet I didn't gave the ugliest example, I have a reputation to save ;)


Seriously... kwargs, styles and classes all still terrify me -
And now, are you friend with at least the first one ?


In my defense... I did know parameters had been added to menus and choices... I just couldn't figure out how to do something clever with them.
It's a legit defense. It's a recent addition (7.2.0 if I remember correctly), and there's sometimes so many things that can change from a version to another one, that it's easy to miss one.
I'm sure that myself have missed some.
 

TessSadist

Well-Known Member
Donor
Game Developer
Aug 4, 2019
1,298
5,562
So I remember when I first looked at code for the very first time (right around Valentine's Day) in my life. It looked like a foreign language! Now, I know a little more, but the above extended discussion is definitely back to foreign language status, haha! (But I actually get some of it so maybe there's hope...) :) Thank you all!
 
  • Like
Reactions: anne O'nymous

OscarSix

Active Member
Modder
Donor
Jul 27, 2019
829
6,671
They are just variables you know, and they don't bite... at least they never bitten me.




Open your mouth, the plane is coming ;)


While their name is just a convention, args and kwargs are two variables used to handle an undefined number of parameters.

To explain them, I have to go back to the base:

By default you have, def myFunction( param1, param2 ):. The function have two, and only two, parameters, all mandatory.
If you write myFunction( 1 ), Python/Ren'py will complain that a parameter is missing.
And if you write myFunction( 1, 2, 3 ), Python/Ren'py will complain that there's too much parameters.

Then come the optional parameters, def myFunction( param1, param2, param3=0 ):. Now the function have between two and three parameters.
If you write myFunction( 1 ), Python/Ren'py will still complain that a parameter is missing.
If you write myFunction( 1, 2, 3 ), the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 3
But if you write myFunction( 1, 2 ), Python will understand that you chose to not give the third parameter, and will assign it the value by default ; the one you gave in the declaration of the function. Therefore, the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 0

This imply that Python/Ren'py have two kind of parameters, named "arguments" in Python. The regular one ("param1" and "param2"), and keyword arguments ("param3").
The later are named like this because they can be used in two way.
The first one, you show it above. "param3" is the third parameters in the declaration line, it will be assigned with the third values passed to the function.
For the second one, imagine that your function only have more than one optional parameters, def myFunction( param1, param2, param3=0, param4=None ):. How do you pass a value to param4, but not to param3 ?
Of course, you can write myFunction( 1, 2, 0, 4 ), but what is the interest to have an optional parameter, if you have to give it a value when you call the function ?
Therefore, instead of relying on the order of the parameter, you can rely on its name. Which mean that if you write myFunction( 1, 2, param4=4 ), the parameters will be
  • param1 = 1
  • param2 = 2
  • param3 = 0
  • param4 = 4
Therefore, the second way to use keyword parameters is to explicitly use their name when you call the function.

Now, what about the args and kwargs variables ?
They are a way to works not only with optional parameters, but also with a totally unknown number of parameters.
Basically you write it like this, def myFunction( *args, **kwargs ):. Every value passed directly to the function will be stored in a tuple named args, and every value passed to the function with the help of a keyword will be stored in dict named kwargs.
So, if now you write myFunction( 1, 2, param3=3, param4=4 ), you'll have
  • args = ( 1, 2 )
  • kwargs = { "param3": 3, "param4": 4 }
And with the same declaration, you can pass as many parameters than you want, or as you need.

Reported to the context of menu choices, most of the games don't need parameters for them. Some will need an "enabled" keyword parameter, other a "hint" one. Some will rely only on basic unnamed parameters, others only on keyword ones. There's just no way to know what will be needed.
Therefore, this feature rely on args and kwargs. This way, whatever the dev need, the values will be stored and available.

On a side note, * and ** are magic commands of Python, and they works both way.
In a function declaration, they'll groups unnamed arguments (*) and keyword arguments (**) using the variable name that follow them. Therefore you can perfectly have def myFunction( *blabla, **annoying ), it will works.
But in a function call, they will do the opposite, splitting the content of args into individual values, and the content of kwargs into keyword parameters.

Code:
init python:
    def myFunction( param1, param2, param3=0, param4=False ):
        store.param1 = param1
        store.param2 = param2
        store.param3 = param3
        store.param4 = param4

    def myProxy( *args, **kwargs ):
        myFunction( *args, **kwargs )

default param1 = None
default param2 = None
default param3 = None
default param4 = None

label start:
    $ myProxy( 1, 2, param3=3, param4=4)
    "[param1] / [param2] / [param3] / [param4]"



And you achieve it in your refactored code.




I'll tell you a secret, don't repeat it: At one time, before parameters were added to menu choice, I had things as ugly as
Code:
menu:
     "open.png|(12,14)|open door|True" if door.isClosed:
          [...]
     "open.png|(12,14)|open door|False" if not door.isClosed:
          [...]
and yet I didn't gave the ugliest example, I have a reputation to save ;)




And now, are you friend with at least the first one ?




It's a legit defense. It's a recent addition (7.2.0 if I remember correctly), and there's sometimes so many things that can change from a version to another one, that it's easy to miss one.
I'm sure that myself have missed some.
Thanks for going into *args and **kwargs while I understood the basics your explanation has seriously helped my understanding. Now if only I could understand classes and the __init__ shit :LOL:
 
  • Like
Reactions: 79flavors